<commit_before>package client

import (
	"bytes"
	"encoding/json"
	"io"
	"mime/multipart"
	"time"

	"github.com/demisto/server/domain"
)

// Target ...
type Target struct {
	Value string `json:"value"`
	Type  string `json:"type"`
}

// Attachment ...
type Attachment struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Path        string `json:"path"`
	Description string `json:"description"`
}

// Incident details.
// An incident can be opened by us algorithmically or arrive from an external source like SIEM.
// If you add fields, make sure to add them to the mapping as well
type Incident struct {
	// ID of the incident
	ID string `json:"id"`
	// Version ...
	Version int64 `json:"version"`
	// Modified timestamp
	Modified time.Time `json:"modified"`
	// Type of the incident
	Type string `json:"type"`
	// Name of the incident
	Name string `json:"name"`
	// Status ...
	Status int `json:"status"`
	// Reason for the resolve
	Reason string `json:"reason"`
	// When was this created
	Created time.Time `json:"created"`
	// When this incident has really occurred
	Occurred time.Time `json:"occurred"`
	// When was this closed
	Closed time.Time `json:"closed"`
	// The severity of the incident
	Level int `json:"severity"`
	// Investigation that was opened as a result of the incoming event
	Investigation string `json:"investigationId"`
	// The targets involved
	Targets []Target `json:"targets"`
	// Attachments
	Attachments []Attachment `json:"attachment"`
	// The details of the incident - reason, etc.
	Details string `json:"details"`
	//Duration incident was open
	OpenDuration int64 `json:"openDuration"`
	//The user ID that closed this investigation
	ClosingUserID string `json:"closingUserId"`
	// The user that activated this investigation
	ActivatingUserID string `json:"activatingingUserId,omitempty"`
	//The user who owns this incident
	OwnerID string `json:"owner"`
	// When was this activated
	Activated time.Time `json:"activated,omitempty"`
	// The reason for archiving the incident
	ArchiveReason string `json:"archiveReason"`
	// The associated playbook for this incident
	PlaybookID string `json:"playbookId"`
	// When was this activated
	DueDate time.Time `json:"dueDate,omitempty"`
}

type idVersion struct {
	ID      string `json:"id"`
	Version int64  `json:"version"`
}

type updateSeverity struct {
	idVersion
	Severity domain.Severity `json:"severity"`
}

// CreateIncident in Demisto
func (c *Client) CreateIncident(inc *Incident) (*Incident, error) {
	data, err := json.Marshal(inc)
	if err != nil {
		return nil, err
	}
	res := &Incident{}
	err = c.req("POST", "incident", "", bytes.NewBuffer(data), res)
	return res, err
}

type investigation struct {
	idVersion
	Investigation *Investigation `json:"investigation"`
}

// IncidentAddAttachment adds an attachment to a given incident
func (c *Client) IncidentAddAttachment(inc *Incident, file io.Reader, name, comment string) (*Incident, error) {
	b := &bytes.Buffer{}
	writer := multipart.NewWriter(b)
	part, err := writer.CreateFormFile("file", name)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(part, file)
	if err != nil {
		return nil, err
	}
	if comment != "" {
		part, err := writer.CreateFormField("comment")
		if err != nil {
			return nil, err
		}
		_, err = part.Write([]byte(comment))
		if err != nil {
			return nil, err
		}
	}
	writer.Close()
	res := &Incident{}
	err = c.req("POST", "incident/upload/"+inc.ID, writer.FormDataContentType(), b, res)
	return res, err
}

// Investigate a given incident, returns ID and version of invetigation created.
func (c *Client) Investigate(incidentID string, incidentVersion int64) (*Investigation, error) {
	data, err := json.Marshal(&idVersion{ID: incidentID, Version: incidentVersion})
	if err != nil {
		return nil, err
	}
	res := &investigation{}
	err = c.req("POST", "incident/investigate", "", bytes.NewBuffer(data), res)
	return res.Investigation, err
}
<commit_msg>removed wrong domain reference<commit_after>package client

import (
	"bytes"
	"encoding/json"
	"io"
	"mime/multipart"
	"time"
)

// Target ...
type Target struct {
	Value string `json:"value"`
	Type  string `json:"type"`
}

// Attachment ...
type Attachment struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Path        string `json:"path"`
	Description string `json:"description"`
}

// Incident details.
// An incident can be opened by us algorithmically or arrive from an external source like SIEM.
// If you add fields, make sure to add them to the mapping as well
type Incident struct {
	// ID of the incident
	ID string `json:"id"`
	// Version ...
	Version int64 `json:"version"`
	// Modified timestamp
	Modified time.Time `json:"modified"`
	// Type of the incident
	Type string `json:"type"`
	// Name of the incident
	Name string `json:"name"`
	// Status ...
	Status int `json:"status"`
	// Reason for the resolve
	Reason string `json:"reason"`
	// When was this created
	Created time.Time `json:"created"`
	// When this incident has really occurred
	Occurred time.Time `json:"occurred"`
	// When was this closed
	Closed time.Time `json:"closed"`
	// The severity of the incident
	Level int `json:"severity"`
	// Investigation that was opened as a result of the incoming event
	Investigation string `json:"investigationId"`
	// The targets involved
	Targets []Target `json:"targets"`
	// Attachments
	Attachments []Attachment `json:"attachment"`
	// The details of the incident - reason, etc.
	Details string `json:"details"`
	//Duration incident was open
	OpenDuration int64 `json:"openDuration"`
	//The user ID that closed this investigation
	ClosingUserID string `json:"closingUserId"`
	// The user that activated this investigation
	ActivatingUserID string `json:"activatingingUserId,omitempty"`
	//The user who owns this incident
	OwnerID string `json:"owner"`
	// When was this activated
	Activated time.Time `json:"activated,omitempty"`
	// The reason for archiving the incident
	ArchiveReason string `json:"archiveReason"`
	// The associated playbook for this incident
	PlaybookID string `json:"playbookId"`
	// When was this activated
	DueDate time.Time `json:"dueDate,omitempty"`
}

type idVersion struct {
	ID      string `json:"id"`
	Version int64  `json:"version"`
}

type updateSeverity struct {
	idVersion
	Severity int `json:"severity"`
}

// CreateIncident in Demisto
func (c *Client) CreateIncident(inc *Incident) (*Incident, error) {
	data, err := json.Marshal(inc)
	if err != nil {
		return nil, err
	}
	res := &Incident{}
	err = c.req("POST", "incident", "", bytes.NewBuffer(data), res)
	return res, err
}

type investigation struct {
	idVersion
	Investigation *Investigation `json:"investigation"`
}

// IncidentAddAttachment adds an attachment to a given incident
func (c *Client) IncidentAddAttachment(inc *Incident, file io.Reader, name, comment string) (*Incident, error) {
	b := &bytes.Buffer{}
	writer := multipart.NewWriter(b)
	part, err := writer.CreateFormFile("file", name)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(part, file)
	if err != nil {
		return nil, err
	}
	if comment != "" {
		part, err := writer.CreateFormField("comment")
		if err != nil {
			return nil, err
		}
		_, err = part.Write([]byte(comment))
		if err != nil {
			return nil, err
		}
	}
	writer.Close()
	res := &Incident{}
	err = c.req("POST", "incident/upload/"+inc.ID, writer.FormDataContentType(), b, res)
	return res, err
}

// Investigate a given incident, returns ID and version of invetigation created.
func (c *Client) Investigate(incidentID string, incidentVersion int64) (*Investigation, error) {
	data, err := json.Marshal(&idVersion{ID: incidentID, Version: incidentVersion})
	if err != nil {
		return nil, err
	}
	res := &investigation{}
	err = c.req("POST", "incident/investigate", "", bytes.NewBuffer(data), res)
	return res.Investigation, err
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"time"

	"mig.ninja/mig"
	"mig.ninja/mig/client"
	"mig.ninja/mig/modules"
)

func usage() {
	fmt.Printf(`%s - Mozilla InvestiGator command line client
usage: %s <module> <global options> <module parameters>

--- Global options ---

-c <path>	path to an alternative config file. If not set, use ~/.migrc
-e <duration>	time after which the action expires. 60 seconds by default.
		example: -e 300s (5 minutes)
-i <file>	load and run action from a file. supersedes other action flags.
-p <bool>       display action json that would be used and exit
-show <mode>	type of results to show. if not set, default is 'found'.
		* found: 	only print positive results
		* notfound: 	only print negative results
		* all: 		print all results
-render <mode>	defines how results should be rendered:
		* text (default):	results are printed to the console
		* map:			results are geolocated and a google map is generated
-t <target>	target to launch the action on. The default targets all online agents.
		(idle and offline agents are ignored).
		examples:
		* linux agents:          -t "queueloc LIKE 'linux.%%'"
		* agents named *mysql*:  -t "name like '%%mysql%%'"
		* proxied linux agents:  -t "queueloc LIKE 'linux.%%' AND environment->>'isproxied' = 'true'"
		* agents operated by IT: -t "tags#>>'{operator}'='IT'"
		* run on local system:	 -t local
-target-found <action ID> targets agents that have found results in a previous action. ex: -target-found 123456
-target-foundnothing <action ID> targets agents that did not find results in a previous action.
		Both -target-found and -target-foundnothing cannot be used simultaneously.
-v		verbose output, includes debug information and raw queries
-V		print version
-z <bool>       compress action before sending it to agents

Progress information is sent to stderr, silence it with "2>/dev/null".
Results are sent to stdout, redirect them with "1>/path/to/file".

--- Modules documentation ---
Each module provides its own set of parameters. Module parameters must be set *after*
global options. Help is available by calling "<module> help". Available modules are:
`, os.Args[0], os.Args[0])
	for module, _ := range modules.Available {
		fmt.Printf("* %s\n", module)
	}
	fmt.Printf("To access a module documentation, use: %s <module> help\n", os.Args[0])
	os.Exit(1)
}

func continueOnFlagError() {
	return
}

func main() {
	var (
		conf                                    client.Configuration
		cli                                     client.Client
		err                                     error
		op                                      mig.Operation
		a                                       mig.Action
		migrc, show, render, target, expiration string
		afile, targetfound, targetnotfound      string
		printAndExit                            bool
		verbose, showversion                    bool
		compressAction                          bool
		modargs                                 []string
		run                                     interface{}
	)
	defer func() {
		if e := recover(); e != nil {
			fmt.Fprintf(os.Stderr, "%v\n", e)
		}
	}()
	homedir := client.FindHomedir()
	fs := flag.NewFlagSet("mig flag", flag.ContinueOnError)
	fs.Usage = continueOnFlagError
	fs.BoolVar(&printAndExit, "p", false, "display action json that would be used and exit")
	fs.StringVar(&migrc, "c", homedir+"/.migrc", "alternative configuration file")
	fs.StringVar(&show, "show", "found", "type of results to show")
	fs.StringVar(&render, "render", "text", "results rendering mode")
	fs.StringVar(&target, "t", fmt.Sprintf("status='%s' AND mode='daemon'", mig.AgtStatusOnline), "action target")
	fs.StringVar(&targetfound, "target-found", "", "targets agents that have found results in a previous action.")
	fs.StringVar(&targetnotfound, "target-foundnothing", "", "targets agents that haven't found results in a previous action.")
	fs.StringVar(&expiration, "e", "300s", "expiration")
	fs.StringVar(&afile, "i", "/path/to/file", "Load action from file")
	fs.BoolVar(&verbose, "v", false, "Enable verbose output")
	fs.BoolVar(&showversion, "V", false, "Show version")
	fs.BoolVar(&compressAction, "z", false, "Request compression of action parameters")

	// if first argument is missing, or is help, print help
	// otherwise, pass the remainder of the arguments to the module for parsing
	// this client is agnostic to module parameters
	if len(os.Args) < 2 || os.Args[1] == "help" || os.Args[1] == "-h" || os.Args[1] == "--help" {
		usage()
	}

	if showversion || (len(os.Args) > 1 && (os.Args[1] == "-V" || os.Args[1] == "version")) {
		fmt.Println(mig.Version)
		os.Exit(0)
	}

	// when reading the action from a file, go directly to launch
	if os.Args[1] == "-i" {
		err = fs.Parse(os.Args[1:])
		if err != nil {
			panic(err)
		}
		if afile == "/path/to/file" {
			panic("-i flag must take an action file path as argument")
		}
		a, err = mig.ActionFromFile(afile)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "[info] launching action from file, all flags are ignored\n")
		if printAndExit {
			actionstr, err := a.IndentedString()
			if err != nil {
				panic(err)
			}
			fmt.Fprintf(os.Stdout, "%v\n", actionstr)
			os.Exit(0)
		}
		goto readytolaunch
	}

	// arguments parsing works as follow:
	// * os.Args[1] must contain the name of the module to launch. we first verify
	//   that a module exist for this name and then continue parsing
	// * os.Args[2:] contains both global options and module parameters. We parse the
	//   whole []string to extract global options, and module parameters will be left
	//   unparsed in fs.Args()
	// * fs.Args() with the module parameters is passed as a string to the module parser
	//   which will return a module operation to store in the action
	op.Module = os.Args[1]
	if _, ok := modules.Available[op.Module]; !ok {
		panic("Unknown module " + op.Module)
	}

	// -- Ugly hack Warning --
	// Parse() will fail on the first flag that is not defined, but in our case module flags
	// are defined in the module packages and not in this program. Therefore, the flag parse error
	// is expected. Unfortunately, Parse() writes directly to stderr and displays the error to
	// the user, which confuses them. The right fix would be to prevent Parse() from writing to
	// stderr, since that's really the job of the calling program, but in the meantime we work around
	// it by redirecting stderr to null before calling Parse(), and put it back to normal afterward.
	// for ref, issue is at https://github.com/golang/go/blob/master/src/flag/flag.go#L793
	fs.SetOutput(os.NewFile(uintptr(87592), os.DevNull))
	err = fs.Parse(os.Args[2:])
	fs.SetOutput(nil)
	if err != nil {
		// ignore the flag not defined error, which is expected because
		// module parameters are defined in modules and not in main
		if len(err.Error()) > 30 && err.Error()[0:29] == "flag provided but not defined" {
			// requeue the parameter that failed
			modargs = append(modargs, err.Error()[31:])
		} else {
			// if it's another error, panic
			panic(err)
		}
	}
	for _, arg := range fs.Args() {
		modargs = append(modargs, arg)
	}
	run = modules.Available[op.Module].NewRun()
	if _, ok := run.(modules.HasParamsParser); !ok {
		fmt.Fprintf(os.Stderr, "[error] module '%s' does not support command line invocation\n", op.Module)
		os.Exit(2)
	}
	op.Parameters, err = run.(modules.HasParamsParser).ParamsParser(modargs)
	if err != nil || op.Parameters == nil {
		panic(err)
	}
	// If compression has been enabled, flag it in the operation.
	if compressAction {
		op.WantCompressed = true
	}
	// If running against the local target, don't post the action to the MIG API
	// but run it locally instead.
	if target == "local" {
		msg, err := modules.MakeMessage(modules.MsgClassParameters, op.Parameters, false)
		if err != nil {
			panic(err)
		}
		out := run.(modules.Runner).Run(bytes.NewBuffer(msg))
		if len(out) == 0 {
			panic("got empty results, run failed")
		}
		if _, ok := run.(modules.HasResultsPrinter); ok {
			var modres modules.Result
			err := json.Unmarshal([]byte(out), &modres)
			if err != nil {
				panic(err)
			}
			outRes, err := run.(modules.HasResultsPrinter).PrintResults(modres, true)
			if err != nil {
				panic(err)
			}
			for _, resLine := range outRes {
				fmt.Println(resLine)
			}
		} else {
			out = fmt.Sprintf("%s\n", out)
		}
		os.Exit(0)
	}

	a.Operations = append(a.Operations, op)

	for _, arg := range os.Args[1:] {
		a.Name += arg + " "
	}

	if targetfound != "" && targetnotfound != "" {
		panic("Both -target-found and -target-foundnothing cannot be used simultaneously")
	}
	if targetfound != "" {
		targetQuery := fmt.Sprintf(`id IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'true')`, targetfound)
		target = targetQuery + " AND " + target
	}
	if targetnotfound != "" {
		targetQuery := fmt.Sprintf(`id NOT IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'true')`, targetnotfound)
		target = targetQuery + " AND " + target
	}
	a.Target = target

	if printAndExit {
		actionstr, err := a.IndentedString()
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stdout, "%v\n", actionstr)
		os.Exit(0)
	}

readytolaunch:
	// instanciate an API client
	conf, err = client.ReadConfiguration(migrc)
	if err != nil {
		panic(err)
	}
	cli, err = client.NewClient(conf, "cmd-"+mig.Version)
	if err != nil {
		panic(err)
	}

	if verbose {
		cli.EnableDebug()
	}

	// set the validity 60 second in the past to deal with clock skew
	a.ValidFrom = time.Now().Add(-60 * time.Second).UTC()
	period, err := time.ParseDuration(expiration)
	if err != nil {
		panic(err)
	}
	a.ExpireAfter = a.ValidFrom.Add(period)
	// add extra 60 seconds taken for clock skew
	a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC()

	a, err = cli.CompressAction(a)
	if err != nil {
		panic(err)
	}
	asig, err := cli.SignAction(a)
	if err != nil {
		panic(err)
	}
	a = asig

	// evaluate target before launch, give a change to cancel before going out to agents
	agents, err := cli.EvaluateAgentTarget(a.Target)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33m%d agents will be targeted. ctrl+c to cancel. launching in \x1b[0m", len(agents))
	for i := 5; i > 0; i-- {
		time.Sleep(1 * time.Second)
		fmt.Fprintf(os.Stderr, "\x1b[33m%d\x1b[0m ", i)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33mGO\n\x1b[0m")

	// launch and follow
	a, err = cli.PostAction(a)
	if err != nil {
		panic(err)
	}
	c := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		err = cli.FollowAction(a, len(agents))
		if err != nil {
			panic(err)
		}
		done <- true
	}()
	select {
	case <-c:
		fmt.Fprintf(os.Stderr, "stop following action. agents may still be running. printing available results:\n")
		goto printresults
	case <-done:
		goto printresults
	}
printresults:
	err = cli.PrintActionResults(a, show, render)
	if err != nil {
		panic(err)
	}
}
<commit_msg>[minor/bug] fix issues with #191 in mig client<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"time"

	"mig.ninja/mig"
	"mig.ninja/mig/client"
	"mig.ninja/mig/modules"
)

func usage() {
	fmt.Printf(`%s - Mozilla InvestiGator command line client
usage: %s <module> <global options> <module parameters>

--- Global options ---

-c <path>	path to an alternative confiig file. If not set, use ~/.migrc

-e <duration>	time after which the action expires. 60 seconds by default.
		example: -e 300s (5 minutes)

-i <file>	load and run action from a file. supersedes other action flags.

-p <bool>       display action json that would be used and exit

-show <mode>	type of results to show. if not set, default is 'found'.
		* found: 	only print positive results
		* notfound: 	only print negative results
		* all: 		print all results

-render <mode>	defines how results should be rendered:
		* text (default):	results are printed to the console
		* map:			results are geolocated and a google map is generated

-t <target>	target to launch the action on. The default targets all online agents.
		(idle and offline agents are ignored).
		examples:
		* linux agents:          -t "queueloc LIKE 'linux.%%'"
		* agents named *mysql*:  -t "name like '%%mysql%%'"
		* proxied linux agents:  -t "queueloc LIKE 'linux.%%' AND environment->>'isproxied' = 'true'"
		* agents operated by IT: -t "tags#>>'{operator}'='IT'"
		* run on local system:	 -t local

-targetfound <action ID>
-targetnotfound <action ID>
		targets agents that have eiher found or not found results in a previous action.
		example: -target-found 123456

-v		verbose output, includes debug information and raw queries

-V		print version

-z <bool>       compress action before sending it to agents

Progress information is sent to stderr, silence it with "2>/dev/null".
Results are sent to stdout, redirect them with "1>/path/to/file".

--- Modules documentation ---
Each module provides its own set of parameters. Module parameters must be set *after*
global options. Help is available by calling "<module> help". Available modules are:
`, os.Args[0], os.Args[0])
	for module, _ := range modules.Available {
		fmt.Printf("* %s\n", module)
	}
	fmt.Printf("To access a module documentation, use: %s <module> help\n", os.Args[0])
	os.Exit(1)
}

func continueOnFlagError() {
	return
}

func main() {
	var (
		conf                                    client.Configuration
		cli                                     client.Client
		err                                     error
		op                                      mig.Operation
		a                                       mig.Action
		migrc, show, render, target, expiration string
		afile, targetfound, targetnotfound      string
		printAndExit                            bool
		verbose, showversion                    bool
		compressAction                          bool
		modargs                                 []string
		run                                     interface{}
	)
	defer func() {
		if e := recover(); e != nil {
			fmt.Fprintf(os.Stderr, "%v\n", e)
		}
	}()
	homedir := client.FindHomedir()
	fs := flag.NewFlagSet("mig flag", flag.ContinueOnError)
	fs.Usage = continueOnFlagError
	fs.BoolVar(&printAndExit, "p", false, "display action json that would be used and exit")
	fs.StringVar(&migrc, "c", homedir+"/.migrc", "alternative configuration file")
	fs.StringVar(&show, "show", "found", "type of results to show")
	fs.StringVar(&render, "render", "text", "results rendering mode")
	fs.StringVar(&target, "t", fmt.Sprintf("status='%s' AND mode='daemon'", mig.AgtStatusOnline), "action target")
	fs.StringVar(&targetfound, "target-found", "", "targets agents that have found results in a previous action.")
	fs.StringVar(&targetnotfound, "target-notfound", "", "targets agents that haven't found results in a previous action.")
	fs.StringVar(&expiration, "e", "300s", "expiration")
	fs.StringVar(&afile, "i", "/path/to/file", "Load action from file")
	fs.BoolVar(&verbose, "v", false, "Enable verbose output")
	fs.BoolVar(&showversion, "V", false, "Show version")
	fs.BoolVar(&compressAction, "z", false, "Request compression of action parameters")

	// if first argument is missing, or is help, print help
	// otherwise, pass the remainder of the arguments to the module for parsing
	// this client is agnostic to module parameters
	if len(os.Args) < 2 || os.Args[1] == "help" || os.Args[1] == "-h" || os.Args[1] == "--help" {
		usage()
	}

	if showversion || (len(os.Args) > 1 && (os.Args[1] == "-V" || os.Args[1] == "version")) {
		fmt.Println(mig.Version)
		os.Exit(0)
	}

	// when reading the action from a file, go directly to launch
	if os.Args[1] == "-i" {
		err = fs.Parse(os.Args[1:])
		if err != nil {
			panic(err)
		}
		if afile == "/path/to/file" {
			panic("-i flag must take an action file path as argument")
		}
		a, err = mig.ActionFromFile(afile)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "[info] launching action from file, all flags are ignored\n")
		if printAndExit {
			actionstr, err := a.IndentedString()
			if err != nil {
				panic(err)
			}
			fmt.Fprintf(os.Stdout, "%v\n", actionstr)
			os.Exit(0)
		}
		goto readytolaunch
	}

	// arguments parsing works as follow:
	// * os.Args[1] must contain the name of the module to launch. we first verify
	//   that a module exist for this name and then continue parsing
	// * os.Args[2:] contains both global options and module parameters. We parse the
	//   whole []string to extract global options, and module parameters will be left
	//   unparsed in fs.Args()
	// * fs.Args() with the module parameters is passed as a string to the module parser
	//   which will return a module operation to store in the action
	op.Module = os.Args[1]
	if _, ok := modules.Available[op.Module]; !ok {
		panic("Unknown module " + op.Module)
	}

	// -- Ugly hack Warning --
	// Parse() will fail on the first flag that is not defined, but in our case module flags
	// are defined in the module packages and not in this program. Therefore, the flag parse error
	// is expected. Unfortunately, Parse() writes directly to stderr and displays the error to
	// the user, which confuses them. The right fix would be to prevent Parse() from writing to
	// stderr, since that's really the job of the calling program, but in the meantime we work around
	// it by redirecting stderr to null before calling Parse(), and put it back to normal afterward.
	// for ref, issue is at https://github.com/golang/go/blob/master/src/flag/flag.go#L793
	fs.SetOutput(os.NewFile(uintptr(87592), os.DevNull))
	err = fs.Parse(os.Args[2:])
	fs.SetOutput(nil)
	if err != nil {
		// ignore the flag not defined error, which is expected because
		// module parameters are defined in modules and not in main
		if len(err.Error()) > 30 && err.Error()[0:29] == "flag provided but not defined" {
			// requeue the parameter that failed
			modargs = append(modargs, err.Error()[31:])
		} else {
			// if it's another error, panic
			panic(err)
		}
	}
	for _, arg := range fs.Args() {
		modargs = append(modargs, arg)
	}
	run = modules.Available[op.Module].NewRun()
	if _, ok := run.(modules.HasParamsParser); !ok {
		fmt.Fprintf(os.Stderr, "[error] module '%s' does not support command line invocation\n", op.Module)
		os.Exit(2)
	}
	op.Parameters, err = run.(modules.HasParamsParser).ParamsParser(modargs)
	if err != nil || op.Parameters == nil {
		panic(err)
	}
	// If compression has been enabled, flag it in the operation.
	if compressAction {
		op.WantCompressed = true
	}
	// If running against the local target, don't post the action to the MIG API
	// but run it locally instead.
	if target == "local" {
		msg, err := modules.MakeMessage(modules.MsgClassParameters, op.Parameters, false)
		if err != nil {
			panic(err)
		}
		out := run.(modules.Runner).Run(bytes.NewBuffer(msg))
		if len(out) == 0 {
			panic("got empty results, run failed")
		}
		if _, ok := run.(modules.HasResultsPrinter); ok {
			var modres modules.Result
			err := json.Unmarshal([]byte(out), &modres)
			if err != nil {
				panic(err)
			}
			outRes, err := run.(modules.HasResultsPrinter).PrintResults(modres, true)
			if err != nil {
				panic(err)
			}
			for _, resLine := range outRes {
				fmt.Println(resLine)
			}
		} else {
			out = fmt.Sprintf("%s\n", out)
		}
		os.Exit(0)
	}

	a.Operations = append(a.Operations, op)

	for _, arg := range os.Args[1:] {
		a.Name += arg + " "
	}

	if targetfound != "" && targetnotfound != "" {
		panic("Both -target-found and -target-foundnothing cannot be used simultaneously")
	}
	if targetfound != "" {
		targetQuery := fmt.Sprintf(`id IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'true')`, targetfound)
		target = targetQuery + " AND " + target
	}
	if targetnotfound != "" {
		targetQuery := fmt.Sprintf(`id NOT IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'false')`, targetnotfound)
		target = targetQuery + " AND " + target
	}
	a.Target = target

	if printAndExit {
		actionstr, err := a.IndentedString()
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stdout, "%v\n", actionstr)
		os.Exit(0)
	}

readytolaunch:
	// instanciate an API client
	conf, err = client.ReadConfiguration(migrc)
	if err != nil {
		panic(err)
	}
	cli, err = client.NewClient(conf, "cmd-"+mig.Version)
	if err != nil {
		panic(err)
	}

	if verbose {
		cli.EnableDebug()
	}

	// set the validity 60 second in the past to deal with clock skew
	a.ValidFrom = time.Now().Add(-60 * time.Second).UTC()
	period, err := time.ParseDuration(expiration)
	if err != nil {
		panic(err)
	}
	a.ExpireAfter = a.ValidFrom.Add(period)
	// add extra 60 seconds taken for clock skew
	a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC()

	a, err = cli.CompressAction(a)
	if err != nil {
		panic(err)
	}
	asig, err := cli.SignAction(a)
	if err != nil {
		panic(err)
	}
	a = asig

	// evaluate target before launch, give a change to cancel before going out to agents
	agents, err := cli.EvaluateAgentTarget(a.Target)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33m%d agents will be targeted. ctrl+c to cancel. launching in \x1b[0m", len(agents))
	for i := 5; i > 0; i-- {
		time.Sleep(1 * time.Second)
		fmt.Fprintf(os.Stderr, "\x1b[33m%d\x1b[0m ", i)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33mGO\n\x1b[0m")

	// launch and follow
	a, err = cli.PostAction(a)
	if err != nil {
		panic(err)
	}
	c := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		err = cli.FollowAction(a, len(agents))
		if err != nil {
			panic(err)
		}
		done <- true
	}()
	select {
	case <-c:
		fmt.Fprintf(os.Stderr, "stop following action. agents may still be running. printing available results:\n")
		goto printresults
	case <-done:
		goto printresults
	}
printresults:
	err = cli.PrintActionResults(a, show, render)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// rabbitmq provides a concrete client implementation using
// rabbitmq / amqp as a message bus

package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	uuid "github.com/nu7hatch/gouuid"
	"github.com/streadway/amqp"

	"github.com/b2aio/typhon/rabbit"
)

type RabbitClient struct {
	inflight   *inflightRegistry
	replyTo    string
	connection *rabbit.RabbitConnection
}

var NewRabbitClient = func() Client {
	uuidQueue, err := uuid.NewV4()
	if err != nil {
		log.Criticalf("[Client] Failed to create UUID for reply queue")
		os.Exit(1)
	}
	return &RabbitClient{
		inflight:   newInflightRegistry(),
		connection: rabbit.NewRabbitConnection(),
		replyTo:    fmt.Sprintf("replyTo-%s", uuidQueue.String()),
	}
}

func (c *RabbitClient) Init() {
	select {
	case <-c.connection.Init():
		log.Info("[Client] Connected to RabbitMQ")
	case <-time.After(10 * time.Second):
		log.Critical("[Client] Failed to connect to RabbitMQ")
		os.Exit(1)
	}
	c.initConsume()
}

func (c *RabbitClient) initConsume() {
	err := c.connection.Channel.DeclareReplyQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to declare reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	deliveries, err := c.connection.Channel.ConsumeQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to consume from reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	go func() {
		log.Infof("[Client] Listening for deliveries on %s", c.replyTo)
		for delivery := range deliveries {
			go c.handleDelivery(delivery)
		}
	}()
}

func (c *RabbitClient) handleDelivery(delivery amqp.Delivery) {
	channel := c.inflight.pop(delivery.CorrelationId)
	if channel == nil {
		log.Errorf("[Client] CorrelationID '%s' does not exist in inflight registry", delivery.CorrelationId)
		return
	}
	select {
	case channel <- delivery:
	default:
		log.Errorf("[Client] Error in delivery for correlation %s", delivery.CorrelationId)
	}
}

func (c *RabbitClient) Call(serviceName, endpoint string, req proto.Message, res proto.Message) error {

	routingKey := c.buildRoutingKey(serviceName, endpoint)

	correlation, err := uuid.NewV4()
	if err != nil {
		log.Error("[Client] Failed to create correlationId in client")
		return errors.New("client.call.uuid.error")
	}

	replyChannel := c.inflight.push(correlation.String())
	defer close(replyChannel)

	requestBody, err := proto.Marshal(req)
	if err != nil {
		log.Error("[Client] Failed to marshal request")
		return errors.New("client.call.marshal.error")
	}

	message := amqp.Publishing{
		CorrelationId: correlation.String(),
		Timestamp:     time.Now().UTC(),
		Body:          requestBody,
		ReplyTo:       c.replyTo,
	}

	err = c.connection.Publish(rabbit.Exchange, routingKey, message)
	if err != nil {
		log.Errorf("[Client] Failed to publish to %s", routingKey)
		return fmt.Errorf("client.call.publish.%s.error", routingKey)
	}

	select {
	case delivery := <-replyChannel:
		if err := proto.Unmarshal(delivery.Body, res); err != nil {
			return fmt.Errorf("client.unmarshal.%s-reply.error", routingKey)
		}
		return nil
	case <-time.After(defaultTimeout):
		log.Criticalf("[Client] Client timeout on delivery")
		return fmt.Errorf("client.call.timeout.%s.error", routingKey)
	}
}

func (c *RabbitClient) buildRoutingKey(serviceName, endpoint string) string {
	return fmt.Sprintf("%s.%s", serviceName, endpoint)
}
<commit_msg>Standardise errors on client.Call<commit_after>// rabbitmq provides a concrete client implementation using
// rabbitmq / amqp as a message bus

package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	uuid "github.com/nu7hatch/gouuid"
	"github.com/streadway/amqp"

	"github.com/b2aio/typhon/rabbit"
)

type RabbitClient struct {
	inflight   *inflightRegistry
	replyTo    string
	connection *rabbit.RabbitConnection
}

var NewRabbitClient = func() Client {
	uuidQueue, err := uuid.NewV4()
	if err != nil {
		log.Criticalf("[Client] Failed to create UUID for reply queue")
		os.Exit(1)
	}
	return &RabbitClient{
		inflight:   newInflightRegistry(),
		connection: rabbit.NewRabbitConnection(),
		replyTo:    fmt.Sprintf("replyTo-%s", uuidQueue.String()),
	}
}

func (c *RabbitClient) Init() {
	select {
	case <-c.connection.Init():
		log.Info("[Client] Connected to RabbitMQ")
	case <-time.After(10 * time.Second):
		log.Critical("[Client] Failed to connect to RabbitMQ")
		os.Exit(1)
	}
	c.initConsume()
}

func (c *RabbitClient) initConsume() {
	err := c.connection.Channel.DeclareReplyQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to declare reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	deliveries, err := c.connection.Channel.ConsumeQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to consume from reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	go func() {
		log.Infof("[Client] Listening for deliveries on %s", c.replyTo)
		for delivery := range deliveries {
			go c.handleDelivery(delivery)
		}
	}()
}

func (c *RabbitClient) handleDelivery(delivery amqp.Delivery) {
	channel := c.inflight.pop(delivery.CorrelationId)
	if channel == nil {
		log.Errorf("[Client] CorrelationID '%s' does not exist in inflight registry", delivery.CorrelationId)
		return
	}
	select {
	case channel <- delivery:
	default:
		log.Errorf("[Client] Error in delivery for correlation %s", delivery.CorrelationId)
	}
}

func (c *RabbitClient) Call(serviceName, endpoint string, req proto.Message, res proto.Message) error {

	routingKey := c.buildRoutingKey(serviceName, endpoint)

	correlation, err := uuid.NewV4()
	if err != nil {
		log.Error("[Client] Failed to create correlationId in client")
		return errors.New("client.call.uuid.error")
	}

	replyChannel := c.inflight.push(correlation.String())
	defer close(replyChannel)

	requestBody, err := proto.Marshal(req)
	if err != nil {
		log.Error("[Client] Failed to marshal request")
		return errors.New("client.call.marshal.error")
	}

	message := amqp.Publishing{
		CorrelationId: correlation.String(),
		Timestamp:     time.Now().UTC(),
		Body:          requestBody,
		ReplyTo:       c.replyTo,
	}

	err = c.connection.Publish(rabbit.Exchange, routingKey, message)
	if err != nil {
		log.Errorf("[Client] Failed to publish to %s", routingKey)
		return errors.New("client.call.publish.error")
	}

	select {
	case delivery := <-replyChannel:
		if err := proto.Unmarshal(delivery.Body, res); err != nil {
			return errors.New("client.unmarshal.badreply.error")
		}
		return nil
	case <-time.After(defaultTimeout):
		log.Criticalf("[Client] Client timeout on delivery")
		return errors.New("client.call.timeout.error")
	}
}

func (c *RabbitClient) buildRoutingKey(serviceName, endpoint string) string {
	return fmt.Sprintf("%s.%s", serviceName, endpoint)
}
<|endoftext|>


<commit_before>// rabbitmq provides a concrete client implementation using
// rabbitmq / amqp as a message bus

package client

import (
	"fmt"
	"os"
	"sync"
	"time"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	uuid "github.com/nu7hatch/gouuid"
	"github.com/streadway/amqp"
	"golang.org/x/net/context"

	"github.com/b2aio/typhon/errors"
	"github.com/b2aio/typhon/rabbit"
)

type RabbitClient struct {
	once       sync.Once
	inflight   *inflightRegistry
	replyTo    string
	connection *rabbit.RabbitConnection
}

var NewRabbitClient = func() Client {
	uuidQueue, err := uuid.NewV4()
	if err != nil {
		log.Criticalf("[Client] Failed to create UUID for reply queue")
		os.Exit(1)
	}
	return &RabbitClient{
		inflight:   newInflightRegistry(),
		connection: rabbit.NewRabbitConnection(),
		replyTo:    fmt.Sprintf("replyTo-%s", uuidQueue.String()),
	}
}

func (c *RabbitClient) Init() {
	select {
	case <-c.connection.Init():
		log.Info("[Client] Connected to RabbitMQ")
	case <-time.After(10 * time.Second):
		log.Critical("[Client] Failed to connect to RabbitMQ")
		os.Exit(1)
	}
	c.initConsume()
}

func (c *RabbitClient) initConsume() {
	err := c.connection.Channel.DeclareReplyQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to declare reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	deliveries, err := c.connection.Channel.ConsumeQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to consume from reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	go func() {
		log.Infof("[Client] Listening for deliveries on %s", c.replyTo)
		for delivery := range deliveries {
			go c.handleDelivery(delivery)
		}
	}()
}

func (c *RabbitClient) handleDelivery(delivery amqp.Delivery) {
	channel := c.inflight.pop(delivery.CorrelationId)
	if channel == nil {
		log.Errorf("[Client] CorrelationID '%s' does not exist in inflight registry", delivery.CorrelationId)
		return
	}
	select {
	case channel <- delivery:
	default:
		log.Errorf("[Client] Error in delivery for correlation %s", delivery.CorrelationId)
	}
}

func (c *RabbitClient) Call(ctx context.Context, serviceName, endpoint string, req proto.Message, res proto.Message) error {

	// Ensure we're initialised, but only do this once
	//
	// @todo we need a connection loop here where we check if we're connected,
	// and if not, block for a short period of time while attempting to reconnect
	c.once.Do(c.Init)

	routingKey := c.buildRoutingKey(serviceName, endpoint)

	correlation, err := uuid.NewV4()
	if err != nil {
		log.Errorf("[Client] Failed to create unique request id: %v", err)
		return errors.InternalService("request.uuid", err.Error())
	}

	replyChannel := c.inflight.push(correlation.String())
	defer close(replyChannel)

	requestBody, err := proto.Marshal(req)
	if err != nil {
		log.Errorf("[Client] Failed to marshal request: %v", err)
		return errors.BadRequest("request.marshal", err.Error())
	}

	message := amqp.Publishing{
		CorrelationId: correlation.String(),
		Timestamp:     time.Now().UTC(),
		Body:          requestBody,
		ReplyTo:       c.replyTo,
	}

	err = c.connection.Publish(rabbit.Exchange, routingKey, message)
	if err != nil {
		log.Errorf("[Client] Failed to publish to '%s': %v", routingKey, err)
		return errors.InternalService("request.publish", err.Error())
	}

	select {
	case delivery := <-replyChannel:
		if err := proto.Unmarshal(delivery.Body, res); err != nil {
			return errors.BadResponse("response.unmarshal", err.Error())
		}
		return nil
	case <-time.After(defaultTimeout):
		e := fmt.Errorf("Timeout caling %v")
		log.Warnf("[Client] %v", e)
		return errors.Timeout(fmt.Sprintf("%s.timeout", routingKey), e.Error())
	}
}

func (c *RabbitClient) buildRoutingKey(serviceName, endpoint string) string {
	return fmt.Sprintf("%s.%s", serviceName, endpoint)
}
<commit_msg>Marshal interservice errors and return in the client<commit_after>// rabbitmq provides a concrete client implementation using
// rabbitmq / amqp as a message bus

package client

import (
	"fmt"
	"os"
	"sync"
	"time"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	uuid "github.com/nu7hatch/gouuid"
	"github.com/streadway/amqp"
	"golang.org/x/net/context"

	"github.com/b2aio/typhon/errors"
	pe "github.com/b2aio/typhon/proto/error"
	"github.com/b2aio/typhon/rabbit"
)

type RabbitClient struct {
	once       sync.Once
	inflight   *inflightRegistry
	replyTo    string
	connection *rabbit.RabbitConnection
}

var NewRabbitClient = func() Client {
	uuidQueue, err := uuid.NewV4()
	if err != nil {
		log.Criticalf("[Client] Failed to create UUID for reply queue")
		os.Exit(1)
	}
	return &RabbitClient{
		inflight:   newInflightRegistry(),
		connection: rabbit.NewRabbitConnection(),
		replyTo:    fmt.Sprintf("replyTo-%s", uuidQueue.String()),
	}
}

func (c *RabbitClient) Init() {
	select {
	case <-c.connection.Init():
		log.Info("[Client] Connected to RabbitMQ")
	case <-time.After(10 * time.Second):
		log.Critical("[Client] Failed to connect to RabbitMQ")
		os.Exit(1)
	}
	c.initConsume()
}

func (c *RabbitClient) initConsume() {
	err := c.connection.Channel.DeclareReplyQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to declare reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	deliveries, err := c.connection.Channel.ConsumeQueue(c.replyTo)
	if err != nil {
		log.Critical("[Client] Failed to consume from reply queue")
		log.Critical(err.Error())
		os.Exit(1)
	}
	go func() {
		log.Infof("[Client] Listening for deliveries on %s", c.replyTo)
		for delivery := range deliveries {
			go c.handleDelivery(delivery)
		}
	}()
}

func (c *RabbitClient) handleDelivery(delivery amqp.Delivery) {
	channel := c.inflight.pop(delivery.CorrelationId)
	if channel == nil {
		log.Errorf("[Client] CorrelationID '%s' does not exist in inflight registry", delivery.CorrelationId)
		return
	}
	select {
	case channel <- delivery:
	default:
		log.Errorf("[Client] Error in delivery for correlation %s", delivery.CorrelationId)
	}
}

func (c *RabbitClient) Call(ctx context.Context, serviceName, endpoint string, req proto.Message, resp proto.Message) error {

	// Ensure we're initialised, but only do this once
	//
	// @todo we need a connection loop here where we check if we're connected,
	// and if not, block for a short period of time while attempting to reconnect
	c.once.Do(c.Init)

	routingKey := c.buildRoutingKey(serviceName, endpoint)

	correlation, err := uuid.NewV4()
	if err != nil {
		log.Errorf("[Client] Failed to create unique request id: %v", err)
		return errors.InternalService("request.uuid", err.Error())
	}

	replyChannel := c.inflight.push(correlation.String())
	defer close(replyChannel)

	requestBody, err := proto.Marshal(req)
	if err != nil {
		log.Errorf("[Client] Failed to marshal request: %v", err)
		return errors.BadRequest("request.marshal", err.Error())
	}

	message := amqp.Publishing{
		CorrelationId: correlation.String(),
		Timestamp:     time.Now().UTC(),
		Body:          requestBody,
		ReplyTo:       c.replyTo,
	}

	err = c.connection.Publish(rabbit.Exchange, routingKey, message)
	if err != nil {
		log.Errorf("[Client] Failed to publish to '%s': %v", routingKey, err)
		return errors.InternalService("request.publish", err.Error())
	}

	select {
	case delivery := <-replyChannel:
		return handleResponse(delivery, resp)
	case <-time.After(defaultTimeout):
		e := fmt.Errorf("Timeout caling %v")
		log.Warnf("[Client] %v", e)
		return errors.Timeout(fmt.Sprintf("%s.timeout", routingKey), e.Error())
	}
}

func (c *RabbitClient) buildRoutingKey(serviceName, endpoint string) string {
	return fmt.Sprintf("%s.%s", serviceName, endpoint)
}

// handleResponse returned from a service by marshaling into the response type,
// or converting an error from the remote service
func handleResponse(delivery amqp.Delivery, resp proto.Message) error {
	// deal with error responses, by converting back from wire format
	if deliveryIsError(delivery) {
		p := &pe.Error{}
		if err := proto.Unmarshal(delivery.Body, p); err != nil {
			return errors.BadResponse("response.unmarshal", err.Error())
		}

		return errors.Unmarshal(p)
	}

	// Otherwise try to marshal to the expected response type
	if err := proto.Unmarshal(delivery.Body, resp); err != nil {
		return errors.BadResponse("response.unmarshal", err.Error())
	}

	return nil
}

// deliveryIsError checks if the delivered response contains an error
func deliveryIsError(delivery amqp.Delivery) bool {
	encoding, ok := delivery.Headers["Content-Encoding"].(string)
	if !ok {
		// Can't type assert header to string, assume error
		log.Warnf("Service returned invalid Content-Encoding header %v", encoding)
		return true
	}

	if encoding == "" || encoding == "ERROR" {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/meteor/procmon"
	"github.com/meteor/procmon/ecu"
	"math"
	"strconv"
)

func main() {
	flag.Parse()

	if flag.NArg() < 1 {
		fmt.Println("Input process is missing")
	}

	process, err := strconv.ParseInt(flag.Arg(0), 10, 32)
	if err != nil {
		log.WithField("input", flag.Arg(0)).WithError(err).Fatal("Couldn't parse input process")
	}

	instance, err := ecu.Mine()
	if err != nil {
		log.WithError(err).Error("Couldn't find instance metadata")
	}

	output := make(chan procmon.Measure, 1)
	monitor, err := procmon.New(output, int(process))
	if err != nil {
		log.WithError(err).Fatal("Couldn't parse input process")
	}
	defer monitor.Stop()

outerloop:
	for {
		select {
		case point, ok := <-output:
			if !ok {
				log.Warn("Not ok, breaking")
				break outerloop
			}
			userSysTotal := point.UserTotal + point.SystemTotal
			var userPerc, sysPerc, userECU, sysECU float64
			if point.UserTotal == 0 {
				userPerc = 0.0
			} else {
				userPerc = 100.0 * float64(point.User) / float64(point.UserTotal)
			}
			if point.SystemTotal == 0 {
				sysPerc = 0.0
			} else {
				sysPerc = 100.0 * float64(point.System) / float64(point.SystemTotal)
			}
			if instance == nil {
				userECU = math.NaN()
				sysECU = math.NaN()
			} else {
				log.WithFields(log.Fields{
					"instance":     instance,
					"instance CPU": instance.ComputeUnitsx10,
					"userSysTotal": userSysTotal,
					"numerator":    float64(instance.ComputeUnitsx10) * float64(point.User),
					"denominator":  (float64(userSysTotal) * 10.0),
				}).Debug("computing")
				userECU = float64(instance.ComputeUnitsx10) * float64(point.User) / (float64(userSysTotal) * 10.0)
				sysECU = float64(instance.ComputeUnitsx10) * float64(point.System) / (float64(userSysTotal) * 10.0)
			}
			log.WithFields(log.Fields{
				"point":      point,
				"user":       userPerc,
				"system":     sysPerc,
				"userInECU":  userECU,
				"sysInECU":   sysECU,
				"memoryInKB": point.Memory,
			}).Info("Got point")
		}
	}
}
<commit_msg>Compute percentages more like top.<commit_after>package main

import (
	"flag"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/meteor/procmon"
	"github.com/meteor/procmon/ecu"
	"math"
	"strconv"
)

func main() {
	flag.Parse()

	if flag.NArg() < 1 {
		fmt.Println("Input process is missing")
	}

	process, err := strconv.ParseInt(flag.Arg(0), 10, 32)
	if err != nil {
		log.WithField("input", flag.Arg(0)).WithError(err).Fatal("Couldn't parse input process")
	}

	instance, err := ecu.Mine()
	if err != nil {
		log.WithError(err).Error("Couldn't find instance metadata")
	}

	output := make(chan procmon.Measure, 1)
	monitor, err := procmon.New(output, int(process))
	if err != nil {
		log.WithError(err).Fatal("Couldn't parse input process")
	}
	defer monitor.Stop()

outerloop:
	for {
		select {
		case point, ok := <-output:
			if !ok {
				log.Warn("Not ok, breaking")
				break outerloop
			}
			userSysTotal := point.UserTotal + point.SystemTotal
			var userPerc, sysPerc, userECU, sysECU float64
			if point.UserTotal == 0 {
				userPerc = 0.0
			} else {
				userPerc = 100.0 * float64(point.User) / float64(userSysTotal)
			}
			if point.SystemTotal == 0 {
				sysPerc = 0.0
			} else {
				sysPerc = 100.0 * float64(point.System) / float64(userSysTotal)
			}
			if instance == nil {
				userECU = math.NaN()
				sysECU = math.NaN()
			} else {
				log.WithFields(log.Fields{
					"instance":     instance,
					"instance CPU": instance.ComputeUnitsx10,
					"userSysTotal": userSysTotal,
					"numerator":    float64(instance.ComputeUnitsx10) * float64(point.User),
					"denominator":  (float64(userSysTotal) * 10.0),
				}).Debug("computing")
				userECU = float64(instance.ComputeUnitsx10) * float64(point.User) / (float64(userSysTotal) * 10.0)
				sysECU = float64(instance.ComputeUnitsx10) * float64(point.System) / (float64(userSysTotal) * 10.0)
			}
			log.WithFields(log.Fields{
				"user":       userPerc,
				"system":     sysPerc,
				"userInECU":  userECU,
				"sysInECU":   sysECU,
				"memoryInKB": point.Memory,
			}).Info("Got point")
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math/rand"
	"strings"
	"time"

	derpiSearch "github.com/PonyvilleFM/aura/cmd/aerial/derpi"
	"github.com/PonyvilleFM/aura/pvfm"
	"github.com/PonyvilleFM/aura/pvfm/pvl"
	pvfmschedule "github.com/PonyvilleFM/aura/pvfm/schedule"
	"github.com/PonyvilleFM/aura/pvfm/station"
	"github.com/bwmarrin/discordgo"
	"github.com/tebeka/strftime"
)

func init() {
	rand.Seed(time.Now().Unix())
}

// randomRange gives a random whole integer between the given integers [min, max)
func randomRange(min, max int) int {
	return rand.Intn(max-min) + min
}

func pesterLink(s *discordgo.Session, m *discordgo.MessageCreate) {
	if musicLinkRegex.Match([]byte(m.Content)) {
		i, err := pvfm.GetStats()
		if err != nil {
			log.Println(err)
			return
		}

		if i.IsDJLive() && m.ChannelID == youtubeSpamRoomID {
			s.ChannelMessageSend(m.ChannelID, "Please be mindful sharing links to music when a DJ is performing. Thanks!")
		}
	}
}

func np(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	i, err := pvfm.GetStats()
	if err != nil {
		log.Printf("Can't get info: %v, failing over to plan b", err)
		return doStationRequest(s, m, parv)
	}

	result := []string{}

	if i.Main.Nowplaying == "Fetching info..." {
		log.Println("Main information was bad, fetching from station directly...")

		err := doStationRequest(s, m, parv)
		if err != nil {
			return err
		}

		return nil
	} else {
		result = append(result, "📻 **Now Playing on PVFM**\n")

		result = append(result, fmt.Sprintf(
			"Main 🎵 %s\n",
			i.Main.Nowplaying,
		))
		result = append(result, fmt.Sprintf(
			"Chill 🎵 %s\n",
			i.Secondary.Nowplaying,
		))
		result = append(result, fmt.Sprintf(
			"Free! 🎵 %s",
			i.MusicOnly.Nowplaying,
		))
	}

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))
	return nil
}

func dj(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	cal, err := pvl.Get()
	if err != nil {
		return err
	}

	now := cal.Result[0]
	result := []string{}

	localTime := time.Now()
	thentime := time.Unix(now.StartTime, 0)
	if thentime.Unix() < localTime.Unix() {
		result = append(result, fmt.Sprintf("Currently live: %s\n", now.Title))
		now = cal.Result[1]
	}

	nowTime := time.Unix(now.StartTime, 0).UTC()
	zone, _ := nowTime.Zone()
	fmttime, _ := strftime.Format("%Y-%m-%d %H:%M:%S", nowTime)

	result = append(result, fmt.Sprintf("Next event: %s at %s \x02%s\x02",
		now.Title,
		fmttime,
		zone,
	))

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))
	return nil
}

func stats(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	i, err := pvfm.GetStats()
	if err != nil {
		log.Printf("Error getting the station info: %v, falling back to plan b", err)
		return doStatsFromStation(s, m, parv)
	}

	st, err := station.GetStats()
	if err != nil {
		return err
	}

	var l int
	var peak int

	for _, source := range st.Icestats.Source {
		l = l + source.Listeners
		peak = peak + source.ListenerPeak
	}

	result := []string{
		fmt.Sprintf(
			"Current listeners across all streams: %d with a maximum of %d!",
			i.Listeners.Listeners, peak,
		),
		fmt.Sprintf(
			"Detailed: Main: %d listeners, Two: %d listeners, Free: %d listeners",
			i.Main.Listeners, i.Secondary.Listeners, i.MusicOnly.Listeners,
		),
	}

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))

	return nil
}

func schedule(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	schEntries, err := pvfmschedule.Get()
	if err != nil {
		return err
	}

	// Create embed object
	outputEmbed := NewEmbed().
		SetTitle("Upcoming Shows").
		SetDescription("These are the upcoming shows and events airing soon on PVFM 1.")

	for _, entry := range schEntries {

		// Format countdown timer
		startTimeUnix := time.Unix(int64(entry.StartUnix), 0)
		nowWithoutNanoseconds := time.Unix(time.Now().Unix(), 0)
		dur := startTimeUnix.Sub(nowWithoutNanoseconds)

		// Show "Live Now!" if the timer is less than 0h0m0s
		if dur > 0 {
			outputEmbed.AddField(":musical_note:  "+entry.Host+" - "+entry.Name, entry.StartTime+"\n"+dur.String())
		} else {
			outputEmbed.AddField(":musical_note:  "+entry.Host+" - "+entry.Name, "Live now!")
		}
	}

	s.ChannelMessageSendEmbed(m.ChannelID, outputEmbed.MessageEmbed)
	return nil
}

func doStationRequest(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	stats, err := station.GetStats()
	if err != nil {
		return err
	}

	result := fmt.Sprintf(
		"Now playing: %s - %s on Ponyville FM!",
		stats.Icestats.Source[0].Title,
		stats.Icestats.Source[0].Artist,
	)

	s.ChannelMessageSend(m.ChannelID, result)
	return nil
}

func doStatsFromStation(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	st, err := station.GetStats()
	if err != nil {
		return err
	}

	var l int
	var peak int

	for _, source := range st.Icestats.Source {
		l = l + source.Listeners
		peak = peak + source.ListenerPeak
	}

	result := []string{
		fmt.Sprintf("Current listeners: %d with a maximum of %d!", l, peak),
	}

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))
	return nil
}

func curTime(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("The time currently is %s", time.Now().UTC().Format("2006-01-02 15:04:05 UTC")))

	return nil
}

func streams(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	currentMeta, metaErr := station.GetStats()
	if metaErr != nil {
		s.ChannelMessageSend(m.ChannelID, "Error receiving pvfm metadata")
		return metaErr
	}

	// start building custom embed
	outputEmbed := NewEmbed().
		SetTitle("Stream Links").
		SetDescription("These are direct feeds of the live streams; most browsers and media players can play them!")

	// this will dynamically build the list from station metadata
	pvfmList := ""
	for _, element := range currentMeta.Icestats.Source {
		pvfmList += element.ServerDescription + ":\n<" + strings.Replace(element.Listenurl, "aerial", "dj.bronyradio.com", -1) + ">\n"
	}

	// PVFM
	outputEmbed.AddField(":musical_note:  PVFM Servers", pvfmList)
	// Luna Radio
	outputEmbed.AddField(":musical_note:  Luna Radio Servers", "Luna Radio MP3 128Kbps Stream:\n<http://radio.ponyvillelive.com:8002/stream.mp3>\nLuna Radio Mobile MP3 64Kbps Stream:\n<http://radio.ponyvillelive.com:8002/mobile?;stream.mp3>\n")
	// Recordings
	outputEmbed.AddField(":cd:  DJ Recordings", "Archive\n<https://pvfmsets.cf/var/93252527679639552/>\nLegacy Archive\n<http://darkling.darkwizards.com/wang/BronyRadio/?M=D>")

	s.ChannelMessageSendEmbed(m.ChannelID, outputEmbed.MessageEmbed)

	// no errors yay!!!!
	return nil
}

func derpi(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	searchResults, err := derpiSearch.SearchDerpi(m.Content[7:len(m.Content)]) // Safe tag will be added in derpi/derpi.go
	if err != nil {
		s.ChannelMessageSend(m.ChannelID, "An error occured.")
		return err
	}
	if len(searchResults.Search) < 1 {
		s.ChannelMessageSend(m.ChannelID, "Error: No results")
		return nil
	}
	s.ChannelMessageSend(m.ChannelID, "http:"+searchResults.Search[randomRange(0, len(searchResults.Search))].Image)
	return nil
}
<commit_msg>Add timezone and "Airs in" for clarity<commit_after>package main

import (
	"fmt"
	"log"
	"math/rand"
	"strings"
	"time"

	derpiSearch "github.com/PonyvilleFM/aura/cmd/aerial/derpi"
	"github.com/PonyvilleFM/aura/pvfm"
	"github.com/PonyvilleFM/aura/pvfm/pvl"
	pvfmschedule "github.com/PonyvilleFM/aura/pvfm/schedule"
	"github.com/PonyvilleFM/aura/pvfm/station"
	"github.com/bwmarrin/discordgo"
	"github.com/tebeka/strftime"
)

func init() {
	rand.Seed(time.Now().Unix())
}

// randomRange gives a random whole integer between the given integers [min, max)
func randomRange(min, max int) int {
	return rand.Intn(max-min) + min
}

func pesterLink(s *discordgo.Session, m *discordgo.MessageCreate) {
	if musicLinkRegex.Match([]byte(m.Content)) {
		i, err := pvfm.GetStats()
		if err != nil {
			log.Println(err)
			return
		}

		if i.IsDJLive() && m.ChannelID == youtubeSpamRoomID {
			s.ChannelMessageSend(m.ChannelID, "Please be mindful sharing links to music when a DJ is performing. Thanks!")
		}
	}
}

func np(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	i, err := pvfm.GetStats()
	if err != nil {
		log.Printf("Can't get info: %v, failing over to plan b", err)
		return doStationRequest(s, m, parv)
	}

	result := []string{}

	if i.Main.Nowplaying == "Fetching info..." {
		log.Println("Main information was bad, fetching from station directly...")

		err := doStationRequest(s, m, parv)
		if err != nil {
			return err
		}

		return nil
	} else {
		result = append(result, "📻 **Now Playing on PVFM**\n")

		result = append(result, fmt.Sprintf(
			"Main 🎵 %s\n",
			i.Main.Nowplaying,
		))
		result = append(result, fmt.Sprintf(
			"Chill 🎵 %s\n",
			i.Secondary.Nowplaying,
		))
		result = append(result, fmt.Sprintf(
			"Free! 🎵 %s",
			i.MusicOnly.Nowplaying,
		))
	}

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))
	return nil
}

func dj(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	cal, err := pvl.Get()
	if err != nil {
		return err
	}

	now := cal.Result[0]
	result := []string{}

	localTime := time.Now()
	thentime := time.Unix(now.StartTime, 0)
	if thentime.Unix() < localTime.Unix() {
		result = append(result, fmt.Sprintf("Currently live: %s\n", now.Title))
		now = cal.Result[1]
	}

	nowTime := time.Unix(now.StartTime, 0).UTC()
	zone, _ := nowTime.Zone()
	fmttime, _ := strftime.Format("%Y-%m-%d %H:%M:%S", nowTime)

	result = append(result, fmt.Sprintf("Next event: %s at %s \x02%s\x02",
		now.Title,
		fmttime,
		zone,
	))

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))
	return nil
}

func stats(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	i, err := pvfm.GetStats()
	if err != nil {
		log.Printf("Error getting the station info: %v, falling back to plan b", err)
		return doStatsFromStation(s, m, parv)
	}

	st, err := station.GetStats()
	if err != nil {
		return err
	}

	var l int
	var peak int

	for _, source := range st.Icestats.Source {
		l = l + source.Listeners
		peak = peak + source.ListenerPeak
	}

	result := []string{
		fmt.Sprintf(
			"Current listeners across all streams: %d with a maximum of %d!",
			i.Listeners.Listeners, peak,
		),
		fmt.Sprintf(
			"Detailed: Main: %d listeners, Two: %d listeners, Free: %d listeners",
			i.Main.Listeners, i.Secondary.Listeners, i.MusicOnly.Listeners,
		),
	}

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))

	return nil
}

func schedule(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	schEntries, err := pvfmschedule.Get()
	if err != nil {
		return err
	}

	// Create embed object
	outputEmbed := NewEmbed().
		SetTitle("Upcoming Shows").
		SetDescription("These are the upcoming shows and events airing soon on PVFM 1.")

	for _, entry := range schEntries {

		// Format countdown timer
		startTimeUnix := time.Unix(int64(entry.StartUnix), 0)
		nowWithoutNanoseconds := time.Unix(time.Now().Unix(), 0)
		dur := startTimeUnix.Sub(nowWithoutNanoseconds)

		// Show "Live Now!" if the timer is less than 0h0m0s
		if dur > 0 {
			outputEmbed.AddField(":musical_note:  "+entry.Host+" - "+entry.Name, entry.StartTime+" "+entry.Timezone+"\nAirs in "+dur.String())
		} else {
			outputEmbed.AddField(":musical_note:  "+entry.Host+" - "+entry.Name, "Live now!")
		}
	}

	s.ChannelMessageSendEmbed(m.ChannelID, outputEmbed.MessageEmbed)
	return nil
}

func doStationRequest(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	stats, err := station.GetStats()
	if err != nil {
		return err
	}

	result := fmt.Sprintf(
		"Now playing: %s - %s on Ponyville FM!",
		stats.Icestats.Source[0].Title,
		stats.Icestats.Source[0].Artist,
	)

	s.ChannelMessageSend(m.ChannelID, result)
	return nil
}

func doStatsFromStation(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	st, err := station.GetStats()
	if err != nil {
		return err
	}

	var l int
	var peak int

	for _, source := range st.Icestats.Source {
		l = l + source.Listeners
		peak = peak + source.ListenerPeak
	}

	result := []string{
		fmt.Sprintf("Current listeners: %d with a maximum of %d!", l, peak),
	}

	s.ChannelMessageSend(m.ChannelID, strings.Join(result, "\n"))
	return nil
}

func curTime(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("The time currently is %s", time.Now().UTC().Format("2006-01-02 15:04:05 UTC")))

	return nil
}

func streams(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	currentMeta, metaErr := station.GetStats()
	if metaErr != nil {
		s.ChannelMessageSend(m.ChannelID, "Error receiving pvfm metadata")
		return metaErr
	}

	// start building custom embed
	outputEmbed := NewEmbed().
		SetTitle("Stream Links").
		SetDescription("These are direct feeds of the live streams; most browsers and media players can play them!")

	// this will dynamically build the list from station metadata
	pvfmList := ""
	for _, element := range currentMeta.Icestats.Source {
		pvfmList += element.ServerDescription + ":\n<" + strings.Replace(element.Listenurl, "aerial", "dj.bronyradio.com", -1) + ">\n"
	}

	// PVFM
	outputEmbed.AddField(":musical_note:  PVFM Servers", pvfmList)
	// Luna Radio
	outputEmbed.AddField(":musical_note:  Luna Radio Servers", "Luna Radio MP3 128Kbps Stream:\n<http://radio.ponyvillelive.com:8002/stream.mp3>\nLuna Radio Mobile MP3 64Kbps Stream:\n<http://radio.ponyvillelive.com:8002/mobile?;stream.mp3>\n")
	// Recordings
	outputEmbed.AddField(":cd:  DJ Recordings", "Archive\n<https://pvfmsets.cf/var/93252527679639552/>\nLegacy Archive\n<http://darkling.darkwizards.com/wang/BronyRadio/?M=D>")

	s.ChannelMessageSendEmbed(m.ChannelID, outputEmbed.MessageEmbed)

	// no errors yay!!!!
	return nil
}

func derpi(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	searchResults, err := derpiSearch.SearchDerpi(m.Content[7:len(m.Content)]) // Safe tag will be added in derpi/derpi.go
	if err != nil {
		s.ChannelMessageSend(m.ChannelID, "An error occured.")
		return err
	}
	if len(searchResults.Search) < 1 {
		s.ChannelMessageSend(m.ChannelID, "Error: No results")
		return nil
	}
	s.ChannelMessageSend(m.ChannelID, "http:"+searchResults.Search[randomRange(0, len(searchResults.Search))].Image)
	return nil
}
<|endoftext|>


<commit_before>/*
This package is just a collection of test cases
*/
package main

import (
	"github.com/keltia/ripe-atlas"
	"github.com/urfave/cli"
	"log"
	"os"
	"sort"
	"strings"
)

var (
	// flags
	fWant4    bool
	fWant6    bool
	fWantMine bool

	fAllProbes       bool
	fAllMeasurements bool

	fAsn         string
	fCountry     string
	fFieldList   string
	fFormat      string
	fOptFields   string
	fProtocol    string
	fSortOrder   string
	fMeasureType string

	fVerbose    bool
	fWantAnchor bool

	mycnf *atlas.Config

	cliCommands []cli.Command
)

const (
	atlasVersion = "0.8"
)

// ByAlphabet is for sorting
type ByAlphabet []cli.Command

func (a ByAlphabet) Len() int           { return len(a) }
func (a ByAlphabet) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAlphabet) Less(i, j int) bool { return a[i].Name < a[j].Name }

// checkGlobalFlags is the place to check global parameters
func checkGlobalFlags(o map[string]string) map[string]string {
	opts := o
	if fSortOrder != "" {
		opts["sort"] = fSortOrder
	}

	if fFieldList != "" {
		opts["fields"] = fFieldList
	}

	if fOptFields != "" {
		opts["optional_fields"] = fOptFields
	}

	if fFormat != "" && validateFormat(fFormat) {
		opts["format"] = fFormat
	}
	return opts
}

// validateFormat allows only supported formats
func validateFormat(fmt string) bool {
	f := strings.ToLower(fmt)
	if f == "json" || f == "xml" || f == "api" || f == "txt" || f == "jsonp" {
		return true
	}
	return false
}

func displayOptions(opts map[string]string) {
	log.Println("Options:")
	for key, val := range opts {
		log.Printf("  %s: %s", key, val)
	}
}

// main is the starting point (and everything)
func main() {
	app := cli.NewApp()
	app.Name = "atlas"
	app.Usage = "RIPE Atlas cli interface"
	app.Author = "Ollivier Robert <roberto@keltia.net>"
	app.Version = atlasVersion
	app.HideVersion = true

	// General flags
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:        "format,f",
			Usage:       "specify output format",
			Destination: &fFormat,
		},
		cli.BoolFlag{
			Name:        "verbose,v",
			Usage:       "verbose mode",
			Destination: &fVerbose,
		},
		cli.StringFlag{
			Name:        "fields,F",
			Usage:       "specify which fields are wanted",
			Destination: &fFieldList,
		},
		cli.StringFlag{
			Name:        "opt-fields,O",
			Usage:       "specify which optional fields are wanted",
			Destination: &fOptFields,
		},
		cli.StringFlag{
			Name:        "sort,S",
			Usage:       "sort results",
			Value:       "id",
			Destination: &fSortOrder,
		},
	}

	var err error

	mycnf, err = atlas.LoadConfig("ripe-atlas")
	if mycnf.APIKey != "" && err == nil {
		atlas.SetAuth(mycnf.APIKey)
		log.Printf("Found API key!")
	} else {
		log.Printf("No API key!")
	}
	if mycnf.DefaultProbe != 0 && err == nil {
		log.Printf("Found default probe: %d\n", mycnf.DefaultProbe)
	}
	sort.Sort(ByAlphabet(cliCommands))
	app.Commands = cliCommands
	app.Run(os.Args)
}
<commit_msg>No need to set a default sort order.<commit_after>/*
This package is just a collection of test cases
*/
package main

import (
	"github.com/keltia/ripe-atlas"
	"github.com/urfave/cli"
	"log"
	"os"
	"sort"
	"strings"
)

var (
	// flags
	fWant4    bool
	fWant6    bool
	fWantMine bool

	fAllProbes       bool
	fAllMeasurements bool

	fAsn         string
	fCountry     string
	fFieldList   string
	fFormat      string
	fOptFields   string
	fProtocol    string
	fSortOrder   string
	fMeasureType string

	fVerbose    bool
	fWantAnchor bool

	mycnf *atlas.Config

	cliCommands []cli.Command
)

const (
	atlasVersion = "0.8"
)

// ByAlphabet is for sorting
type ByAlphabet []cli.Command

func (a ByAlphabet) Len() int           { return len(a) }
func (a ByAlphabet) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAlphabet) Less(i, j int) bool { return a[i].Name < a[j].Name }

// checkGlobalFlags is the place to check global parameters
func checkGlobalFlags(o map[string]string) map[string]string {
	opts := o
	if fSortOrder != "" {
		opts["sort"] = fSortOrder
	}

	if fFieldList != "" {
		opts["fields"] = fFieldList
	}

	if fOptFields != "" {
		opts["optional_fields"] = fOptFields
	}

	if fFormat != "" && validateFormat(fFormat) {
		opts["format"] = fFormat
	}
	return opts
}

// validateFormat allows only supported formats
func validateFormat(fmt string) bool {
	f := strings.ToLower(fmt)
	if f == "json" || f == "xml" || f == "api" || f == "txt" || f == "jsonp" {
		return true
	}
	return false
}

func displayOptions(opts map[string]string) {
	log.Println("Options:")
	for key, val := range opts {
		log.Printf("  %s: %s", key, val)
	}
}

// main is the starting point (and everything)
func main() {
	app := cli.NewApp()
	app.Name = "atlas"
	app.Usage = "RIPE Atlas cli interface"
	app.Author = "Ollivier Robert <roberto@keltia.net>"
	app.Version = atlasVersion
	app.HideVersion = true

	// General flags
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:        "format,f",
			Usage:       "specify output format",
			Destination: &fFormat,
		},
		cli.BoolFlag{
			Name:        "verbose,v",
			Usage:       "verbose mode",
			Destination: &fVerbose,
		},
		cli.StringFlag{
			Name:        "fields,F",
			Usage:       "specify which fields are wanted",
			Destination: &fFieldList,
		},
		cli.StringFlag{
			Name:        "opt-fields,O",
			Usage:       "specify which optional fields are wanted",
			Destination: &fOptFields,
		},
		cli.StringFlag{
			Name:        "sort,S",
			Usage:       "sort results",
			Destination: &fSortOrder,
		},
	}

	var err error

	mycnf, err = atlas.LoadConfig("ripe-atlas")
	if mycnf.APIKey != "" && err == nil {
		atlas.SetAuth(mycnf.APIKey)
		log.Printf("Found API key!")
	} else {
		log.Printf("No API key!")
	}
	if mycnf.DefaultProbe != 0 && err == nil {
		log.Printf("Found default probe: %d\n", mycnf.DefaultProbe)
	}
	sort.Sort(ByAlphabet(cliCommands))
	app.Commands = cliCommands
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package cmd

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"os/signal"
	"strings"

	// _ "net/http/pprof"

	"github.com/dh1tw/remoteAudio/audio"
	"github.com/dh1tw/remoteAudio/audio/pbReader"
	"github.com/dh1tw/remoteAudio/audio/pbWriter"
	"github.com/dh1tw/remoteAudio/audio/scReader"
	"github.com/dh1tw/remoteAudio/audio/scWriter"
	"github.com/dh1tw/remoteAudio/audio/wavReader"
	"github.com/dh1tw/remoteAudio/audiocodec/opus"
	"github.com/gordonklaus/portaudio"
	"github.com/nats-io/go-nats"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

// serverMqttCmd represents the mqtt command
var natsClientCmd = &cobra.Command{
	Use:   "natsclient",
	Short: "nats client",
	Long:  `nats client`,
	Run:   natsAudioClient,
}

func init() {
	serverCmd.AddCommand(natsClientCmd)
	natsClientCmd.Flags().StringP("broker-url", "u", "localhost", "Broker URL")
	natsClientCmd.Flags().IntP("broker-port", "p", 4222, "Broker Port")
	natsClientCmd.Flags().StringP("password", "P", "", "NATS Password")
	natsClientCmd.Flags().StringP("username", "U", "", "NATS Username")
	natsClientCmd.Flags().StringP("radio", "Y", "myradio", "Radio ID")
}

func natsAudioClient(cmd *cobra.Command, args []string) {

	// Try to read config file
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	} else {
		if strings.Contains(err.Error(), "Not Found in") {
			fmt.Println("no config file found")
		} else {
			fmt.Println("Error parsing config file", viper.ConfigFileUsed())
			fmt.Println(err)
			os.Exit(1)
		}
	}

	// check if values from config file / pflags are valid
	if !checkAudioParameterValues() {
		os.Exit(-1)
	}

	// bind the pflags to viper settings
	viper.BindPFlag("nats.broker-url", cmd.Flags().Lookup("broker-url"))
	viper.BindPFlag("nats.broker-port", cmd.Flags().Lookup("broker-port"))
	viper.BindPFlag("nats.password", cmd.Flags().Lookup("password"))
	viper.BindPFlag("nats.username", cmd.Flags().Lookup("username"))
	viper.BindPFlag("nats.radio", cmd.Flags().Lookup("radio"))

	// profiling server
	// go func() {
	// 	log.Println(http.ListenAndServe("localhost:6060", nil))
	// }()

	// viper settings need to be copied in local variables
	// since viper lookups allocate of each lookup a copy
	// and are quite inperformant

	audioFramesPerBuffer := viper.GetInt("audio.frame-length")

	oDeviceName := viper.GetString("output-device.device-name")
	oSamplerate := viper.GetFloat64("output-device.samplerate")
	oLatency := viper.GetDuration("output-device.latency")
	oChannels := viper.GetInt("output-device.channels")
	oRingBufferSize := viper.GetInt("audio.rx-buffer-length")

	iDeviceName := viper.GetString("input-device.device-name")
	iSamplerate := viper.GetFloat64("input-device.samplerate")
	iLatency := viper.GetDuration("input-device.latency")
	iChannels := viper.GetInt("input-device.channels")

	opusBitrate := viper.GetInt("opus.bitrate")
	opusComplexity := viper.GetInt("opus.complexity")

	portaudio.Initialize()
	defer portaudio.Terminate()

	// Setup receiving path
	fromRadioSinks, err := audio.NewDefaultRouter()
	if err != nil {
		log.Fatal(err)
	}

	fromRadioSources, err := audio.NewDefaultSelector()
	if err != nil {
		log.Fatal(err)
	}

	// Setup transmitting path
	toRadioSinks, err := audio.NewDefaultRouter()
	if err != nil {
		log.Fatal(err)
	}

	toRadioSources, err := audio.NewDefaultSelector()
	if err != nil {
		log.Fatal(err)
	}

	nc := &natsClient{
		fromRadioSources: fromRadioSources,
		fromRadioSinks:   fromRadioSinks,
		toRadioSources:   toRadioSources,
		toRadioSinks:     toRadioSinks,
	}

	fromRadioAudio, err := scWriter.NewScWriter(
		scWriter.DeviceName(oDeviceName),
		scWriter.Channels(oChannels),
		scWriter.Samplerate(oSamplerate),
		scWriter.Latency(oLatency),
		scWriter.RingBufferSize(oRingBufferSize),
		scWriter.FramesPerBuffer(audioFramesPerBuffer),
	)
	if err != nil {
		log.Fatal(err)
	}

	// wavRec, err := wavWriter.NewWavWriter("test_rec.wav",
	// 	wavWriter.BitDepth(8),
	// 	wavWriter.Channels(1),
	// 	wavWriter.Samplerate(22000))
	// if err != nil {
	// 	log.Fatal(err)
	// }

	// nc.txRouter.AddSink("wavFile", wavRec, false)

	wav, err := wavReader.NewWavReader("test.wav")
	if err != nil {
		log.Fatal(err)
	}

	toRadioAudio, err := scReader.NewScReader(
		scReader.Callback(nc.toTxSinksCb),
		scReader.DeviceName(iDeviceName),
		scReader.Channels(iChannels),
		scReader.Samplerate(iSamplerate),
		scReader.Latency(iLatency),
		scReader.FramesPerBuffer(audioFramesPerBuffer),
	)
	if err != nil {
		log.Fatal(err)
	}

	fromNetwork, err := pbReader.NewPbReader()
	if err != nil {
		log.Fatal(err)
	}

	natsc, err := nats.Connect("nats://195.201.117.206:4222")
	if err != nil {
		log.Fatal(err)
	}

	// subscribe to the audio topic and enqueue the raw data into the pbReader
	natsc.Subscribe("fromRadio", func(m *nats.Msg) {
		err := fromNetwork.Enqueue(m.Data)
		if err != nil {
			log.Println(err)
		}
	})

	// Callback which is called by pbWriter to push the audio
	// packets to the network
	toWireCb := func(data []byte) {
		err := natsc.Publish("toRadio", data)
		if err != nil {
			log.Println(err)
		}
	}

	// opus Encoder for PbWriter
	opusEncoder, err := opus.NewEncoder(
		opus.Bitrate(opusBitrate),
		opus.Complexity(opusComplexity),
		opus.Channels(iChannels),
		opus.Samplerate(iSamplerate),
		// opus.MaxBandwidth(opusMaxBandwidth),
	)

	toNetwork, err := pbWriter.NewPbWriter(toWireCb,
		pbWriter.Encoder(opusEncoder),
		pbWriter.Samplerate(iSamplerate),
		pbWriter.Channels(iChannels),
		pbWriter.FramesPerBuffer(audioFramesPerBuffer),
	)
	if err != nil {
		log.Fatal(err)
	}

	nc.fromRadioSources.AddSource("file", wav)
	nc.fromRadioSources.AddSource("fromNetwork", fromNetwork)
	nc.fromRadioSinks.AddSink("fromRadioAudio", fromRadioAudio, false)

	nc.toRadioSources.AddSource("toRadioAudio", toRadioAudio)
	nc.toRadioSinks.AddSink("toNetwork", toNetwork, false)

	// Channel to handle OS signals
	osSignals := make(chan os.Signal, 1)

	//subscribe to os.Interrupt (CTRL-C signal)
	signal.Notify(osSignals, os.Interrupt)

	// set callback to process audio fro
	nc.fromRadioSources.SetCb(nc.toRxSinksCb)
	// start streaming to the network immediately
	nc.fromRadioSinks.EnableSink("toNetwork", true)
	nc.fromRadioSources.SetSource("fromNetwork")

	// set callback to process audio to be send to the radio
	nc.toRadioSources.SetCb(nc.toTxSinksCb)

	// stream immediately audio from the network to the radio
	nc.toRadioSources.SetSource("toRadioAudio")
	nc.toRadioSinks.EnableSink("toNetwork", true)

	keyb := make(chan string, 10)

	go func() {
		for {
			reader := bufio.NewReader(os.Stdin)
			text, _ := reader.ReadString('\n')
			keyb <- strings.TrimSuffix(text, "\n")
		}
	}()

	for {
		select {
		case input := <-keyb:
			switch input {
			// case "a":
			// 	if err := n.router.EnableSink("wavFile", true); err != nil {
			// 		log.Println(err)
			// 	}
			// case "b":
			// 	if err := n.router.EnableSink("wavFile", false); err != nil {
			// 		log.Println(err)
			// 	}
			case "f":
				nc.fromRadioSinks.Flush()
				if err := nc.fromRadioSources.SetSource("file"); err != nil {
					log.Println(err)
				}
			case "m":
				nc.toRadioSinks.Flush()
				if err := nc.fromRadioSources.SetSource("fromNetwork"); err != nil {
					log.Println(err)
				}
			case "i":
				fromRadioAudio.SetVolume(fromRadioAudio.Volume() + 0.5)
			case "d":
				fromRadioAudio.SetVolume(fromRadioAudio.Volume() - 0.5)
			}
		case sig := <-osSignals:
			if sig == os.Interrupt {
				// TBD: close also router (and all sinks)
				toRadioAudio.Close()
				fromRadioAudio.Close()
				return
			}
		}
	}
}

type natsClient struct {
	fromRadioSinks   audio.Router   //rx path
	fromRadioSources audio.Selector //rx path
	toRadioSinks     audio.Router   //tx path
	toRadioSources   audio.Selector //tx path
	isPlaying        bool
	play             chan audio.Msg
}

func (nc *natsClient) toRxSinksCb(data audio.Msg) {
	token := nc.fromRadioSinks.Write(data)
	if token.Error != nil {
		// handle Error -> remove source
	}
	token.Wait()
	if data.EOF {
		// switch back to default source
		nc.fromRadioSinks.Flush()
		if err := nc.fromRadioSources.SetSource("toNetwork"); err != nil {
			log.Println(err)
		}
	}
}

func (nc *natsClient) toTxSinksCb(data audio.Msg) {
	token := nc.toRadioSinks.Write(data)
	if token.Error != nil {
		// handle Error -> remove source
	}
	token.Wait()
	if data.EOF {
		// switch back to default source
		nc.toRadioSinks.Flush()
		if err := nc.toRadioSources.SetSource("fromNetwork"); err != nil {
			log.Println(err)
		}
	}
}

// // GetOpusApplication returns the integer representation of a
// // Opus application value string (typically read from application settings)
// func GetOpusApplication(app string) (opus.Application, error) {
// 	switch app {
// 	case "audio":
// 		return opus.AppAudio, nil
// 	case "restricted_lowdelay":
// 		return opus.AppRestrictedLowdelay, nil
// 	case "voip":
// 		return opus.AppVoIP, nil
// 	}
// 	return 0, errors.New("unknown opus application value")
// }

// // GetOpusMaxBandwith returns the integer representation of an
// // Opus max bandwitdh value string (typically read from application settings)
// func GetOpusMaxBandwith(maxBw string) (opus.Bandwidth, error) {
// 	switch strings.ToLower(maxBw) {
// 	case "narrowband":
// 		return opus.Narrowband, nil
// 	case "mediumband":
// 		return opus.Mediumband, nil
// 	case "wideband":
// 		return opus.Wideband, nil
// 	case "superwideband":
// 		return opus.SuperWideband, nil
// 	case "fullband":
// 		return opus.Fullband, nil
// 	}

// 	return 0, errors.New("unknown opus max bandwidth value")
// }

// // GetCodec return the integer representation of a string containing the
// // name of an audio codec
// func GetCodec(codec string) (int, error) {
// 	switch strings.ToLower(codec) {
// 	case "pcm":
// 		return PCM, nil
// 	case "opus":
// 		return OPUS, nil
// 	}
// 	errMsg := fmt.Sprintf("unknown codec: %s", codec)
// 	return 0, errors.New(errMsg)
// }
<commit_msg>enable sinks<commit_after>package cmd

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"os/signal"
	"strings"

	// _ "net/http/pprof"

	"github.com/dh1tw/remoteAudio/audio"
	"github.com/dh1tw/remoteAudio/audio/pbReader"
	"github.com/dh1tw/remoteAudio/audio/pbWriter"
	"github.com/dh1tw/remoteAudio/audio/scReader"
	"github.com/dh1tw/remoteAudio/audio/scWriter"
	"github.com/dh1tw/remoteAudio/audio/wavReader"
	"github.com/dh1tw/remoteAudio/audiocodec/opus"
	"github.com/gordonklaus/portaudio"
	"github.com/nats-io/go-nats"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

// serverMqttCmd represents the mqtt command
var natsClientCmd = &cobra.Command{
	Use:   "natsclient",
	Short: "nats client",
	Long:  `nats client`,
	Run:   natsAudioClient,
}

func init() {
	serverCmd.AddCommand(natsClientCmd)
	natsClientCmd.Flags().StringP("broker-url", "u", "localhost", "Broker URL")
	natsClientCmd.Flags().IntP("broker-port", "p", 4222, "Broker Port")
	natsClientCmd.Flags().StringP("password", "P", "", "NATS Password")
	natsClientCmd.Flags().StringP("username", "U", "", "NATS Username")
	natsClientCmd.Flags().StringP("radio", "Y", "myradio", "Radio ID")
}

func natsAudioClient(cmd *cobra.Command, args []string) {

	// Try to read config file
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	} else {
		if strings.Contains(err.Error(), "Not Found in") {
			fmt.Println("no config file found")
		} else {
			fmt.Println("Error parsing config file", viper.ConfigFileUsed())
			fmt.Println(err)
			os.Exit(1)
		}
	}

	// check if values from config file / pflags are valid
	if !checkAudioParameterValues() {
		os.Exit(-1)
	}

	// bind the pflags to viper settings
	viper.BindPFlag("nats.broker-url", cmd.Flags().Lookup("broker-url"))
	viper.BindPFlag("nats.broker-port", cmd.Flags().Lookup("broker-port"))
	viper.BindPFlag("nats.password", cmd.Flags().Lookup("password"))
	viper.BindPFlag("nats.username", cmd.Flags().Lookup("username"))
	viper.BindPFlag("nats.radio", cmd.Flags().Lookup("radio"))

	// profiling server
	// go func() {
	// 	log.Println(http.ListenAndServe("localhost:6060", nil))
	// }()

	// viper settings need to be copied in local variables
	// since viper lookups allocate of each lookup a copy
	// and are quite inperformant

	audioFramesPerBuffer := viper.GetInt("audio.frame-length")

	oDeviceName := viper.GetString("output-device.device-name")
	oSamplerate := viper.GetFloat64("output-device.samplerate")
	oLatency := viper.GetDuration("output-device.latency")
	oChannels := viper.GetInt("output-device.channels")
	oRingBufferSize := viper.GetInt("audio.rx-buffer-length")

	iDeviceName := viper.GetString("input-device.device-name")
	iSamplerate := viper.GetFloat64("input-device.samplerate")
	iLatency := viper.GetDuration("input-device.latency")
	iChannels := viper.GetInt("input-device.channels")

	opusBitrate := viper.GetInt("opus.bitrate")
	opusComplexity := viper.GetInt("opus.complexity")

	portaudio.Initialize()
	defer portaudio.Terminate()

	// Setup receiving path
	fromRadioSinks, err := audio.NewDefaultRouter()
	if err != nil {
		log.Fatal(err)
	}

	fromRadioSources, err := audio.NewDefaultSelector()
	if err != nil {
		log.Fatal(err)
	}

	// Setup transmitting path
	toRadioSinks, err := audio.NewDefaultRouter()
	if err != nil {
		log.Fatal(err)
	}

	toRadioSources, err := audio.NewDefaultSelector()
	if err != nil {
		log.Fatal(err)
	}

	nc := &natsClient{
		fromRadioSources: fromRadioSources,
		fromRadioSinks:   fromRadioSinks,
		toRadioSources:   toRadioSources,
		toRadioSinks:     toRadioSinks,
	}

	fromRadioAudio, err := scWriter.NewScWriter(
		scWriter.DeviceName(oDeviceName),
		scWriter.Channels(oChannels),
		scWriter.Samplerate(oSamplerate),
		scWriter.Latency(oLatency),
		scWriter.RingBufferSize(oRingBufferSize),
		scWriter.FramesPerBuffer(audioFramesPerBuffer),
	)
	if err != nil {
		log.Fatal(err)
	}

	// wavRec, err := wavWriter.NewWavWriter("test_rec.wav",
	// 	wavWriter.BitDepth(8),
	// 	wavWriter.Channels(1),
	// 	wavWriter.Samplerate(22000))
	// if err != nil {
	// 	log.Fatal(err)
	// }

	// nc.txRouter.AddSink("wavFile", wavRec, false)

	wav, err := wavReader.NewWavReader("test.wav")
	if err != nil {
		log.Fatal(err)
	}

	toRadioAudio, err := scReader.NewScReader(
		scReader.Callback(nc.toTxSinksCb),
		scReader.DeviceName(iDeviceName),
		scReader.Channels(iChannels),
		scReader.Samplerate(iSamplerate),
		scReader.Latency(iLatency),
		scReader.FramesPerBuffer(audioFramesPerBuffer),
	)
	if err != nil {
		log.Fatal(err)
	}

	fromNetwork, err := pbReader.NewPbReader()
	if err != nil {
		log.Fatal(err)
	}

	natsc, err := nats.Connect("nats://195.201.117.206:4222")
	if err != nil {
		log.Fatal(err)
	}

	// subscribe to the audio topic and enqueue the raw data into the pbReader
	natsc.Subscribe("fromRadio", func(m *nats.Msg) {
		err := fromNetwork.Enqueue(m.Data)
		if err != nil {
			log.Println(err)
		}
	})

	// Callback which is called by pbWriter to push the audio
	// packets to the network
	toWireCb := func(data []byte) {
		err := natsc.Publish("toRadio", data)
		if err != nil {
			log.Println(err)
		}
	}

	// opus Encoder for PbWriter
	opusEncoder, err := opus.NewEncoder(
		opus.Bitrate(opusBitrate),
		opus.Complexity(opusComplexity),
		opus.Channels(iChannels),
		opus.Samplerate(iSamplerate),
		// opus.MaxBandwidth(opusMaxBandwidth),
	)

	toNetwork, err := pbWriter.NewPbWriter(toWireCb,
		pbWriter.Encoder(opusEncoder),
		pbWriter.Samplerate(iSamplerate),
		pbWriter.Channels(iChannels),
		pbWriter.FramesPerBuffer(audioFramesPerBuffer),
	)
	if err != nil {
		log.Fatal(err)
	}

	nc.fromRadioSources.AddSource("file", wav)
	nc.fromRadioSources.AddSource("fromNetwork", fromNetwork)
	nc.fromRadioSinks.AddSink("fromRadioAudio", fromRadioAudio, false)

	nc.toRadioSources.AddSource("file", wav)
	nc.toRadioSources.AddSource("toRadioAudio", toRadioAudio)
	nc.toRadioSinks.AddSink("toNetwork", toNetwork, false)

	// Channel to handle OS signals
	osSignals := make(chan os.Signal, 1)

	//subscribe to os.Interrupt (CTRL-C signal)
	signal.Notify(osSignals, os.Interrupt)

	// set callback to process audio fro
	nc.fromRadioSources.SetCb(nc.toRxSinksCb)
	// start streaming to the network immediately
	nc.fromRadioSinks.EnableSink("fromRadioAudio", true)
	nc.fromRadioSources.SetSource("fromNetwork")

	// set callback to process audio to be send to the radio
	nc.toRadioSources.SetCb(nc.toTxSinksCb)

	// stream immediately audio from the network to the radio
	nc.toRadioSources.SetSource("toRadioAudio")
	nc.toRadioSinks.EnableSink("toNetwork", true)

	keyb := make(chan string, 10)

	go func() {
		for {
			reader := bufio.NewReader(os.Stdin)
			text, _ := reader.ReadString('\n')
			keyb <- strings.TrimSuffix(text, "\n")
		}
	}()

	for {
		select {
		case input := <-keyb:
			switch input {
			// case "a":
			// 	if err := n.router.EnableSink("wavFile", true); err != nil {
			// 		log.Println(err)
			// 	}
			// case "b":
			// 	if err := n.router.EnableSink("wavFile", false); err != nil {
			// 		log.Println(err)
			// 	}
			case "f":
				nc.fromRadioSinks.Flush()
				if err := nc.fromRadioSources.SetSource("file"); err != nil {
					log.Println(err)
				}
				nc.toRadioSinks.Flush()
				if err := nc.toRadioSources.SetSource("file"); err != nil {
					log.Println(err)
				}

			case "m":
				nc.toRadioSinks.Flush()
				if err := nc.fromRadioSources.SetSource("fromNetwork"); err != nil {
					log.Println(err)
				}
			case "i":
				fromRadioAudio.SetVolume(fromRadioAudio.Volume() + 0.5)
			case "d":
				fromRadioAudio.SetVolume(fromRadioAudio.Volume() - 0.5)
			}
		case sig := <-osSignals:
			if sig == os.Interrupt {
				// TBD: close also router (and all sinks)
				toRadioAudio.Close()
				fromRadioAudio.Close()
				return
			}
		}
	}
}

type natsClient struct {
	fromRadioSinks   audio.Router   //rx path
	fromRadioSources audio.Selector //rx path
	toRadioSinks     audio.Router   //tx path
	toRadioSources   audio.Selector //tx path
	isPlaying        bool
	play             chan audio.Msg
}

func (nc *natsClient) toRxSinksCb(data audio.Msg) {
	token := nc.fromRadioSinks.Write(data)
	if token.Error != nil {
		// handle Error -> remove source
	}
	token.Wait()
	if data.EOF {
		// switch back to default source
		nc.fromRadioSinks.Flush()
		if err := nc.fromRadioSources.SetSource("fromNetwork"); err != nil {
			log.Println(err)
		}
	}
}

func (nc *natsClient) toTxSinksCb(data audio.Msg) {
	token := nc.toRadioSinks.Write(data)
	if token.Error != nil {
		// handle Error -> remove source
	}
	token.Wait()
	if data.EOF {
		// switch back to default source
		nc.toRadioSinks.Flush()
		if err := nc.toRadioSources.SetSource("toRadioAudio"); err != nil {
			log.Println(err)
		}
	}
}

// // GetOpusApplication returns the integer representation of a
// // Opus application value string (typically read from application settings)
// func GetOpusApplication(app string) (opus.Application, error) {
// 	switch app {
// 	case "audio":
// 		return opus.AppAudio, nil
// 	case "restricted_lowdelay":
// 		return opus.AppRestrictedLowdelay, nil
// 	case "voip":
// 		return opus.AppVoIP, nil
// 	}
// 	return 0, errors.New("unknown opus application value")
// }

// // GetOpusMaxBandwith returns the integer representation of an
// // Opus max bandwitdh value string (typically read from application settings)
// func GetOpusMaxBandwith(maxBw string) (opus.Bandwidth, error) {
// 	switch strings.ToLower(maxBw) {
// 	case "narrowband":
// 		return opus.Narrowband, nil
// 	case "mediumband":
// 		return opus.Mediumband, nil
// 	case "wideband":
// 		return opus.Wideband, nil
// 	case "superwideband":
// 		return opus.SuperWideband, nil
// 	case "fullband":
// 		return opus.Fullband, nil
// 	}

// 	return 0, errors.New("unknown opus max bandwidth value")
// }

// // GetCodec return the integer representation of a string containing the
// // name of an audio codec
// func GetCodec(codec string) (int, error) {
// 	switch strings.ToLower(codec) {
// 	case "pcm":
// 		return PCM, nil
// 	case "opus":
// 		return OPUS, nil
// 	}
// 	errMsg := fmt.Sprintf("unknown codec: %s", codec)
// 	return 0, errors.New(errMsg)
// }
<|endoftext|>