2015-05-17 19:33:41 +00:00
|
|
|
package goadb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
2016-01-10 21:33:22 +00:00
|
|
|
"math/rand"
|
2015-05-17 19:33:41 +00:00
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"sync/atomic"
|
2015-11-29 18:39:58 +00:00
|
|
|
"time"
|
2015-05-17 19:33:41 +00:00
|
|
|
|
|
|
|
"github.com/zach-klippenstein/goadb/util"
|
|
|
|
"github.com/zach-klippenstein/goadb/wire"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
DeviceWatcher publishes device status change events.
|
|
|
|
If the server dies while listening for events, it restarts the server.
|
|
|
|
*/
|
|
|
|
type DeviceWatcher struct {
|
|
|
|
*deviceWatcherImpl
|
|
|
|
}
|
|
|
|
|
2015-09-08 04:19:59 +00:00
|
|
|
// DeviceStateChangedEvent represents a device state transition.
|
2015-05-17 19:33:41 +00:00
|
|
|
type DeviceStateChangedEvent struct {
|
|
|
|
Serial string
|
2015-09-08 04:19:59 +00:00
|
|
|
OldState DeviceState
|
|
|
|
NewState DeviceState
|
|
|
|
}
|
|
|
|
|
|
|
|
// CameOnline returns true if this event represents a device coming online.
|
|
|
|
func (s DeviceStateChangedEvent) CameOnline() bool {
|
|
|
|
return s.OldState != StateOnline && s.NewState == StateOnline
|
|
|
|
}
|
|
|
|
|
|
|
|
// WentOffline returns true if this event represents a device going offline.
|
|
|
|
func (s DeviceStateChangedEvent) WentOffline() bool {
|
|
|
|
return s.OldState == StateOnline && s.NewState != StateOnline
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeviceState represents one of the 3 possible states adb will report devices.
|
|
|
|
// A device can be communicated with when it's in StateOnline.
|
|
|
|
// A USB device will transition from StateDisconnected->StateOffline->StateOnline when
|
|
|
|
// plugged in, and then StateOnline->StateDisconnected when unplugged.
|
|
|
|
// If code doesn't care about specific states, DeviceStateChangedEvent provides methods
|
|
|
|
// to query at a higher level.
|
|
|
|
//go:generate stringer -type=DeviceState
|
|
|
|
type DeviceState int8
|
|
|
|
|
|
|
|
const (
|
|
|
|
StateDisconnected DeviceState = iota
|
|
|
|
StateOffline
|
|
|
|
StateOnline
|
|
|
|
)
|
|
|
|
|
|
|
|
var deviceStateStrings = map[string]DeviceState{
|
|
|
|
"": StateDisconnected,
|
|
|
|
"offline": StateOffline,
|
|
|
|
"device": StateOnline,
|
2015-05-17 19:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type deviceWatcherImpl struct {
|
2016-01-10 21:33:22 +00:00
|
|
|
server Server
|
2015-05-17 19:33:41 +00:00
|
|
|
|
|
|
|
// If an error occurs, it is stored here and eventChan is close immediately after.
|
|
|
|
err atomic.Value
|
|
|
|
|
|
|
|
eventChan chan DeviceStateChangedEvent
|
|
|
|
}
|
|
|
|
|
2016-01-10 21:33:22 +00:00
|
|
|
func NewDeviceWatcher(server Server) *DeviceWatcher {
|
2015-05-17 19:33:41 +00:00
|
|
|
watcher := &DeviceWatcher{&deviceWatcherImpl{
|
2016-01-10 21:33:22 +00:00
|
|
|
server: server,
|
|
|
|
eventChan: make(chan DeviceStateChangedEvent),
|
2015-05-17 19:33:41 +00:00
|
|
|
}}
|
|
|
|
|
|
|
|
runtime.SetFinalizer(watcher, func(watcher *DeviceWatcher) {
|
|
|
|
watcher.Shutdown()
|
|
|
|
})
|
|
|
|
|
|
|
|
go publishDevices(watcher.deviceWatcherImpl)
|
|
|
|
|
2015-09-06 05:44:36 +00:00
|
|
|
return watcher
|
2015-05-17 19:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
C returns a channel than can be received on to get events.
|
|
|
|
If an unrecoverable error occurs, or Shutdown is called, the channel will be closed.
|
|
|
|
*/
|
|
|
|
func (w *DeviceWatcher) C() <-chan DeviceStateChangedEvent {
|
|
|
|
return w.eventChan
|
|
|
|
}
|
|
|
|
|
|
|
|
// Err returns the error that caused the channel returned by C to be closed, if C is closed.
|
|
|
|
// If C is not closed, its return value is undefined.
|
|
|
|
func (w *DeviceWatcher) Err() error {
|
|
|
|
if err, ok := w.err.Load().(error); ok {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown stops the watcher from listening for events and closes the channel returned
|
|
|
|
// from C.
|
|
|
|
func (w *DeviceWatcher) Shutdown() {
|
|
|
|
// TODO(z): Implement.
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *deviceWatcherImpl) reportErr(err error) {
|
|
|
|
w.err.Store(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
publishDevices reads device lists from scanner, calculates diffs, and publishes events on
|
|
|
|
eventChan.
|
|
|
|
Returns when scanner returns an error.
|
|
|
|
Doesn't refer directly to a *DeviceWatcher so it can be GCed (which will,
|
|
|
|
in turn, close Scanner and stop this goroutine).
|
|
|
|
|
|
|
|
TODO: to support shutdown, spawn a new goroutine each time a server connection is established.
|
|
|
|
This goroutine should read messages and send them to a message channel. Can write errors directly
|
2015-11-29 18:39:58 +00:00
|
|
|
to errVal. publishDevicesUntilError should take the msg chan and the scanner and select on the msg chan and stop chan, and if the stop
|
2015-05-17 19:33:41 +00:00
|
|
|
chan sends, close the scanner and return true. If the msg chan closes, just return false.
|
|
|
|
publishDevices can look at ret val: if false and err == EOF, reconnect. If false and other error, report err
|
|
|
|
and abort. If true, report no error and stop.
|
|
|
|
*/
|
|
|
|
func publishDevices(watcher *deviceWatcherImpl) {
|
|
|
|
defer close(watcher.eventChan)
|
|
|
|
|
2015-09-08 04:19:59 +00:00
|
|
|
var lastKnownStates map[string]DeviceState
|
2015-05-17 19:33:41 +00:00
|
|
|
finished := false
|
|
|
|
|
|
|
|
for {
|
2016-01-10 21:33:22 +00:00
|
|
|
scanner, err := connectToTrackDevices(watcher.server)
|
2015-05-17 19:33:41 +00:00
|
|
|
if err != nil {
|
|
|
|
watcher.reportErr(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
finished, err = publishDevicesUntilError(scanner, watcher.eventChan, &lastKnownStates)
|
|
|
|
|
|
|
|
if finished {
|
|
|
|
scanner.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if util.HasErrCode(err, util.ConnectionResetError) {
|
|
|
|
// The server died, restart and reconnect.
|
2015-11-29 18:39:58 +00:00
|
|
|
|
|
|
|
// Delay by a random [0ms, 500ms) in case multiple DeviceWatchers are trying to
|
|
|
|
// start the same server.
|
|
|
|
delay := time.Duration(rand.Intn(500)) * time.Millisecond
|
|
|
|
|
|
|
|
log.Printf("[DeviceWatcher] server died, restarting in %s…", delay)
|
|
|
|
time.Sleep(delay)
|
2016-01-10 21:33:22 +00:00
|
|
|
if err := watcher.server.Start(); err != nil {
|
2015-05-17 19:33:41 +00:00
|
|
|
log.Println("[DeviceWatcher] error restarting server, giving up")
|
|
|
|
watcher.reportErr(err)
|
|
|
|
return
|
|
|
|
} // Else server should be running, continue listening.
|
|
|
|
} else {
|
|
|
|
// Unknown error, don't retry.
|
|
|
|
watcher.reportErr(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-10 21:33:22 +00:00
|
|
|
func connectToTrackDevices(server Server) (wire.Scanner, error) {
|
|
|
|
conn, err := server.Dial()
|
2015-05-17 19:33:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := wire.SendMessageString(conn, "host:track-devices"); err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-12-28 23:28:53 +00:00
|
|
|
if _, err := conn.ReadStatus("host:track-devices"); err != nil {
|
2015-05-17 19:33:41 +00:00
|
|
|
conn.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
2015-09-08 04:19:59 +00:00
|
|
|
func publishDevicesUntilError(scanner wire.Scanner, eventChan chan<- DeviceStateChangedEvent, lastKnownStates *map[string]DeviceState) (finished bool, err error) {
|
2015-05-17 19:33:41 +00:00
|
|
|
for {
|
|
|
|
msg, err := scanner.ReadMessage()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
deviceStates, err := parseDeviceStates(string(msg))
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, event := range calculateStateDiffs(*lastKnownStates, deviceStates) {
|
|
|
|
eventChan <- event
|
|
|
|
}
|
|
|
|
*lastKnownStates = deviceStates
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-08 04:19:59 +00:00
|
|
|
func parseDeviceStates(msg string) (states map[string]DeviceState, err error) {
|
|
|
|
states = make(map[string]DeviceState)
|
2015-05-17 19:33:41 +00:00
|
|
|
|
|
|
|
for lineNum, line := range strings.Split(msg, "\n") {
|
|
|
|
if len(line) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
fields := strings.Split(line, "\t")
|
|
|
|
if len(fields) != 2 {
|
|
|
|
err = util.Errorf(util.ParseError, "invalid device state line %d: %s", lineNum, line)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-09-08 04:19:59 +00:00
|
|
|
serial, stateString := fields[0], fields[1]
|
|
|
|
state, ok := deviceStateStrings[stateString]
|
|
|
|
if !ok {
|
|
|
|
err = util.Errorf(util.ParseError, "invalid device state: %s", state)
|
|
|
|
}
|
2015-05-17 19:33:41 +00:00
|
|
|
states[serial] = state
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-09-08 04:19:59 +00:00
|
|
|
func calculateStateDiffs(oldStates, newStates map[string]DeviceState) (events []DeviceStateChangedEvent) {
|
2015-05-17 19:33:41 +00:00
|
|
|
for serial, oldState := range oldStates {
|
|
|
|
newState, ok := newStates[serial]
|
|
|
|
|
|
|
|
if oldState != newState {
|
|
|
|
if ok {
|
|
|
|
// Device present in both lists: state changed.
|
|
|
|
events = append(events, DeviceStateChangedEvent{serial, oldState, newState})
|
|
|
|
} else {
|
|
|
|
// Device only present in old list: device removed.
|
2015-09-08 04:19:59 +00:00
|
|
|
events = append(events, DeviceStateChangedEvent{serial, oldState, StateDisconnected})
|
2015-05-17 19:33:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for serial, newState := range newStates {
|
|
|
|
if _, ok := oldStates[serial]; !ok {
|
|
|
|
// Device only present in new list: device added.
|
2015-09-08 04:19:59 +00:00
|
|
|
events = append(events, DeviceStateChangedEvent{serial, StateDisconnected, newState})
|
2015-05-17 19:33:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return events
|
|
|
|
}
|