schnutibox/vendor/github.com/fhs/gompd/v2/mpd/watcher.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()
}