func (smm *SkyManagerMonitor) maintainConnectedNodesList(newcns skynode.NodeInfoSlice, statusMsgChan chan<- string) {
	log.Debug("SkyManagerMonitor.maintainConnectedNodesList: Start")
	defer log.Debug("SkyManagerMonitor.maintainConnectedNodesList: End")
	smm.m.Lock()
	defer smm.m.Unlock()
	if newcns == nil {
		log.Error("SkyManagerMonitor.maintainConnectedNodesList: newcns is nil.")
		return
	}
	for _, v := range newcns {
		_, hasKey := smm.connectedNodes[v.Key]
		if hasKey {
			delete(smm.connectedNodes, v.Key)
			smm.connectedNodes[v.Key] = v
		} else {
			smm.connectedNodes[v.Key] = v
			msg := fmt.Sprintf(wcconst.MsgNodeConnected, v.Key, len(smm.connectedNodes))
			log.Debugln(msg)
			statusMsgChan <- msg
		}
	}
	if len(smm.connectedNodes) > len(newcns) {
		niMap := skynode.NodeInfoSliceToMap(newcns)
		for _, v := range smm.connectedNodes {
			_, hasKey := niMap[v.Key]
			if !hasKey {
				log.Debugf("SkyManagerMonitor.maintainConnectedNodesList: Node Removed:\n%s\n", v.FmtString())
				delete(smm.connectedNodes, v.Key)
				msg := fmt.Sprintf(wcconst.MsgNodeDisconnected, v.Key, len(smm.connectedNodes))
				log.Debugln(msg)
				statusMsgChan <- msg
			}
		}
	}
}
maintainConnectedNodeList is responsible for maintaining (adding, updating and deleting) Nodes from the
Monitors internal connectedNodeList.
TODO: Support use of channels for signaling of change events
package skymgrmon
import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"
	"github.com/BigOokie/skywire-wing-commander/internal/skynode"
	"github.com/BigOokie/skywire-wing-commander/internal/wcconst"
	log "github.com/sirupsen/logrus"
)
const (
	managerAPIGetAllConnectedNodes = "conn/getAll"
)
type SkyManagerMonitor struct {
	ManagerAddress       string
	DiscoveryAddress     string
	cancelFunc           func()
	monitorStatusMsgChan chan<- string
	connectedNodes       skynode.NodeInfoMap
	discConnNodeCount    int
	m                    sync.Mutex
	updateStarted        bool
	updateMsgChan        chan string
}
func (smm *SkyManagerMonitor) SetCancelFunc(cf func()) {
	smm.m.Lock()
	defer smm.m.Unlock()
	smm.cancelFunc = cf
}
func (smm *SkyManagerMonitor) GetCancelFunc() func() {
	smm.m.Lock()
	defer smm.m.Unlock()
	return smm.cancelFunc
}
func (smm *SkyManagerMonitor) DoCancelFunc() {
	smm.m.Lock()
	defer smm.m.Unlock()
	if smm.cancelFunc != nil {
		smm.cancelFunc()
	}
}
func (smm *SkyManagerMonitor) GetUpdateStarted() bool {
	smm.m.Lock()
	defer smm.m.Unlock()
	return smm.updateStarted
}
func (smm *SkyManagerMonitor) SetUpdateStarted(flag bool) {
	smm.m.Lock()
	defer smm.m.Unlock()
	smm.updateStarted = flag
}
func NewMonitor(manageraddress, discoveryaddress string) *SkyManagerMonitor {
	return &SkyManagerMonitor{
		ManagerAddress:       manageraddress,
		DiscoveryAddress:     discoveryaddress,
		cancelFunc:           nil,
		monitorStatusMsgChan: nil,
		connectedNodes:       make(skynode.NodeInfoMap),
		discConnNodeCount:    0,
		updateStarted:        false,
		updateMsgChan:        nil,
	}
}
func (smm *SkyManagerMonitor) RunManagerMonitor(runctx context.Context, doCancelFunc func(), statusMsgChan chan<- string, pollInt time.Duration) {
	log.Debugf("SkyManagerMonitor.RunManagerMonitor: Start (Interval: %v)", pollInt)
	defer log.Debugln("SkyManagerMonitor.RunManagerMonitor: End")
	smm.SetCancelFunc(doCancelFunc)
	smm.monitorStatusMsgChan = statusMsgChan
	ticker := time.NewTicker(pollInt)
	for {
		select {
		case <-ticker.C:
			newcns, err := getAllNodesList(smm.ManagerAddress)
			if err != nil {
				log.Error(err)
				statusMsgChan <- wcconst.MsgErrorGetNodes
			} else {
				smm.maintainConnectedNodesList(newcns, statusMsgChan)
			}
		case <-runctx.Done():
			log.Debugln("SkyManagerMonitor.RunManagerMonitor: Done Event.")
			return
		}
	}
}
func (smm *SkyManagerMonitor) StopManagerMonitor() {
	log.Debugln("SkyManagerMonitor.StopManagerMonitor: Start")
	defer log.Debugln("SkyManagerMonitor.StopManagerMonitor: End")
	if smm.IsRunning() {
		smm.DoCancelFunc()
		smm.SetCancelFunc(nil)
		close(smm.monitorStatusMsgChan)
		smm.monitorStatusMsgChan = nil
		log.Debug(wcconst.MsgMonitorStopped)
	}
}
func (smm *SkyManagerMonitor) ConnectedDiscNodeCount() (int, error) {
	log.Debug("SkyManagerMonitor.ConnectedDiscNodeCount: Start")
	defer log.Debugln("SkyManagerMonitor.ConnectedDiscNodeCount: End")
	discConnNodeCount := 0
	if smm.GetConnectedNodeCount() == 0 {
		log.Debug("SkyManagerMonitor.ConnectedDiscNodeCount: Connected Node list is empty. No work to do.")
		return discConnNodeCount, nil
	}
	discNodes, err := getAllNodesList(smm.DiscoveryAddress)
	if err != nil {
		log.Errorf("SkyManagerMonitor.ConnectedDiscNodeCount: Error contacting Discovery Server: %v", err)
		return discConnNodeCount, err
	} else if len(discNodes) == 0 {
		log.Debugln("SkyManagerMonitor.ConnectedDiscNodeCount: Empty Discovery Server Node List.")
		return discConnNodeCount, nil
	} else {
		log.Debugf("SkyManagerMonitor.ConnectedDiscNodeCount: Discovery Node Returned %v Nodes.", len(discNodes))
		smm.m.Lock()
		defer smm.m.Unlock()
		discNodeMap := skynode.NodeInfoSliceToMap(discNodes)
		for _, v := range smm.connectedNodes {
			_, hasKey := discNodeMap[v.Key]
			if hasKey {
				log.Debugf("SkyManagerMonitor.ConnectedDiscNodeCount: Node Connected:\n%s\n", v.FmtString())
				discConnNodeCount++
			} else {
				log.Debugf("SkyManagerMonitor.ConnectedDiscNodeCount: Node Not Connected:\n%s\n", v.FmtString())
			}
		}
		log.Debugf("SkyManagerMonitor.ConnectedDiscNodeCount: %d Nodes Connected to Discovery", discConnNodeCount)
	}
	return discConnNodeCount, nil
}
func (smm *SkyManagerMonitor) IsRunning() bool {
	return smm.GetCancelFunc() != nil
}
func getAllNodesList(managerAddr string) (cns skynode.NodeInfoSlice, err error) {
	log.Debug("SkyManagerMonitor.getAllNodesList: Start")
	defer log.Debug("SkyManagerMonitor.getAllNodesList: End")
	userAgent := "Wing Commander Telegram Bot " + wcconst.BotVersion
	client := &http.Client{}
	client.Timeout = time.Second * 30
	apiURL := fmt.Sprintf("http://%s/%s", managerAddr, managerAPIGetAllConnectedNodes)
	log.Debugln(apiURL)
	req, err := http.NewRequest(http.MethodGet, apiURL, nil)
	if err != nil {
		log.Errorf("http.NewRequest() failed with '%s'\n", err)
		return
	}
	req.Header.Set("User-Agent", userAgent)
	resp, err := client.Do(req)
	if err != nil {
		log.Errorf("client.Do() failed with '%s'\n", err)
		return
	}
	defer resp.Body.Close()
	respbuf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Errorf("ioutil.ReadAll() failed with '%s'\n", err)
		return
	}
	err = json.Unmarshal(respbuf, &cns)
	if err != nil {
		log.Error(err)
	}
	return
}