120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
|
// Copyright 2013 The GoMPD Authors. All rights reserved.
|
||
|
// Use of this source code is governed by the MIT
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package mpd
|
||
|
|
||
|
// Watcher represents a MPD client connection that can be watched for events.
|
||
|
type Watcher struct {
|
||
|
conn *Client // client connection to MPD
|
||
|
exit chan bool // channel used to ask loop to terminate
|
||
|
done chan bool // channel indicating loop has terminated
|
||
|
names chan []string // channel to set new subsystems to watch
|
||
|
Event chan string // event channel
|
||
|
Error chan error // error channel
|
||
|
}
|
||
|
|
||
|
// NewWatcher connects to MPD server and watches for changes in subsystems
|
||
|
// names. If no subsystem is specified, all changes are reported.
|
||
|
//
|
||
|
// See http://www.musicpd.org/doc/protocol/command_reference.html#command_idle
|
||
|
// for valid subsystem names.
|
||
|
func NewWatcher(net, addr, passwd string, names ...string) (w *Watcher, err error) {
|
||
|
conn, err := DialAuthenticated(net, addr, passwd)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
w = &Watcher{
|
||
|
conn: conn,
|
||
|
Event: make(chan string),
|
||
|
Error: make(chan error),
|
||
|
done: make(chan bool),
|
||
|
// Buffer channels to avoid race conditions with noIdle
|
||
|
names: make(chan []string, 1),
|
||
|
exit: make(chan bool, 1),
|
||
|
}
|
||
|
go w.watch(names...)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (w *Watcher) watch(names ...string) {
|
||
|
defer w.closeChans()
|
||
|
|
||
|
// We can block in two places: idle and sending on Event/Error channels.
|
||
|
// We need to check w.exit and w.names after each.
|
||
|
for {
|
||
|
changed, err := w.conn.idle(names...)
|
||
|
select {
|
||
|
case <-w.exit:
|
||
|
// If Close interrupted idle with a noidle, and we don't
|
||
|
// exit now, we will block trying to send on Event/Error.
|
||
|
return
|
||
|
case names = <-w.names:
|
||
|
// Received new subsystems to watch. Ignore results.
|
||
|
changed = []string{}
|
||
|
err = nil
|
||
|
default: // continue
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case err != nil:
|
||
|
w.Error <- err
|
||
|
default:
|
||
|
for _, name := range changed {
|
||
|
w.Event <- name
|
||
|
}
|
||
|
}
|
||
|
select {
|
||
|
case <-w.exit:
|
||
|
// If Close unblocks us from sending on Event/Error channels,
|
||
|
// we should exit now because noidle might be sent out
|
||
|
// before we get to idle.
|
||
|
return
|
||
|
case names = <-w.names:
|
||
|
// If method Subsystems unblocks us from sending on Event/Error
|
||
|
// channels, the next call to idle should be on the new names.
|
||
|
default: // continue
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (w *Watcher) closeChans() {
|
||
|
close(w.Event)
|
||
|
close(w.Error)
|
||
|
close(w.names)
|
||
|
close(w.exit)
|
||
|
close(w.done)
|
||
|
}
|
||
|
|
||
|
func (w *Watcher) consume() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-w.Event:
|
||
|
case <-w.Error:
|
||
|
default:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Subsystems interrupts watching current subsystems, consumes all
|
||
|
// outstanding values from Event and Error channels, and then
|
||
|
// changes the subsystems to watch for to names.
|
||
|
func (w *Watcher) Subsystems(names ...string) {
|
||
|
w.names <- names
|
||
|
w.consume()
|
||
|
w.conn.noIdle()
|
||
|
}
|
||
|
|
||
|
// Close closes Event and Error channels, and the connection to MPD server.
|
||
|
func (w *Watcher) Close() error {
|
||
|
w.exit <- true
|
||
|
w.consume()
|
||
|
w.conn.noIdle()
|
||
|
|
||
|
<-w.done // wait for idle to finish and channels to close
|
||
|
// At this point, watch goroutine has ended,
|
||
|
// so it's safe to close connection.
|
||
|
return w.conn.Close()
|
||
|
}
|