// // rhctl // // Copyright (C) 2009-2016 Christian Pointner // // This file is part of rhctl. // // rhctl is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhctl is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhctl. If not, see . // package main import ( "sort" "strings" "time" "github.com/spreadspace/telgo" ) type TelnetInterface struct { server *telgo.Server } func genStateString(values []bool) string { var states []string for _, val := range values { if val { states = append(states, "1") } else { states = append(states, "0") } } return strings.Join(states, ",") } func telnetCmdState(c *telgo.Client, args []string, ctrl *SwitchControl) bool { resp := make(chan interface{}) ctrl.Commands <- &Command{Type: CmdState, Response: resp} r := <-resp switch r.(type) { case error: c.Sayln("%v", r) case State: s := r.(State) c.Say("Mood: %v", s.Mood) if !s.Settled { c.Sayln(" (settling)") } else { c.Sayln("") } c.Sayln("Switch:") c.Sayln(" audio: (last updated: %v ago)", time.Since(s.Switch.AudioInputsUpdated)) for num, out := range s.Switch.Audio { c.Say(" out %d: ", num+1) if out.Silence { c.Sayln("%s (silent, since > %v)", genStateString(out.Inputs[:]), time.Since(s.Switch.AudioSilenceUpdated)) } else { c.Sayln("%s", genStateString(out.Inputs[:])) } } c.Sayln(" gpi: %s (last updated: %v ago)", genStateString(s.Switch.GPI[:]), time.Since(s.Switch.GPIUpdated)) c.Sayln(" relay: %s (last updated: %v ago)", genStateString(s.Switch.Relay[:]), time.Since(s.Switch.RelayUpdated)) c.Sayln(" oc: %s (last updated: %v ago)", genStateString(s.Switch.OC[:]), time.Since(s.Switch.OCUpdated)) c.Sayln("Server:") var names []string for n, _ := range s.Server { names = append(names, n) } sort.Strings(names) for _, name := range names { if name == s.ActiveServer { c.Say(" * ") } else { c.Say(" ") } c.Sayln("%s(%s): '%s' (last updated: %v ago)", name, s.Server[name].Health, s.Server[name].Channel, time.Since(s.Server[name].Updated)) } default: c.Sayln("invalid response of type %T: %+v", r, r) } return false } func telnetUpdateListener(c *telgo.Client, ctrl *SwitchControl) { ch := c.UserData.(chan interface{}) for { data, ok := <-ch if !ok { return } switch data.(type) { case SwitchUpdate: update := data.(SwitchUpdate) if !c.Sayln("audio-switch update(%v): %s", update.Type, update.Data) { ctrl.Updates.Unsub(ch) return } case SwitchState: state := data.(SwitchState) silence := "no output found in state??" if len(state.Audio) > 0 { if state.Audio[0].Silence { silence = "output 1 is silent!!" } else { silence = "output 1 is noisy" } } if !c.Sayln("audio-switch state: %s ... (use command 'state' for more info)", silence) { ctrl.Updates.Unsub(ch) return } case ServerState: state := data.(ServerState) if !c.Sayln("playout-server(%s): health=%s, channel=%s", state.Name, state.Health, state.Channel) { ctrl.Updates.Unsub(ch) return } case State: state := data.(State) if !c.Sayln("overall state: mood is %s ... (use command 'state' for more info)", state.Mood) { ctrl.Updates.Unsub(ch) return } default: if !c.Sayln("unknown update of type: %T", data) { ctrl.Updates.Unsub(ch) return } } } } func telnetCmdListen(c *telgo.Client, args []string, ctrl *SwitchControl) bool { if len(args) <= 1 { c.Sayln("missing argument: ") return false } var ch chan interface{} if c.UserData == nil { ch = ctrl.Updates.Sub() c.UserData = ch } else { ch = c.UserData.(chan interface{}) } switch strings.ToLower(args[1]) { case "state": ctrl.Updates.AddSub(ch, "state") case "server": ctrl.Updates.AddSub(ch, "server:state") case "switch": ctrl.Updates.AddSub(ch, "switch:state") case "audio": fallthrough case "gpi": fallthrough case "oc": fallthrough case "relay": fallthrough case "silence": ctrl.Updates.AddSub(ch, "switch:"+args[1]) default: c.Sayln("unknown message type") return false } go telnetUpdateListener(c, ctrl) return false } func telnetCmdServer(c *telgo.Client, args []string, ctrl *SwitchControl) bool { resp := make(chan interface{}) ctrl.Commands <- &Command{Type: CmdServer, Args: args[1:], Response: resp} r := <-resp switch r.(type) { case error: c.Sayln("%v", r) case bool: if !r.(bool) { c.Sayln("the switch-over was denied - is the requested server alive? is the state settling?") } } return false } func telnetCmdSwitch(c *telgo.Client, args []string, ctrl *SwitchControl) bool { if len(args) < 2 { c.Sayln("missing switch command") return false } resp := make(chan interface{}) ctrl.Commands <- &Command{Type: CmdSwitch, Args: args[1:], Response: resp} r := <-resp switch r.(type) { case error: c.Sayln("%v", r) case SwitchResponse: if r.(SwitchResponse).Result != SwitchOK { c.Sayln("%v: %s", r.(SwitchResponse).Result, r.(SwitchResponse).Message) } default: c.Sayln("invalid response of type %T: %+v", r, r) } return false } func telnetHelp(c *telgo.Client, args []string) bool { switch len(args) { case 2: switch strings.ToLower(args[1]) { case "quit": c.Sayln("usage: quit") c.Sayln(" terminates the client connection. You may also use Ctrl-D to do this.") return false case "help": c.Sayln("usage: help [ ]") c.Sayln(" prints command overview or detailed info to .") return false case "state": c.Sayln("usage: state") c.Sayln(" show the state of the switch and servers") return false case "listen": c.Sayln("usage: listen ") c.Sayln(" subscribe to messages of type . The following types are allowed:") c.Sayln(" - state overall state changes") c.Sayln(" - server state/health of the playout server") c.Sayln(" - switch state/health of switch") c.Sayln(" - audio audio input/output mapping changes") c.Sayln(" - gpi general purpose input state messages") c.Sayln(" - oc open-collector state messages") c.Sayln(" - relay relay state messages") c.Sayln(" - silence state of the silence detector") return false case "server": c.Sayln("usage: server ") c.Sayln(" switch to the server of name . If the given server name does not") c.Sayln(" exist or is dead, the switch-over will be denied") return false case "switch": c.Sayln("usage: switch [ [ ] ... ]") c.Sayln(" send commands to tha audio switch directly. Possible commands:") help := SwitchCommandHelp() for _, line := range help { c.Sayln(" %s", line) } return false } fallthrough default: c.Sayln("usage: [ [ ] ... ]") c.Sayln(" available commands:") c.Sayln(" quit close connection (or use Ctrl-D)") c.Sayln(" help [ ] print this, or help for specific command") c.Sayln(" state show state of switch and all servers") c.Sayln(" listen add listener for messages of type ") c.Sayln(" server switch to server ") c.Sayln(" switch [ [ ] ... ] send command to switch") } return false } func telnetQuit(c *telgo.Client, args []string) bool { return true } func (telnet *TelnetInterface) Run() { rhdl.Printf("Telnet: handler running...") if err := telnet.server.Run(); err != nil { rhl.Printf("Telnet: server returned: %s", err) } } func TelnetInit(conf *Config, ctrl *SwitchControl) (telnet *TelnetInterface, err error) { telnet = &TelnetInterface{} cmdlist := make(telgo.CmdList) cmdlist["state"] = func(c *telgo.Client, args []string) bool { return telnetCmdState(c, args, ctrl) } cmdlist["listen"] = func(c *telgo.Client, args []string) bool { return telnetCmdListen(c, args, ctrl) } cmdlist["server"] = func(c *telgo.Client, args []string) bool { return telnetCmdServer(c, args, ctrl) } cmdlist["switch"] = func(c *telgo.Client, args []string) bool { return telnetCmdSwitch(c, args, ctrl) } cmdlist["help"] = telnetHelp cmdlist["quit"] = telnetQuit telnet.server, err = telgo.NewServer(conf.Clients.Telnet.Address, "rhctl> ", cmdlist, nil) return }