<commit_before>package gottyclient

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"unicode/utf8"
	"unsafe"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh/terminal"
)

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

type Client struct {
	Dialer    *websocket.Dialer
	Conn      *websocket.Conn
	Headers   *http.Header
	Target    string
	Connected bool
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	conn, _, err := c.Dialer.Dial(c.Target, *c.Headers)
	if err != nil {
		return err
	}
	c.Conn = conn
	return nil
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	done := make(chan bool)
	go c.readLoop(done)
	go c.writeLoop(done)
	go c.termsizeLoop(done)
	<-done
	return nil
}

type winsize struct {
	Rows    uint16 `json:"rows"`
	Columns uint16 `json:"columns"`
	// unused
	x uint16
	y uint16
}

func (c *Client) termsizeLoop(done chan bool) {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGWINCH)
	ws := winsize{}

	for {
		syscall.Syscall(syscall.SYS_IOCTL,
			uintptr(0), uintptr(syscall.TIOCGWINSZ),
			uintptr(unsafe.Pointer(&ws)))

		b, err := json.Marshal(ws)
		if err != nil {
			logrus.Warnf("json.Marshal error: %v", err)
		}

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("1"), b...))
		if err != nil {
			logrus.Warnf("ws.WriteMessage failed: %v", err)
		}

		<-ch
	}
}

func (c *Client) writeLoop(done chan bool) {
	oldState, err := terminal.MakeRaw(0)
	if err == nil {
		defer terminal.Restore(0, oldState)
	}

	reader := bufio.NewReader(os.Stdin)
	for {
		x, size, err := reader.ReadRune()
		if size <= 0 || err != nil {
			done <- true
			return
		}

		p := make([]byte, size)
		utf8.EncodeRune(p, x)

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("0"), p...))
		if err != nil {
			done <- true
			return
		}
	}
}

func (c *Client) readLoop(done chan bool) {
	for {
		_, data, err := c.Conn.ReadMessage()
		if err != nil {
			done <- true
			logrus.Warnf("c.Conn.ReadMessage: %v", err)
			return
		}

		switch data[0] {
		case '0': // data
			fmt.Print(string(data[1:]))
		case '1': // new title
			newTitle := string(data[1:])
			fmt.Printf("\033]0;%s\007", newTitle)
		case '2': // json prefs
			logrus.Debugf("Unhandled protocol message: json pref: %s", string(data))
		default:
			logrus.Warnf("Unhandled protocol message: %s", string(data))
		}
	}
}

// NewClient returns a GoTTY client object
func NewClient(httpURL string) (*Client, error) {
	target, header, err := GetWebsocketURL(httpURL)
	if err != nil {
		return nil, err
	}

	return &Client{
		Dialer:  &websocket.Dialer{},
		Target:  target.String(),
		Headers: header,
	}, nil
}
<commit_msg>Bumped GoTTY protocol for GoTTY@0.10<commit_after>package gottyclient

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"syscall"
	"time"
	"unicode/utf8"
	"unsafe"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh/terminal"
)

// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

type Client struct {
	Dialer    *websocket.Dialer
	Conn      *websocket.Conn
	URL       string
	Connected bool
}

// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
func (c *Client) GetAuthToken() (string, error) {
	target, header, err := GetAuthTokenURL(c.URL)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest("GET", target.String(), nil)
	req.Header = *header
	client := http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
	return re.FindStringSubmatch(string(body))[1], nil
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	// Retrieve AuthToken
	authToken, err := c.GetAuthToken()
	if err != nil {
		return err
	}

	// Open WebSocket connection
	target, header, err := GetWebsocketURL(c.URL)
	if err != nil {
		return err
	}
	conn, _, err := c.Dialer.Dial(target.String(), *header)
	if err != nil {
		return err
	}
	c.Conn = conn

	// Send AuthToken
	err = c.Conn.WriteMessage(websocket.TextMessage, []byte(authToken))
	if err != nil {
		return err
	}

	go c.pingLoop()

	return nil
}

func (c *Client) pingLoop() {
	for {
		c.Conn.WriteMessage(websocket.TextMessage, []byte("1"))
		time.Sleep(30 * time.Second)
	}
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	done := make(chan bool)
	go c.readLoop(done)
	go c.writeLoop(done)
	go c.termsizeLoop(done)
	<-done
	return nil
}

type winsize struct {
	Rows    uint16 `json:"rows"`
	Columns uint16 `json:"columns"`
	// unused
	x uint16
	y uint16
}

func (c *Client) termsizeLoop(done chan bool) {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGWINCH)
	ws := winsize{}

	for {
		syscall.Syscall(syscall.SYS_IOCTL,
			uintptr(0), uintptr(syscall.TIOCGWINSZ),
			uintptr(unsafe.Pointer(&ws)))

		b, err := json.Marshal(ws)
		if err != nil {
			logrus.Warnf("json.Marshal error: %v", err)
		}

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("2"), b...))
		if err != nil {
			logrus.Warnf("ws.WriteMessage failed: %v", err)
		}

		<-ch
	}
}

func (c *Client) writeLoop(done chan bool) {
	oldState, err := terminal.MakeRaw(0)
	if err == nil {
		defer terminal.Restore(0, oldState)
	}

	reader := bufio.NewReader(os.Stdin)
	for {
		x, size, err := reader.ReadRune()
		if size <= 0 || err != nil {
			done <- true
			return
		}

		p := make([]byte, size)
		utf8.EncodeRune(p, x)

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("0"), p...))
		if err != nil {
			done <- true
			return
		}
	}
}

func (c *Client) readLoop(done chan bool) {
	for {
		_, data, err := c.Conn.ReadMessage()
		if err != nil {
			done <- true
			logrus.Warnf("c.Conn.ReadMessage: %v", err)
			return
		}

		switch data[0] {
		case '0': // data
			buf, err := base64.StdEncoding.DecodeString(string(data[1:]))
			if err != nil {
				logrus.Warnf("Invalid base64 content: %q", data[1:])
			}
			fmt.Print(string(buf))
		case '1': // pong
		case '2': // new title
			newTitle := string(data[1:])
			fmt.Printf("\033]0;%s\007", newTitle)
		case '3': // json prefs
			logrus.Debugf("Unhandled protocol message: json pref: %s", string(data))
		case '4': // autoreconnect
			logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(data))
		default:
			logrus.Warnf("Unhandled protocol message: %s", string(data))
		}
	}
}

// NewClient returns a GoTTY client object
func NewClient(httpURL string) (*Client, error) {
	return &Client{
		Dialer: &websocket.Dialer{},
		URL:    httpURL,
	}, nil
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2014 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package vm

import (
	"flag"
	"fmt"
	"io"
	"os"
	"text/tabwriter"

	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/govc/cli"
	"github.com/vmware/govmomi/govc/flags"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/vim25/mo"
	"golang.org/x/net/context"
)

type info struct {
	*flags.ClientFlag
	*flags.OutputFlag
	*flags.SearchFlag

	WaitForIP   bool
	ExtraConfig bool
}

func init() {
	cli.Register("vm.info", &info{})
}

func (cmd *info) Register(f *flag.FlagSet) {
	cmd.SearchFlag = flags.NewSearchFlag(flags.SearchVirtualMachines)

	f.BoolVar(&cmd.WaitForIP, "waitip", false, "Wait for VM to acquire IP address")
	f.BoolVar(&cmd.ExtraConfig, "e", false, "Show ExtraConfig")
}

func (cmd *info) Process() error { return nil }

func (cmd *info) Run(f *flag.FlagSet) error {
	c, err := cmd.Client()
	if err != nil {
		return err
	}

	vms, err := cmd.VirtualMachines(f.Args())
	if err != nil {
		if _, ok := err.(*find.NotFoundError); ok {
			// Continue with empty VM slice
		} else {
			return err
		}
	}

	var res infoResult
	var props []string

	if cmd.OutputFlag.JSON {
		props = nil // Load everything
	} else {
		props = []string{"summary", "guest.ipAddress"} // Load summary
		if cmd.ExtraConfig {
			props = append(props, "config.extraConfig")
		}
	}

	for _, vm := range vms {
		for {
			var mvm mo.VirtualMachine

			pc := property.DefaultCollector(c)
			err = pc.RetrieveOne(context.TODO(), vm.Reference(), props, &mvm)
			if err != nil {
				return err
			}

			if cmd.WaitForIP && mvm.Guest.IpAddress == "" {
				_, err = vm.WaitForIP(context.TODO())
				if err != nil {
					return err
				}

				// Reload virtual machine object
				continue
			}

			res.VirtualMachines = append(res.VirtualMachines, mvm)
			break
		}
	}

	return cmd.WriteResult(&res)
}

type infoResult struct {
	VirtualMachines []mo.VirtualMachine
}

func (r *infoResult) Write(w io.Writer) error {
	tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)

	for _, vm := range r.VirtualMachines {
		s := vm.Summary

		fmt.Fprintf(tw, "Name:\t%s\n", s.Config.Name)
		fmt.Fprintf(tw, "  UUID:\t%s\n", s.Config.Uuid)
		fmt.Fprintf(tw, "  Guest name:\t%s\n", s.Config.GuestFullName)
		fmt.Fprintf(tw, "  Memory:\t%dMB\n", s.Config.MemorySizeMB)
		fmt.Fprintf(tw, "  CPU:\t%d vCPU(s)\n", s.Config.NumCpu)
		fmt.Fprintf(tw, "  Power state:\t%s\n", s.Runtime.PowerState)
		fmt.Fprintf(tw, "  Boot time:\t%s\n", s.Runtime.BootTime)
		fmt.Fprintf(tw, "  IP address:\t%s\n", s.Guest.IpAddress)
		if vm.Config != nil && vm.Config.ExtraConfig != nil {
			fmt.Fprintf(tw, "  ExtraConfig:\n")
			for _, v := range vm.Config.ExtraConfig {
				fmt.Fprintf(tw, "    %s:\t%s\n", v.GetOptionValue().Key, v.GetOptionValue().Value)
			}
		}
	}

	return tw.Flush()
}
<commit_msg>Use property.Collector.Retrieve() in vm.info<commit_after>/*
Copyright (c) 2014 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package vm

import (
	"flag"
	"fmt"
	"io"
	"os"
	"text/tabwriter"

	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/govc/cli"
	"github.com/vmware/govmomi/govc/flags"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
	"golang.org/x/net/context"
)

type info struct {
	*flags.ClientFlag
	*flags.OutputFlag
	*flags.SearchFlag

	WaitForIP   bool
	ExtraConfig bool
}

func init() {
	cli.Register("vm.info", &info{})
}

func (cmd *info) Register(f *flag.FlagSet) {
	cmd.SearchFlag = flags.NewSearchFlag(flags.SearchVirtualMachines)

	f.BoolVar(&cmd.WaitForIP, "waitip", false, "Wait for VM to acquire IP address")
	f.BoolVar(&cmd.ExtraConfig, "e", false, "Show ExtraConfig")
}

func (cmd *info) Process() error { return nil }

func (cmd *info) Run(f *flag.FlagSet) error {
	c, err := cmd.Client()
	if err != nil {
		return err
	}

	vms, err := cmd.VirtualMachines(f.Args())
	if err != nil {
		if _, ok := err.(*find.NotFoundError); ok {
			// Continue with empty VM slice
		} else {
			return err
		}
	}

	refs := make([]types.ManagedObjectReference, 0, len(vms))
	for _, vm := range vms {
		refs = append(refs, vm.Reference())
	}

	var res infoResult
	var props []string

	if cmd.OutputFlag.JSON {
		props = nil // Load everything
	} else {
		props = []string{"summary", "guest.ipAddress"} // Load summary
		if cmd.ExtraConfig {
			props = append(props, "config.extraConfig")
		}
	}

	ctx := context.TODO()
	pc := property.DefaultCollector(c)
	err = pc.Retrieve(ctx, refs, props, &res.VirtualMachines)
	if err != nil {
		return err
	}

	if cmd.WaitForIP {
		for i, vm := range res.VirtualMachines {
			if vm.Guest == nil || vm.Guest.IpAddress == "" {
				_, err = vms[i].WaitForIP(ctx)
				if err != nil {
					return err
				}
				// Reload virtual machine object
				err = pc.RetrieveOne(ctx, vms[i].Reference(), props, &res.VirtualMachines[i])
				if err != nil {
					return err
				}
			}
		}
	}

	return cmd.WriteResult(&res)
}

type infoResult struct {
	VirtualMachines []mo.VirtualMachine
}

func (r *infoResult) Write(w io.Writer) error {
	tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)

	for _, vm := range r.VirtualMachines {
		s := vm.Summary

		fmt.Fprintf(tw, "Name:\t%s\n", s.Config.Name)
		fmt.Fprintf(tw, "  UUID:\t%s\n", s.Config.Uuid)
		fmt.Fprintf(tw, "  Guest name:\t%s\n", s.Config.GuestFullName)
		fmt.Fprintf(tw, "  Memory:\t%dMB\n", s.Config.MemorySizeMB)
		fmt.Fprintf(tw, "  CPU:\t%d vCPU(s)\n", s.Config.NumCpu)
		fmt.Fprintf(tw, "  Power state:\t%s\n", s.Runtime.PowerState)
		fmt.Fprintf(tw, "  Boot time:\t%s\n", s.Runtime.BootTime)
		fmt.Fprintf(tw, "  IP address:\t%s\n", s.Guest.IpAddress)
		if vm.Config != nil && vm.Config.ExtraConfig != nil {
			fmt.Fprintf(tw, "  ExtraConfig:\n")
			for _, v := range vm.Config.ExtraConfig {
				fmt.Fprintf(tw, "    %s:\t%s\n", v.GetOptionValue().Key, v.GetOptionValue().Value)
			}
		}
	}

	return tw.Flush()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/bcicen/ctop/config"
	"github.com/bcicen/ctop/container"
	"github.com/bcicen/ctop/widgets"
	"github.com/bcicen/ctop/widgets/menu"
	ui "github.com/gizak/termui"
)

// MenuFn executes a menu window, returning the next menu or nil
type MenuFn func() MenuFn

var helpDialog = []menu.Item{
	{"<enter> - open container menu", ""},
	{"", ""},
	{"[a] - toggle display of all containers", ""},
	{"[f] - filter displayed containers", ""},
	{"[h] - open this help dialog", ""},
	{"[H] - toggle ctop header", ""},
	{"[s] - select container sort field", ""},
	{"[r] - reverse container sort order", ""},
	{"[o] - open single view", ""},
	{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
	{"[e] - exec shell", ""},
	{"[c] - configure columns", ""},
	{"[S] - save current configuration to file", ""},
	{"[q] - exit ctop", ""},
}

func HelpMenu() MenuFn {
	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.BorderLabel = "Help"
	m.AddItems(helpDialog...)
	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		ui.Clear()
		ui.Render(m)
	})
	ui.Handle("/sys/kbd/", func(ui.Event) {
		ui.StopLoop()
	})
	ui.Loop()
	return nil
}

func FilterMenu() MenuFn {
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	i := widgets.NewInput()
	i.BorderLabel = "Filter"
	i.SetY(ui.TermHeight() - i.Height)
	i.Data = config.GetVal("filterStr")
	ui.Render(i)

	// refresh container rows on input
	stream := i.Stream()
	go func() {
		for s := range stream {
			config.Update("filterStr", s)
			RefreshDisplay()
			ui.Render(i)
		}
	}()

	i.InputHandlers()
	ui.Handle("/sys/kbd/<escape>", func(ui.Event) {
		config.Update("filterStr", "")
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		config.Update("filterStr", i.Data)
		ui.StopLoop()
	})
	ui.Loop()
	return nil
}

func SortMenu() MenuFn {
	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.Selectable = true
	m.SortItems = true
	m.BorderLabel = "Sort Field"

	for _, field := range container.SortFields() {
		m.AddItems(menu.Item{field, ""})
	}

	// set cursor position to current sort field
	m.SetCursor(config.GetVal("sortField"))

	HandleKeys("up", m.Up)
	HandleKeys("down", m.Down)
	HandleKeys("exit", ui.StopLoop)

	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		config.Update("sortField", m.SelectedValue())
		ui.StopLoop()
	})

	ui.Render(m)
	ui.Loop()
	return nil
}

func ColumnsMenu() MenuFn {
	const (
		enabledStr  = "[X]"
		disabledStr = "[ ]"
		padding     = 2
	)

	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.Selectable = true
	m.SortItems = false
	m.BorderLabel = "Columns"
	//m.SubText = "Enabled Columns"

	rebuild := func() {
		// get padding for right alignment of enabled status
		var maxLen int
		for _, col := range config.GlobalColumns {
			if len(col.Label) > maxLen {
				maxLen = len(col.Label)
			}
		}
		maxLen += padding

		// rebuild menu items
		m.ClearItems()
		for _, col := range config.GlobalColumns {
			txt := col.Label + strings.Repeat(" ", maxLen-len(col.Label))
			if col.Enabled {
				txt += enabledStr
			} else {
				txt += disabledStr
			}
			m.AddItems(menu.Item{col.Name, txt})
		}
	}

	upFn := func() {
		config.ColumnLeft(m.SelectedValue())
		m.Up()
		rebuild()
	}

	downFn := func() {
		config.ColumnRight(m.SelectedValue())
		m.Down()
		rebuild()
	}

	toggleFn := func() {
		config.ColumnToggle(m.SelectedValue())
		rebuild()
	}

	rebuild()

	HandleKeys("up", m.Up)
	HandleKeys("down", m.Down)
	HandleKeys("enter", toggleFn)
	HandleKeys("pgup", upFn)
	HandleKeys("pgdown", downFn)

	ui.Handle("/sys/kbd/x", func(ui.Event) { toggleFn() })
	ui.Handle("/sys/kbd/<enter>", func(ui.Event) { toggleFn() })

	HandleKeys("exit", func() {
		cSource, err := cursor.cSuper.Get()
		if err == nil {
			for _, c := range cSource.All() {
				c.RecreateWidgets()
			}
		}
		ui.StopLoop()
	})

	ui.Render(m)
	ui.Loop()
	return nil
}

func ContainerMenu() MenuFn {
	c := cursor.Selected()
	if c == nil {
		return nil
	}

	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.Selectable = true
	m.BorderLabel = "Menu"

	items := []menu.Item{
		menu.Item{Val: "single", Label: "[o] single view"},
		menu.Item{Val: "logs", Label: "[l] log view"},
	}

	if c.Meta["state"] == "running" {
		items = append(items, menu.Item{Val: "stop", Label: "[s] stop"})
		items = append(items, menu.Item{Val: "pause", Label: "[p] pause"})
		items = append(items, menu.Item{Val: "restart", Label: "[r] restart"})
		items = append(items, menu.Item{Val: "exec", Label: "[e] exec shell"})
	}
	if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
		items = append(items, menu.Item{Val: "start", Label: "[s] start"})
		items = append(items, menu.Item{Val: "remove", Label: "[R] remove"})
	}
	if c.Meta["state"] == "paused" {
		items = append(items, menu.Item{Val: "unpause", Label: "[p] unpause"})
	}
	items = append(items, menu.Item{Val: "cancel", Label: "[c] cancel"})

	m.AddItems(items...)
	ui.Render(m)

	HandleKeys("up", m.Up)
	HandleKeys("down", m.Down)

	var selected string

	// shortcuts
	ui.Handle("/sys/kbd/o", func(ui.Event) {
		selected = "single"
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/l", func(ui.Event) {
		selected = "logs"
		ui.StopLoop()
	})
	if c.Meta["state"] != "paused" {
		ui.Handle("/sys/kbd/s", func(ui.Event) {
			if c.Meta["state"] == "running" {
				selected = "stop"
			} else {
				selected = "start"
			}
			ui.StopLoop()
		})
	}
	if c.Meta["state"] != "exited" || c.Meta["state"] != "created" {
		ui.Handle("/sys/kbd/p", func(ui.Event) {
			if c.Meta["state"] == "paused" {
				selected = "unpause"
			} else {
				selected = "pause"
			}
			ui.StopLoop()
		})
	}
	if c.Meta["state"] == "running" {
		ui.Handle("/sys/kbd/e", func(ui.Event) {
			selected = "exec"
			ui.StopLoop()
		})
		ui.Handle("/sys/kbd/r", func(ui.Event) {
			selected = "restart"
			ui.StopLoop()
		})
	}
	ui.Handle("/sys/kbd/R", func(ui.Event) {
		selected = "remove"
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/c", func(ui.Event) {
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		selected = m.SelectedValue()
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/", func(ui.Event) {
		ui.StopLoop()
	})
	ui.Loop()

	var nextMenu MenuFn
	switch selected {
	case "single":
		nextMenu = SingleView
	case "logs":
		nextMenu = LogMenu
	case "exec":
		nextMenu = ExecShell
	case "start":
		nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
	case "stop":
		nextMenu = Confirm(confirmTxt("stop", c.GetMeta("name")), c.Stop)
	case "remove":
		nextMenu = Confirm(confirmTxt("remove", c.GetMeta("name")), c.Remove)
	case "pause":
		nextMenu = Confirm(confirmTxt("pause", c.GetMeta("name")), c.Pause)
	case "unpause":
		nextMenu = Confirm(confirmTxt("unpause", c.GetMeta("name")), c.Unpause)
	case "restart":
		nextMenu = Confirm(confirmTxt("restart", c.GetMeta("name")), c.Restart)
	}

	return nextMenu
}

func LogMenu() MenuFn {

	c := cursor.Selected()
	if c == nil {
		return nil
	}

	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	logs, quit := logReader(c)
	m := widgets.NewTextView(logs)
	m.BorderLabel = fmt.Sprintf("Logs [%s]", c.GetMeta("name"))
	ui.Render(m)

	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		m.Resize()
	})
	ui.Handle("/sys/kbd/t", func(ui.Event) {
		m.Toggle()
	})
	ui.Handle("/sys/kbd/", func(ui.Event) {
		quit <- true
		ui.StopLoop()
	})
	ui.Loop()
	return nil
}

func ExecShell() MenuFn {
	c := cursor.Selected()

	if c == nil {
		return nil
	}

	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	shell := config.GetVal("shell")
	if err := c.Exec([]string{shell, "-c", "printf '\\e[0m\\e[?25h' && clear && " + shell}); err != nil {
		log.Fatal(err)
	}

	return nil
}

// Create a confirmation dialog with a given description string and
// func to perform if confirmed
func Confirm(txt string, fn func()) MenuFn {
	menu := func() MenuFn {
		ui.DefaultEvtStream.ResetHandlers()
		defer ui.DefaultEvtStream.ResetHandlers()

		m := menu.NewMenu()
		m.Selectable = true
		m.BorderLabel = "Confirm"
		m.SubText = txt

		items := []menu.Item{
			menu.Item{Val: "cancel", Label: "[c]ancel"},
			menu.Item{Val: "yes", Label: "[y]es"},
		}

		var response bool

		m.AddItems(items...)
		ui.Render(m)

		yes := func() {
			response = true
			ui.StopLoop()
		}

		no := func() {
			response = false
			ui.StopLoop()
		}

		HandleKeys("up", m.Up)
		HandleKeys("down", m.Down)
		HandleKeys("exit", no)
		ui.Handle("/sys/kbd/c", func(ui.Event) { no() })
		ui.Handle("/sys/kbd/y", func(ui.Event) { yes() })

		ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
			switch m.SelectedValue() {
			case "cancel":
				no()
			case "yes":
				yes()
			}
		})

		ui.Loop()
		if response {
			fn()
		}
		return nil
	}
	return menu
}

type toggleLog struct {
	timestamp time.Time
	message   string
}

func (t *toggleLog) Toggle(on bool) string {
	if on {
		return fmt.Sprintf("%s %s", t.timestamp.Format("2006-01-02T15:04:05.999Z07:00"), t.message)
	}
	return t.message
}

func logReader(container *container.Container) (logs chan widgets.ToggleText, quit chan bool) {

	logCollector := container.Logs()
	stream := logCollector.Stream()
	logs = make(chan widgets.ToggleText)
	quit = make(chan bool)

	go func() {
		for {
			select {
			case log := <-stream:
				logs <- &toggleLog{timestamp: log.Timestamp, message: log.Message}
			case <-quit:
				logCollector.Stop()
				close(logs)
				return
			}
		}
	}()
	return
}

func confirmTxt(a, n string) string { return fmt.Sprintf("%s container %s?", a, n) }
<commit_msg>exec shell: on error show a status message instead of fatal exit<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/bcicen/ctop/config"
	"github.com/bcicen/ctop/container"
	"github.com/bcicen/ctop/widgets"
	"github.com/bcicen/ctop/widgets/menu"
	ui "github.com/gizak/termui"
)

// MenuFn executes a menu window, returning the next menu or nil
type MenuFn func() MenuFn

var helpDialog = []menu.Item{
	{"<enter> - open container menu", ""},
	{"", ""},
	{"[a] - toggle display of all containers", ""},
	{"[f] - filter displayed containers", ""},
	{"[h] - open this help dialog", ""},
	{"[H] - toggle ctop header", ""},
	{"[s] - select container sort field", ""},
	{"[r] - reverse container sort order", ""},
	{"[o] - open single view", ""},
	{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
	{"[e] - exec shell", ""},
	{"[c] - configure columns", ""},
	{"[S] - save current configuration to file", ""},
	{"[q] - exit ctop", ""},
}

func HelpMenu() MenuFn {
	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.BorderLabel = "Help"
	m.AddItems(helpDialog...)
	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		ui.Clear()
		ui.Render(m)
	})
	ui.Handle("/sys/kbd/", func(ui.Event) {
		ui.StopLoop()
	})
	ui.Loop()
	return nil
}

func FilterMenu() MenuFn {
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	i := widgets.NewInput()
	i.BorderLabel = "Filter"
	i.SetY(ui.TermHeight() - i.Height)
	i.Data = config.GetVal("filterStr")
	ui.Render(i)

	// refresh container rows on input
	stream := i.Stream()
	go func() {
		for s := range stream {
			config.Update("filterStr", s)
			RefreshDisplay()
			ui.Render(i)
		}
	}()

	i.InputHandlers()
	ui.Handle("/sys/kbd/<escape>", func(ui.Event) {
		config.Update("filterStr", "")
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		config.Update("filterStr", i.Data)
		ui.StopLoop()
	})
	ui.Loop()
	return nil
}

func SortMenu() MenuFn {
	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.Selectable = true
	m.SortItems = true
	m.BorderLabel = "Sort Field"

	for _, field := range container.SortFields() {
		m.AddItems(menu.Item{field, ""})
	}

	// set cursor position to current sort field
	m.SetCursor(config.GetVal("sortField"))

	HandleKeys("up", m.Up)
	HandleKeys("down", m.Down)
	HandleKeys("exit", ui.StopLoop)

	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		config.Update("sortField", m.SelectedValue())
		ui.StopLoop()
	})

	ui.Render(m)
	ui.Loop()
	return nil
}

func ColumnsMenu() MenuFn {
	const (
		enabledStr  = "[X]"
		disabledStr = "[ ]"
		padding     = 2
	)

	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.Selectable = true
	m.SortItems = false
	m.BorderLabel = "Columns"
	//m.SubText = "Enabled Columns"

	rebuild := func() {
		// get padding for right alignment of enabled status
		var maxLen int
		for _, col := range config.GlobalColumns {
			if len(col.Label) > maxLen {
				maxLen = len(col.Label)
			}
		}
		maxLen += padding

		// rebuild menu items
		m.ClearItems()
		for _, col := range config.GlobalColumns {
			txt := col.Label + strings.Repeat(" ", maxLen-len(col.Label))
			if col.Enabled {
				txt += enabledStr
			} else {
				txt += disabledStr
			}
			m.AddItems(menu.Item{col.Name, txt})
		}
	}

	upFn := func() {
		config.ColumnLeft(m.SelectedValue())
		m.Up()
		rebuild()
	}

	downFn := func() {
		config.ColumnRight(m.SelectedValue())
		m.Down()
		rebuild()
	}

	toggleFn := func() {
		config.ColumnToggle(m.SelectedValue())
		rebuild()
	}

	rebuild()

	HandleKeys("up", m.Up)
	HandleKeys("down", m.Down)
	HandleKeys("enter", toggleFn)
	HandleKeys("pgup", upFn)
	HandleKeys("pgdown", downFn)

	ui.Handle("/sys/kbd/x", func(ui.Event) { toggleFn() })
	ui.Handle("/sys/kbd/<enter>", func(ui.Event) { toggleFn() })

	HandleKeys("exit", func() {
		cSource, err := cursor.cSuper.Get()
		if err == nil {
			for _, c := range cSource.All() {
				c.RecreateWidgets()
			}
		}
		ui.StopLoop()
	})

	ui.Render(m)
	ui.Loop()
	return nil
}

func ContainerMenu() MenuFn {
	c := cursor.Selected()
	if c == nil {
		return nil
	}

	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	m := menu.NewMenu()
	m.Selectable = true
	m.BorderLabel = "Menu"

	items := []menu.Item{
		menu.Item{Val: "single", Label: "[o] single view"},
		menu.Item{Val: "logs", Label: "[l] log view"},
	}

	if c.Meta["state"] == "running" {
		items = append(items, menu.Item{Val: "stop", Label: "[s] stop"})
		items = append(items, menu.Item{Val: "pause", Label: "[p] pause"})
		items = append(items, menu.Item{Val: "restart", Label: "[r] restart"})
		items = append(items, menu.Item{Val: "exec", Label: "[e] exec shell"})
	}
	if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
		items = append(items, menu.Item{Val: "start", Label: "[s] start"})
		items = append(items, menu.Item{Val: "remove", Label: "[R] remove"})
	}
	if c.Meta["state"] == "paused" {
		items = append(items, menu.Item{Val: "unpause", Label: "[p] unpause"})
	}
	items = append(items, menu.Item{Val: "cancel", Label: "[c] cancel"})

	m.AddItems(items...)
	ui.Render(m)

	HandleKeys("up", m.Up)
	HandleKeys("down", m.Down)

	var selected string

	// shortcuts
	ui.Handle("/sys/kbd/o", func(ui.Event) {
		selected = "single"
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/l", func(ui.Event) {
		selected = "logs"
		ui.StopLoop()
	})
	if c.Meta["state"] != "paused" {
		ui.Handle("/sys/kbd/s", func(ui.Event) {
			if c.Meta["state"] == "running" {
				selected = "stop"
			} else {
				selected = "start"
			}
			ui.StopLoop()
		})
	}
	if c.Meta["state"] != "exited" || c.Meta["state"] != "created" {
		ui.Handle("/sys/kbd/p", func(ui.Event) {
			if c.Meta["state"] == "paused" {
				selected = "unpause"
			} else {
				selected = "pause"
			}
			ui.StopLoop()
		})
	}
	if c.Meta["state"] == "running" {
		ui.Handle("/sys/kbd/e", func(ui.Event) {
			selected = "exec"
			ui.StopLoop()
		})
		ui.Handle("/sys/kbd/r", func(ui.Event) {
			selected = "restart"
			ui.StopLoop()
		})
	}
	ui.Handle("/sys/kbd/R", func(ui.Event) {
		selected = "remove"
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/c", func(ui.Event) {
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		selected = m.SelectedValue()
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/", func(ui.Event) {
		ui.StopLoop()
	})
	ui.Loop()

	var nextMenu MenuFn
	switch selected {
	case "single":
		nextMenu = SingleView
	case "logs":
		nextMenu = LogMenu
	case "exec":
		nextMenu = ExecShell
	case "start":
		nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
	case "stop":
		nextMenu = Confirm(confirmTxt("stop", c.GetMeta("name")), c.Stop)
	case "remove":
		nextMenu = Confirm(confirmTxt("remove", c.GetMeta("name")), c.Remove)
	case "pause":
		nextMenu = Confirm(confirmTxt("pause", c.GetMeta("name")), c.Pause)
	case "unpause":
		nextMenu = Confirm(confirmTxt("unpause", c.GetMeta("name")), c.Unpause)
	case "restart":
		nextMenu = Confirm(confirmTxt("restart", c.GetMeta("name")), c.Restart)
	}

	return nextMenu
}

func LogMenu() MenuFn {

	c := cursor.Selected()
	if c == nil {
		return nil
	}

	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	logs, quit := logReader(c)
	m := widgets.NewTextView(logs)
	m.BorderLabel = fmt.Sprintf("Logs [%s]", c.GetMeta("name"))
	ui.Render(m)

	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		m.Resize()
	})
	ui.Handle("/sys/kbd/t", func(ui.Event) {
		m.Toggle()
	})
	ui.Handle("/sys/kbd/", func(ui.Event) {
		quit <- true
		ui.StopLoop()
	})
	ui.Loop()
	return nil
}

func ExecShell() MenuFn {
	c := cursor.Selected()

	if c == nil {
		return nil
	}

	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	shell := config.GetVal("shell")
	if err := c.Exec([]string{shell, "-c", "printf '\\e[0m\\e[?25h' && clear && " + shell}); err != nil {
		log.StatusErr(err)
	}

	return nil
}

// Create a confirmation dialog with a given description string and
// func to perform if confirmed
func Confirm(txt string, fn func()) MenuFn {
	menu := func() MenuFn {
		ui.DefaultEvtStream.ResetHandlers()
		defer ui.DefaultEvtStream.ResetHandlers()

		m := menu.NewMenu()
		m.Selectable = true
		m.BorderLabel = "Confirm"
		m.SubText = txt

		items := []menu.Item{
			menu.Item{Val: "cancel", Label: "[c]ancel"},
			menu.Item{Val: "yes", Label: "[y]es"},
		}

		var response bool

		m.AddItems(items...)
		ui.Render(m)

		yes := func() {
			response = true
			ui.StopLoop()
		}

		no := func() {
			response = false
			ui.StopLoop()
		}

		HandleKeys("up", m.Up)
		HandleKeys("down", m.Down)
		HandleKeys("exit", no)
		ui.Handle("/sys/kbd/c", func(ui.Event) { no() })
		ui.Handle("/sys/kbd/y", func(ui.Event) { yes() })

		ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
			switch m.SelectedValue() {
			case "cancel":
				no()
			case "yes":
				yes()
			}
		})

		ui.Loop()
		if response {
			fn()
		}
		return nil
	}
	return menu
}

type toggleLog struct {
	timestamp time.Time
	message   string
}

func (t *toggleLog) Toggle(on bool) string {
	if on {
		return fmt.Sprintf("%s %s", t.timestamp.Format("2006-01-02T15:04:05.999Z07:00"), t.message)
	}
	return t.message
}

func logReader(container *container.Container) (logs chan widgets.ToggleText, quit chan bool) {

	logCollector := container.Logs()
	stream := logCollector.Stream()
	logs = make(chan widgets.ToggleText)
	quit = make(chan bool)

	go func() {
		for {
			select {
			case log := <-stream:
				logs <- &toggleLog{timestamp: log.Timestamp, message: log.Message}
			case <-quit:
				logCollector.Stop()
				close(logs)
				return
			}
		}
	}()
	return
}

func confirmTxt(a, n string) string { return fmt.Sprintf("%s container %s?", a, n) }
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"fmt"
	"sort"
	"time"

	"github.com/joliv/spark"

	"os"
)

type Merki struct {
	delimiter rune
}

func NewMerki(delimier rune) *Merki {
	return &Merki{delimiter}
}

func (m *Merki) AddRecord(fileName string, record *Record) error {
	f, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}

	defer f.Close()

	w := csv.NewWriter(f)
	w.Comma = m.delimiter
	if err := w.Write(record.getStrings(false)); err != nil {
		return err
	}
	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}

func (m *Merki) DrawSparkline(fileName, measure string) (string, error) {
	var values []float64
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				if record.Measurement == measure {
					values = append(values, record.Value)
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return "", err
	}
	sparkline := spark.Line(values)
	return sparkline, nil
}

func (m *Merki) Measurements(fileName string) error {
	measures := make(map[string]bool)
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				measures[record.Measurement] = true
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}
	for name := range measures {
		fmt.Println(name)
	}
	return nil
}

func (m *Merki) Latest(fileName string) error {
	w := csv.NewWriter(os.Stdout)
	w.Comma = m.delimiter
	parser := NewParser(string(m.delimiter))
	list := make(map[string]*Record)
	var ss sort.StringSlice
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				key := record.Measurement
				val, ok := list[key]
				if !ok {
					list[key] = record
					ss = append(ss, key)
					continue
				}
				if record.Date.After(val.Date) {
					list[key] = record
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}
	ss.Sort()
	for _, key := range ss {
		r, _ := list[key]
		if err := w.Write(r.getStrings(true)); err != nil {
			return err
		}
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}

func (m *Merki) Filter(fileName, measure string, gi GroupingInterval, gt GroupingType) error {
	w := csv.NewWriter(os.Stdout)
	w.Comma = m.delimiter
	filter := NewFilter(w, measure, gi, gt)
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				if err := filter.Add(record); err != nil {
					return err
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}

	err = filter.Print()
	if err != nil {
		return err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}

func (m *Merki) Interval(fileName, measure string) error {
	w := csv.NewWriter(os.Stdout)
	w.Comma = m.delimiter
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		var startTime *time.Time
		var interval time.Duration
		for {
			select {
			case record := <-parser.Record:
				if record.Measurement == measure {
					if startTime != nil {
						interval = record.Date.Sub(*startTime)
						err := w.Write([]string{fmt.Sprintf("%d", interval.Seconds())})
						if err != nil {
							return err
						}
					}
					startTime = &record.Date
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}
	w.Flush()
	if err != nil {
		return err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}
<commit_msg>Fix format<commit_after>package main

import (
	"encoding/csv"
	"fmt"
	"sort"
	"time"

	"github.com/joliv/spark"

	"os"
)

type Merki struct {
	delimiter rune
}

func NewMerki(delimier rune) *Merki {
	return &Merki{delimiter}
}

func (m *Merki) AddRecord(fileName string, record *Record) error {
	f, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}

	defer f.Close()

	w := csv.NewWriter(f)
	w.Comma = m.delimiter
	if err := w.Write(record.getStrings(false)); err != nil {
		return err
	}
	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}

func (m *Merki) DrawSparkline(fileName, measure string) (string, error) {
	var values []float64
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				if record.Measurement == measure {
					values = append(values, record.Value)
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return "", err
	}
	sparkline := spark.Line(values)
	return sparkline, nil
}

func (m *Merki) Measurements(fileName string) error {
	measures := make(map[string]bool)
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				measures[record.Measurement] = true
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}
	for name := range measures {
		fmt.Println(name)
	}
	return nil
}

func (m *Merki) Latest(fileName string) error {
	w := csv.NewWriter(os.Stdout)
	w.Comma = m.delimiter
	parser := NewParser(string(m.delimiter))
	list := make(map[string]*Record)
	var ss sort.StringSlice
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				key := record.Measurement
				val, ok := list[key]
				if !ok {
					list[key] = record
					ss = append(ss, key)
					continue
				}
				if record.Date.After(val.Date) {
					list[key] = record
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}
	ss.Sort()
	for _, key := range ss {
		r, _ := list[key]
		if err := w.Write(r.getStrings(true)); err != nil {
			return err
		}
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}

func (m *Merki) Filter(fileName, measure string, gi GroupingInterval, gt GroupingType) error {
	w := csv.NewWriter(os.Stdout)
	w.Comma = m.delimiter
	filter := NewFilter(w, measure, gi, gt)
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		for {
			select {
			case record := <-parser.Record:
				if err := filter.Add(record); err != nil {
					return err
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}

	err = filter.Print()
	if err != nil {
		return err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}

func (m *Merki) Interval(fileName, measure string) error {
	w := csv.NewWriter(os.Stdout)
	w.Comma = m.delimiter
	parser := NewParser(string(m.delimiter))
	go parser.ParseFile(fileName)
	err := func() error {
		var startTime *time.Time
		var interval time.Duration
		for {
			select {
			case record := <-parser.Record:
				if record.Measurement == measure {
					if startTime != nil {
						interval = record.Date.Sub(*startTime)
						err := w.Write([]string{fmt.Sprintf("%d", int(interval.Seconds()))})
						if err != nil {
							return err
						}
					}
					startTime = &record.Date
				}
			case err := <-parser.Error:
				return err
			case <-parser.Done:
				return nil
			}
		}
	}()
	if err != nil {
		return err
	}
	w.Flush()
	if err != nil {
		return err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before><commit_msg>Optimizes HasVar<commit_after><|endoftext|>


<commit_before><commit_msg>start breaking up program2<commit_after><|endoftext|>


<commit_before><commit_msg>use polling instead of inotify with tailed files. also other cleanup<commit_after><|endoftext|>


<commit_before>// Copyright (C) 2015 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package monkit

import (
	"sync"
	"time"

	"github.com/spacemonkeygo/monotime"
)

const (
	ticksToKeep = 24
	timePerTick = 10 * time.Minute
)

var (
	defaultTicker = ticker{}
)

type meterBucket struct {
	count int64
	start time.Duration
}

// Meter keeps track of events and their rates over time.
// Implements the StatSource interface. You should construct using NewMeter,
// though expected usage is like:
//
//   var (
//     mon   = monkit.Package()
//     meter = mon.Meter("meter")
//   )
//
//   func MyFunc() {
//     ...
//     meter.Mark(4) // 4 things happened
//     ...
//   }
//
type Meter struct {
	mtx    sync.Mutex
	total  int64
	slices [ticksToKeep]meterBucket
}

// NewMeter constructs a Meter
func NewMeter() *Meter {
	rv := &Meter{}
	now := monotime.Monotonic()
	for i := 0; i < ticksToKeep; i++ {
		rv.slices[i].start = now
	}
	defaultTicker.register(rv)
	return rv
}

// SetTotal sets the initial total count of the meter.
func (e *Meter) SetTotal(total int64) {
	e.mtx.Lock()
	e.total = total
	e.mtx.Unlock()
}

// Mark marks amount events occurring in the current time window.
func (e *Meter) Mark(amount int) {
	e.mtx.Lock()
	e.slices[ticksToKeep-1].count += int64(amount)
	e.mtx.Unlock()
}

func (e *Meter) tick(now time.Duration) {
	e.mtx.Lock()
	// only advance meter buckets if something happened. otherwise
	// rare events will always just have zero rates.
	if e.slices[ticksToKeep-1].count != 0 {
		e.total += e.slices[0].count
		copy(e.slices[:], e.slices[1:])
		e.slices[ticksToKeep-1] = meterBucket{count: 0, start: now}
	}
	e.mtx.Unlock()
}

func (e *Meter) stats(now time.Duration) (rate float64, total int64) {
	current := int64(0)
	e.mtx.Lock()
	start := e.slices[0].start
	for i := 0; i < ticksToKeep; i++ {
		current += e.slices[i].count
	}
	total = e.total
	e.mtx.Unlock()
	total += current
	duration := (now - start).Seconds()
	if duration > 0 {
		rate = float64(current) / duration
	} else {
		rate = 0
	}
	return rate, total
}

// Rate returns the rate over the internal sliding window
func (e *Meter) Rate() float64 {
	rate, _ := e.stats(monotime.Monotonic())
	return rate
}

// Total returns the total over the internal sliding window
func (e *Meter) Total() float64 {
	_, total := e.stats(monotime.Monotonic())
	return float64(total)
}

// Stats implements the StatSource interface
func (e *Meter) Stats(cb func(name string, val float64)) {
	rate, total := e.stats(monotime.Monotonic())
	cb("rate", rate)
	cb("total", float64(total))
}

// DiffMeter is a StatSource that shows the difference between
// the rates of two meters. Expected usage like:
//
//   var (
//     mon = monkit.Package()
//     herps = mon.Meter("herps")
//     derps = mon.Meter("derps")
//     herpToDerp = mon.DiffMeter("herp_to_derp", herps, derps)
//   )
//
type DiffMeter struct {
	meter1, meter2 *Meter
}

// Constructs a DiffMeter.
func NewDiffMeter(meter1, meter2 *Meter) *DiffMeter {
	return &DiffMeter{meter1: meter1, meter2: meter2}
}

// Stats implements the StatSource interface
func (m *DiffMeter) Stats(cb func(name string, val float64)) {
	now := monotime.Monotonic()
	rate1, total1 := m.meter1.stats(now)
	rate2, total2 := m.meter2.stats(now)
	cb("rate", rate1-rate2)
	cb("total", float64(total1-total2))
}

type ticker struct {
	mtx     sync.Mutex
	started bool
	meters  []*Meter
}

func (t *ticker) register(m *Meter) {
	t.mtx.Lock()
	if !t.started {
		t.started = true
		go t.run()
	}
	t.meters = append(t.meters, m)
	t.mtx.Unlock()
}

func (t *ticker) run() {
	for {
		time.Sleep(timePerTick)
		t.mtx.Lock()
		meters := t.meters // this is safe since we only use append
		t.mtx.Unlock()
		now := monotime.Monotonic()
		for _, m := range meters {
			m.tick(now)
		}
	}
}
<commit_msg>meter: adds Mark64 method for int64 input<commit_after>// Copyright (C) 2015 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package monkit

import (
	"sync"
	"time"

	"github.com/spacemonkeygo/monotime"
)

const (
	ticksToKeep = 24
	timePerTick = 10 * time.Minute
)

var (
	defaultTicker = ticker{}
)

type meterBucket struct {
	count int64
	start time.Duration
}

// Meter keeps track of events and their rates over time.
// Implements the StatSource interface. You should construct using NewMeter,
// though expected usage is like:
//
//   var (
//     mon   = monkit.Package()
//     meter = mon.Meter("meter")
//   )
//
//   func MyFunc() {
//     ...
//     meter.Mark(4) // 4 things happened
//     ...
//   }
//
type Meter struct {
	mtx    sync.Mutex
	total  int64
	slices [ticksToKeep]meterBucket
}

// NewMeter constructs a Meter
func NewMeter() *Meter {
	rv := &Meter{}
	now := monotime.Monotonic()
	for i := 0; i < ticksToKeep; i++ {
		rv.slices[i].start = now
	}
	defaultTicker.register(rv)
	return rv
}

// SetTotal sets the initial total count of the meter.
func (e *Meter) SetTotal(total int64) {
	e.mtx.Lock()
	e.total = total
	e.mtx.Unlock()
}

// Mark marks amount events occurring in the current time window.
func (e *Meter) Mark(amount int) {
	e.mtx.Lock()
	e.slices[ticksToKeep-1].count += int64(amount)
	e.mtx.Unlock()
}

// Mark64 marks amount events occurring in the current time window (int64 version).
func (e *Meter) Mark64(amount int64) {
	e.mtx.Lock()
	e.slices[ticksToKeep-1].count += amount
	e.mtx.Unlock()
}

func (e *Meter) tick(now time.Duration) {
	e.mtx.Lock()
	// only advance meter buckets if something happened. otherwise
	// rare events will always just have zero rates.
	if e.slices[ticksToKeep-1].count != 0 {
		e.total += e.slices[0].count
		copy(e.slices[:], e.slices[1:])
		e.slices[ticksToKeep-1] = meterBucket{count: 0, start: now}
	}
	e.mtx.Unlock()
}

func (e *Meter) stats(now time.Duration) (rate float64, total int64) {
	current := int64(0)
	e.mtx.Lock()
	start := e.slices[0].start
	for i := 0; i < ticksToKeep; i++ {
		current += e.slices[i].count
	}
	total = e.total
	e.mtx.Unlock()
	total += current
	duration := (now - start).Seconds()
	if duration > 0 {
		rate = float64(current) / duration
	} else {
		rate = 0
	}
	return rate, total
}

// Rate returns the rate over the internal sliding window
func (e *Meter) Rate() float64 {
	rate, _ := e.stats(monotime.Monotonic())
	return rate
}

// Total returns the total over the internal sliding window
func (e *Meter) Total() float64 {
	_, total := e.stats(monotime.Monotonic())
	return float64(total)
}

// Stats implements the StatSource interface
func (e *Meter) Stats(cb func(name string, val float64)) {
	rate, total := e.stats(monotime.Monotonic())
	cb("rate", rate)
	cb("total", float64(total))
}

// DiffMeter is a StatSource that shows the difference between
// the rates of two meters. Expected usage like:
//
//   var (
//     mon = monkit.Package()
//     herps = mon.Meter("herps")
//     derps = mon.Meter("derps")
//     herpToDerp = mon.DiffMeter("herp_to_derp", herps, derps)
//   )
//
type DiffMeter struct {
	meter1, meter2 *Meter
}

// Constructs a DiffMeter.
func NewDiffMeter(meter1, meter2 *Meter) *DiffMeter {
	return &DiffMeter{meter1: meter1, meter2: meter2}
}

// Stats implements the StatSource interface
func (m *DiffMeter) Stats(cb func(name string, val float64)) {
	now := monotime.Monotonic()
	rate1, total1 := m.meter1.stats(now)
	rate2, total2 := m.meter2.stats(now)
	cb("rate", rate1-rate2)
	cb("total", float64(total1-total2))
}

type ticker struct {
	mtx     sync.Mutex
	started bool
	meters  []*Meter
}

func (t *ticker) register(m *Meter) {
	t.mtx.Lock()
	if !t.started {
		t.started = true
		go t.run()
	}
	t.meters = append(t.meters, m)
	t.mtx.Unlock()
}

func (t *ticker) run() {
	for {
		time.Sleep(timePerTick)
		t.mtx.Lock()
		meters := t.meters // this is safe since we only use append
		t.mtx.Unlock()
		now := monotime.Monotonic()
		for _, m := range meters {
			m.tick(now)
		}
	}
}
<|endoftext|>


<commit_before>package gangliamr

import (
	"github.com/daaku/go.ganglia/gmetric"
	"github.com/daaku/go.metrics"
)

// Meters count events to produce exponentially-weighted moving average rates
// at one-, five-, and fifteen-minutes and a mean rate.
type Meter struct {
	metrics.Meter
	Name        string // Required.
	Title       string
	Units       string // Default is "count".
	Description string
	Groups      []string
	base        *meterBase
}

func (m *Meter) writeMeta(c *gmetric.Client) {
	m.base.writeMeta(c)
}

func (m *Meter) writeValue(c *gmetric.Client) {
	m.base.writeValue(c)
}

func (m *Meter) register(r *Registry) {
	if m.Meter == nil {
		m.Meter = metrics.NewMeter()
	}
	m.base = &meterBase{
		meterMetric: m,
		Name:        m.Name,
		Title:       m.Title,
		Units:       m.Units,
		Description: m.Description,
		Groups:      m.Groups,
	}
	m.base.register(r)
}

type meterMetric interface {
	Count() int64
	Rate1() float64
	Rate5() float64
	Rate15() float64
	RateMean() float64
}

type meterBase struct {
	meterMetric
	Name        string // Required.
	Title       string
	Units       string // Default is "count".
	Description string
	Groups      []string
	count       gmetric.Metric
	m1rate      gmetric.Metric
	m5rate      gmetric.Metric
	m15rate     gmetric.Metric
	meanRate    gmetric.Metric
}

func (m *meterBase) writeMeta(c *gmetric.Client) {
	c.WriteMeta(&m.count)
	c.WriteMeta(&m.m1rate)
	c.WriteMeta(&m.m5rate)
	c.WriteMeta(&m.m15rate)
	c.WriteMeta(&m.meanRate)
}

func (m *meterBase) writeValue(c *gmetric.Client) {
	c.WriteValue(&m.count, m.Count())
	c.WriteValue(&m.m1rate, m.Rate1())
	c.WriteValue(&m.m5rate, m.Rate5())
	c.WriteValue(&m.m15rate, m.Rate15())
	c.WriteValue(&m.meanRate, m.RateMean())
}

func (m *meterBase) register(r *Registry) {
	m.count = gmetric.Metric{
		Name:        r.makeName(m.Name, "count"),
		Title:       m.Title,
		Units:       "count",
		Description: makeOptional(m.Description, "count"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueInt32,
		Slope:       gmetric.SlopeBoth,
	}
	m.m1rate = gmetric.Metric{
		Name:        r.makeName(m.Name, "one-minute"),
		Title:       makeOptional(m.Title, "one minute"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "one minute"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
	m.m5rate = gmetric.Metric{
		Name:        r.makeName(m.Name, "five-minute"),
		Title:       makeOptional(m.Title, "five minute"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "five minute"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
	m.m15rate = gmetric.Metric{
		Name:        r.makeName(m.Name, "fifteen-minute"),
		Title:       makeOptional(m.Title, "fifteen minute"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "fifteen minute"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
	m.meanRate = gmetric.Metric{
		Name:        r.makeName(m.Name, "mean"),
		Title:       makeOptional(m.Title, "mean"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "mean"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
}
<commit_msg>remove now unnecessary meterBase<commit_after>package gangliamr

import (
	"github.com/daaku/go.ganglia/gmetric"
	"github.com/daaku/go.metrics"
)

// Meters count events to produce exponentially-weighted moving average rates
// at one-, five-, and fifteen-minutes and a mean rate.
type Meter struct {
	metrics.Meter
	Name        string // Required.
	Title       string
	Units       string // Default is "count".
	Description string
	Groups      []string
	count       gmetric.Metric
	m1rate      gmetric.Metric
	m5rate      gmetric.Metric
	m15rate     gmetric.Metric
	meanRate    gmetric.Metric
}

func (m *Meter) writeMeta(c *gmetric.Client) {
	c.WriteMeta(&m.count)
	c.WriteMeta(&m.m1rate)
	c.WriteMeta(&m.m5rate)
	c.WriteMeta(&m.m15rate)
	c.WriteMeta(&m.meanRate)
}

func (m *Meter) writeValue(c *gmetric.Client) {
	c.WriteValue(&m.count, m.Count())
	c.WriteValue(&m.m1rate, m.Rate1())
	c.WriteValue(&m.m5rate, m.Rate5())
	c.WriteValue(&m.m15rate, m.Rate15())
	c.WriteValue(&m.meanRate, m.RateMean())
}

func (m *Meter) register(r *Registry) {
	if m.Meter == nil {
		m.Meter = metrics.NewMeter()
	}
	m.count = gmetric.Metric{
		Name:        r.makeName(m.Name, "count"),
		Title:       m.Title,
		Units:       "count",
		Description: makeOptional(m.Description, "count"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueInt32,
		Slope:       gmetric.SlopeBoth,
	}
	m.m1rate = gmetric.Metric{
		Name:        r.makeName(m.Name, "one-minute"),
		Title:       makeOptional(m.Title, "one minute"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "one minute"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
	m.m5rate = gmetric.Metric{
		Name:        r.makeName(m.Name, "five-minute"),
		Title:       makeOptional(m.Title, "five minute"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "five minute"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
	m.m15rate = gmetric.Metric{
		Name:        r.makeName(m.Name, "fifteen-minute"),
		Title:       makeOptional(m.Title, "fifteen minute"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "fifteen minute"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
	m.meanRate = gmetric.Metric{
		Name:        r.makeName(m.Name, "mean"),
		Title:       makeOptional(m.Title, "mean"),
		Units:       nonEmpty(m.Units, "count"),
		Description: makeOptional(m.Description, "mean"),
		Groups:      m.Groups,
		ValueType:   gmetric.ValueFloat64,
		Slope:       gmetric.SlopeBoth,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	pubsub "google.golang.org/api/pubsub/v1"

	"github.com/urfave/cli"
)

type CliActions struct {
}

func (c *CliActions) configFlag() cli.StringFlag {
	return cli.StringFlag{
		Name:  "config, c",
		Usage: "`FILE` to load configuration",
		Value: "./config.json",
	}
}

func (act *CliActions) MainFlags() []cli.Flag {
	return []cli.Flag{
		act.configFlag(),
	}
}

func (act *CliActions) Main(c *cli.Context) error {
	config := act.LoadAndSetupProcessConfig(c)
	p := act.newProcess(config)

	err := p.run()
	if err != nil {
		fmt.Printf("Error to run cause of %v\n", err)
		os.Exit(1)
	}
	return nil
}

func (act *CliActions) CheckCommand() cli.Command {
	return cli.Command{
		Name:  "check",
		Usage: "Check config file is valid",
		Action: act.Check,
		Flags: []cli.Flag{
			act.configFlag(),
		},
	}
}

func (act *CliActions) Check(c *cli.Context) error {
	act.LoadAndSetupProcessConfig(c)
	fmt.Println("OK")
	return nil
}

func (act *CliActions) DownloadCommand() cli.Command {
	return cli.Command{
		Name:  "download",
		Usage: "Download the files from GCS to downloads directory",
		Action: act.Download,
		Flags: []cli.Flag{
			act.configFlag(),
			cli.StringFlag{
				Name:  "downloads_dir, d",
				Usage: "`PATH` to the directory which has bucket_name/path/to/file",
			},
			cli.IntFlag{
				Name:  "workers, n",
				Usage: "`NUMBER` of workers",
				Value: 5,
			},
			cli.IntFlag{
				Name:  "max_tries, m",
				Usage: "`NUMBER` of max tries",
				Value: 3,
			},
			cli.IntFlag{
				Name:  "wait, w",
				Usage: "`NUMBER` of seconds to wait",
				Value: 0,
			},
		},
	}
}

func (act *CliActions) Download(c *cli.Context) error {
	config = act.LoadAndSetupProcessConfig(c)
	config.Download.Workers = c.Int("workers")
	config.Download.MaxTries = c.Int("max_tries")
	config.Job.Sustainer = &JobSustainerConfig{
		Disabled: true,
	}
	p := act.newProcess(config)
	files := []interface{}{}
	for _, arg := range c.Args() {
		files = append(files, arg)
	}
	job := &Job{
		config:              config.Command,
		downloads_dir:       c.String("downloads_dir"),
		remoteDownloadFiles: files,
		storage:             p.storage,
		downloadConfig:      config.Download,
	}
	err := job.setupDownloadFiles()
	if err != nil {
		return err
	}
	err = job.downloadFiles()

	w := c.Int("wait")
	if w > 0 {
		time.Sleep(time.Duration(w) * time.Second)
	}

	return nil
}

func (act *CliActions) UploadCommand() cli.Command {
	return cli.Command{
		Name:  "upload",
		Usage: "Upload the files under uploads directory",
		Action: act.Upload,
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "uploads_dir, d",
				Usage: "Path to the directory which has bucket_name/path/to/file",
			},
			cli.IntFlag{
				Name:  "uploaders, n",
				Usage: "Number of uploaders",
				Value: 6,
			},
		},
	}
}

func (act *CliActions) Upload(c *cli.Context) error {
	fmt.Printf("Uploading files\n")
	config := &ProcessConfig{}
	config.Log = &LogConfig{Level: "debug"}
	config.setup([]string{})
	config.Upload.Workers = c.Int("uploaders")
	config.Job.Sustainer = &JobSustainerConfig{
		Disabled: true,
	}
	p := act.newProcess(config)
	p.setup()
	job := &Job{
		config:      config.Command,
		uploads_dir: c.String("uploads_dir"),
		storage:     p.storage,
	}
	fmt.Printf("Uploading files under %v\n", job.uploads_dir)
	err := job.uploadFiles()
	return err
}

func (act *CliActions) ExecCommand() cli.Command {
	return cli.Command{
		Name:  "exec",
		Usage: "Execute job without download nor upload",
		Action: act.Exec,
		Flags: []cli.Flag{
			act.configFlag(),
			cli.StringFlag{
				Name:  "message, m",
				Usage: "Path to the message json file which has attributes and data",
			},
			cli.StringFlag{
				Name:  "workspace, w",
				Usage: "Path to workspace directory which has downloads and uploads",
			},
		},
	}
}

func (act *CliActions) Exec(c *cli.Context) error {
	config := act.LoadAndSetupProcessConfig(c)

	msg_file := c.String("message")
	workspace := c.String("workspace")

	type Msg struct {
		Attributes  map[string]string `json:"attributes"`
		Data        string            `json:"data"`
		MessageId   string            `json:"messageId"`
		PublishTime string            `json:"publishTime"`
		AckId       string            `json:"ackId"`
	}
	var msg Msg

	data, err := ioutil.ReadFile(msg_file)
	if err != nil {
		fmt.Printf("Error to read file %v because of %v\n", msg_file, err)
		os.Exit(1)
	}

	err = json.Unmarshal(data, &msg)
	if err != nil {
		fmt.Printf("Error to parse json file %v because of %v\n", msg_file, err)
		os.Exit(1)
	}

	job := &Job{
		workspace: workspace,
		config:    config.Command,
		message: &JobMessage{
			raw: &pubsub.ReceivedMessage{
				AckId: msg.AckId,
				Message: &pubsub.PubsubMessage{
					Attributes: msg.Attributes,
					Data:       msg.Data,
					MessageId:  msg.MessageId,
					// PublishTime: time.Now().Format(time.RFC3339),
					PublishTime: msg.PublishTime,
				},
			},
		},
	}
	fmt.Printf("Preparing job\n")
	err = job.prepare()
	if err != nil {
		return err
	}
	fmt.Printf("Executing job\n")
	err = job.execute()
	return err
}

func (act *CliActions) LoadAndSetupProcessConfig(c *cli.Context) *ProcessConfig {
	path := c.String("config")
	config, err := LoadProcessConfig(path)
	if err != nil {
		fmt.Printf("Error to load %v cause of %v\n", path, err)
		os.Exit(1)
	}
	err = config.setup(c.Args())
	if err != nil {
		fmt.Printf("Error to setup %v cause of %v\n", path, err)
		os.Exit(1)
	}
	return config
}

func (act *CliActions) newProcess(config *ProcessConfig) *Process {
	p := &Process{config: config}
	err := p.setup()
	if err != nil {
		fmt.Printf("Error to setup Process cause of %v\n", err)
		os.Exit(1)
	}
	return p
}
<commit_msg>:recycle: Extract #LoadAndSetupProcessConfigWith from #LoadAndSetupProcessConfig<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	pubsub "google.golang.org/api/pubsub/v1"

	"github.com/urfave/cli"
)

type CliActions struct {
}

func (c *CliActions) configFlag() cli.StringFlag {
	return cli.StringFlag{
		Name:  "config, c",
		Usage: "`FILE` to load configuration",
		Value: "./config.json",
	}
}

func (act *CliActions) MainFlags() []cli.Flag {
	return []cli.Flag{
		act.configFlag(),
	}
}

func (act *CliActions) Main(c *cli.Context) error {
	config := act.LoadAndSetupProcessConfig(c)
	p := act.newProcess(config)

	err := p.run()
	if err != nil {
		fmt.Printf("Error to run cause of %v\n", err)
		os.Exit(1)
	}
	return nil
}

func (act *CliActions) CheckCommand() cli.Command {
	return cli.Command{
		Name:  "check",
		Usage: "Check config file is valid",
		Action: act.Check,
		Flags: []cli.Flag{
			act.configFlag(),
		},
	}
}

func (act *CliActions) Check(c *cli.Context) error {
	act.LoadAndSetupProcessConfig(c)
	fmt.Println("OK")
	return nil
}

func (act *CliActions) DownloadCommand() cli.Command {
	return cli.Command{
		Name:  "download",
		Usage: "Download the files from GCS to downloads directory",
		Action: act.Download,
		Flags: []cli.Flag{
			act.configFlag(),
			cli.StringFlag{
				Name:  "downloads_dir, d",
				Usage: "`PATH` to the directory which has bucket_name/path/to/file",
			},
			cli.IntFlag{
				Name:  "workers, n",
				Usage: "`NUMBER` of workers",
				Value: 5,
			},
			cli.IntFlag{
				Name:  "max_tries, m",
				Usage: "`NUMBER` of max tries",
				Value: 3,
			},
			cli.IntFlag{
				Name:  "wait, w",
				Usage: "`NUMBER` of seconds to wait",
				Value: 0,
			},
		},
	}
}

func (act *CliActions) Download(c *cli.Context) error {
	config := act.LoadAndSetupProcessConfigWith(c, func(cfg *ProcessConfig) error {
		cfg.Download.Workers = c.Int("workers")
		cfg.Download.MaxTries = c.Int("max_tries")
		cfg.Job.Sustainer = &JobSustainerConfig{
			Disabled: true,
		}
		return nil
	})
	p := act.newProcess(config)
	files := []interface{}{}
	for _, arg := range c.Args() {
		files = append(files, arg)
	}
	job := &Job{
		config:              config.Command,
		downloads_dir:       c.String("downloads_dir"),
		remoteDownloadFiles: files,
		storage:             p.storage,
		downloadConfig:      config.Download,
	}
	err := job.setupDownloadFiles()
	if err != nil {
		return err
	}
	err = job.downloadFiles()

	w := c.Int("wait")
	if w > 0 {
		time.Sleep(time.Duration(w) * time.Second)
	}

	return nil
}

func (act *CliActions) UploadCommand() cli.Command {
	return cli.Command{
		Name:  "upload",
		Usage: "Upload the files under uploads directory",
		Action: act.Upload,
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "uploads_dir, d",
				Usage: "Path to the directory which has bucket_name/path/to/file",
			},
			cli.IntFlag{
				Name:  "uploaders, n",
				Usage: "Number of uploaders",
				Value: 6,
			},
		},
	}
}

func (act *CliActions) Upload(c *cli.Context) error {
	fmt.Printf("Uploading files\n")
	config := &ProcessConfig{}
	config.Log = &LogConfig{Level: "debug"}
	config.setup([]string{})
	config.Upload.Workers = c.Int("uploaders")
	config.Job.Sustainer = &JobSustainerConfig{
		Disabled: true,
	}
	p := act.newProcess(config)
	p.setup()
	job := &Job{
		config:      config.Command,
		uploads_dir: c.String("uploads_dir"),
		storage:     p.storage,
	}
	fmt.Printf("Uploading files under %v\n", job.uploads_dir)
	err := job.uploadFiles()
	return err
}

func (act *CliActions) ExecCommand() cli.Command {
	return cli.Command{
		Name:  "exec",
		Usage: "Execute job without download nor upload",
		Action: act.Exec,
		Flags: []cli.Flag{
			act.configFlag(),
			cli.StringFlag{
				Name:  "message, m",
				Usage: "Path to the message json file which has attributes and data",
			},
			cli.StringFlag{
				Name:  "workspace, w",
				Usage: "Path to workspace directory which has downloads and uploads",
			},
		},
	}
}

func (act *CliActions) Exec(c *cli.Context) error {
	config := act.LoadAndSetupProcessConfig(c)

	msg_file := c.String("message")
	workspace := c.String("workspace")

	type Msg struct {
		Attributes  map[string]string `json:"attributes"`
		Data        string            `json:"data"`
		MessageId   string            `json:"messageId"`
		PublishTime string            `json:"publishTime"`
		AckId       string            `json:"ackId"`
	}
	var msg Msg

	data, err := ioutil.ReadFile(msg_file)
	if err != nil {
		fmt.Printf("Error to read file %v because of %v\n", msg_file, err)
		os.Exit(1)
	}

	err = json.Unmarshal(data, &msg)
	if err != nil {
		fmt.Printf("Error to parse json file %v because of %v\n", msg_file, err)
		os.Exit(1)
	}

	job := &Job{
		workspace: workspace,
		config:    config.Command,
		message: &JobMessage{
			raw: &pubsub.ReceivedMessage{
				AckId: msg.AckId,
				Message: &pubsub.PubsubMessage{
					Attributes: msg.Attributes,
					Data:       msg.Data,
					MessageId:  msg.MessageId,
					// PublishTime: time.Now().Format(time.RFC3339),
					PublishTime: msg.PublishTime,
				},
			},
		},
	}
	fmt.Printf("Preparing job\n")
	err = job.prepare()
	if err != nil {
		return err
	}
	fmt.Printf("Executing job\n")
	err = job.execute()
	return err
}

func (act *CliActions) LoadAndSetupProcessConfig(c *cli.Context) *ProcessConfig {
	return act.LoadAndSetupProcessConfigWith(c, func(_ *ProcessConfig) error{ return nil})
}

func (act *CliActions) LoadAndSetupProcessConfigWith(c *cli.Context, callback func(*ProcessConfig) error) *ProcessConfig {
	path := c.String("config")
	config, err := LoadProcessConfig(path)
	if err != nil {
		fmt.Printf("Error to load %v cause of %v\n", path, err)
		os.Exit(1)
	}
	err = config.setup(c.Args())
	if err != nil {
		fmt.Printf("Error to setup %v cause of %v\n", path, err)
		os.Exit(1)
	}
	err = callback(config)
	if err != nil {
		fmt.Printf("Error to callback on setup %v cause of %v\n", path, err)
		os.Exit(1)
	}
	return config
}

func (act *CliActions) newProcess(config *ProcessConfig) *Process {
	p := &Process{config: config}
	err := p.setup()
	if err != nil {
		fmt.Printf("Error to setup Process cause of %v\n", err)
		os.Exit(1)
	}
	return p
}
<|endoftext|>


<commit_before>// Clinet is linux daemon to collect logs and send it to LogVoyage service.
// If service is down Client will write to file and try to send them again
// when service is up.
//
// Client can accept messages in two ways: by tcp and http interface
package main

import (
	"errors"
	"flag"
	"github.com/firstrow/logvoyage/tcp_server"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
)

var (
	logVoyageConnection net.Conn
	emptyStringError    = errors.New("Received empty string")
)

func main() {
	httpDsn := flag.String("httpHost", "localhost:27078", "Host and port to start local HTTP server.")
	tcpDsn := flag.String("tcpDsn", "localhost:27079", "Host and port to start local TCP server.")
	logVoyageDsn := flag.String("logVoyage", "localhost:27077", "LogVoyage server host and port.")

	flag.Parse()

	// Connect to to LogVoyage server
	conn, err := net.Dial("tcp", *logVoyageDsn)
	if err != nil {
		log.Printf("Error connection to server: %s", err)
	}
	// Set package variable
	logVoyageConnection = conn
	defer conn.Close()

	go startHttpServer(*httpDsn)
	startTcpServer(*tcpDsn)
}

func httpHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("Received new http request")
	bytes, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print("Error reading http request body")
	} else {
		send(bytes)
	}
}

func startHttpServer(httpDsn string) {
	log.Printf("Starting http server at %s", httpDsn)
	http.HandleFunc("/", httpHandler)
	http.ListenAndServe(httpDsn, nil)
}

func startTcpServer(tcpDsn string) {
	log.Printf("Starting tcp server at %s", tcpDsn)
	server := tcp_server.NewServer(tcpDsn)
	server.OnNewClient(func(c *tcp_server.Client) {})

	server.OnNewMessage(func(c *tcp_server.Client, message string) {
		b := []byte(message)
		send(b)
	})

	server.OnClientConnectionClosed(func(c *tcp_server.Client, err error) {})
	server.Listen()
}

// Sends message to LogVoyage server
func send(message []byte) {
	text, err := prepareMessage(string(message))
	if err == nil {
		// TODO: Handle write.
		// Create restore-log.
		_, err := logVoyageConnection.Write([]byte(text))
		if err != nil {
			log.Print("Connection with LogVoyage server lost. Will try again after 10 sec.")
		}
	}
}

// Trims message and adds \n to end
func prepareMessage(message string) (string, error) {
	result := strings.TrimSpace(message)
	if len(result) > 0 {
		return result + "\n", nil
	}
	return "", emptyStringError
}
<commit_msg>Added client reconnect<commit_after>// Clinet is linux daemon to collect logs and send it to LogVoyage service.
// If service is down Client will write to file and try to send them again
// when service is up.
//
// Client can accept messages in two ways: by tcp and http interface
package main

import (
	"errors"
	"flag"
	"github.com/firstrow/logvoyage/tcp_server"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
	"time"
)

var (
	logVoyageConnection net.Conn
	emptyStringError    = errors.New("Received empty string")
)

func main() {
	httpDsn := flag.String("httpHost", "localhost:27078", "Host and port to start local HTTP server.")
	tcpDsn := flag.String("tcpDsn", "localhost:27079", "Host and port to start local TCP server.")
	logVoyageDsn := flag.String("logVoyage", "localhost:27077", "LogVoyage server host and port.")

	flag.Parse()
	initialize(*httpDsn, *tcpDsn, *logVoyageDsn)
}

func initialize(httpDsn string, tcpDsn string, logVoyageDsn string) {
	// Connect to to LogVoyage server
	conn, err := net.Dial("tcp", logVoyageDsn)
	if err != nil {
		log.Print("Error connecting to LogVoyage server. Will retry in 10 seconds.")
		time.Sleep(10 * time.Second)
		initialize(httpDsn, tcpDsn, logVoyageDsn)
		return
	}
	// Set package variable
	logVoyageConnection = conn
	defer conn.Close()

	go startHttpServer(httpDsn)
	startTcpServer(tcpDsn)
}

func httpHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("Received new http request")
	bytes, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print("Error reading http request body")
	} else {
		send(bytes)
	}
}

func startHttpServer(httpDsn string) {
	log.Printf("Starting http server at %s", httpDsn)
	http.HandleFunc("/", httpHandler)
	http.ListenAndServe(httpDsn, nil)
}

func startTcpServer(tcpDsn string) {
	log.Printf("Starting tcp server at %s", tcpDsn)
	server := tcp_server.NewServer(tcpDsn)
	server.OnNewClient(func(c *tcp_server.Client) {})

	server.OnNewMessage(func(c *tcp_server.Client, message string) {
		b := []byte(message)
		go send(b)
	})

	server.OnClientConnectionClosed(func(c *tcp_server.Client, err error) {})
	server.Listen()
}

// Sends message to LogVoyage server
func send(message []byte) {
	text, err := prepareMessage(string(message))
	if err == nil {
		// TODO: Handle write.
		// Create restore-log.
		_, err := logVoyageConnection.Write([]byte(text))
		if err != nil {
			log.Print("Connection with LogVoyage server lost. Will try again after 10 sec.")
		}
	}
}

// Trims message and adds \n to end
func prepareMessage(message string) (string, error) {
	result := strings.TrimSpace(message)
	if len(result) > 0 {
		return result + "\n", nil
	}
	return "", emptyStringError
}
<|endoftext|>


<commit_before>package stun

import (
	"io"
	"sync"
	"testing"
	"time"
)

type TestAgent struct {
	f chan AgentFn
}

func (n *TestAgent) Close() error {
	close(n.f)
	return nil
}

func (TestAgent) Collect(time.Time) error { return nil }

func (TestAgent) Process(m *Message) error { return nil }

func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error {
	n.f <- f
	return nil
}

func (n *TestAgent) Stop([TransactionIDSize]byte) error {
	return nil
}

type noopConnection struct{}

func (noopConnection) Write(b []byte) (int, error) {
	return len(b), nil
}

func (noopConnection) Read(b []byte) (int, error) {
	time.Sleep(time.Millisecond)
	return 0, io.EOF
}

func (noopConnection) Close() error {
	return nil
}

func BenchmarkClient_Do(b *testing.B) {
	b.ReportAllocs()
	agent := &TestAgent{
		f: make(chan AgentFn),
	}
	client := NewClient(ClientOptions{
		Agent:      agent,
		Connection: noopConnection{},
	})
	defer client.Close()
	go func() {
		e := AgentEvent{
			Error:   nil,
			Message: nil,
		}
		for f := range agent.f {
			f(e)
		}
	}()
	m := new(Message)
	m.Encode()
	noopF := func(event AgentEvent) {
		// pass
	}
	for i := 0; i < b.N; i++ {
		if err := client.Do(m, time.Time{}, noopF); err != nil {
			b.Fatal(err)
		}
	}
}

type testConnection struct {
	write   func([]byte) (int, error)
	b       []byte
	l       sync.Mutex
	stopped bool
}

func (t *testConnection) Write(b []byte) (int, error) {
	t.l.Unlock()
	return t.write(b)
}

func (t *testConnection) Close() error {
	t.stopped = true
	t.l.Unlock()
	return nil
}

func (t *testConnection) Read(b []byte) (int, error) {
	t.l.Lock()
	if t.stopped {
		return 0, io.EOF
	}
	return copy(b, t.b), nil
}

func TestClosedOrPanic(t *testing.T) {
	closedOrPanic(nil)
	closedOrPanic(ErrAgentClosed)
	func() {
		defer func() {
			r := recover()
			if r != io.EOF {
				t.Error(r)
			}
		}()
		closedOrPanic(io.EOF)
	}()
}

func TestClient_Do(t *testing.T) {
	response := MustBuild(TransactionID, BindingSuccess)
	response.Encode()
	conn := &testConnection{
		b: response.Raw,
		write: func(bytes []byte) (int, error) {
			return len(bytes), nil
		},
	}
	conn.l.Lock()
	c := NewClient(ClientOptions{
		Connection: conn,
	})
	defer func() {
		if err := c.Close(); err != nil {
			t.Error(err)
		}
		if err := c.Close(); err == nil {
			t.Error("second close should fail")
		}
	}()
	m := new(Message)
	m.TransactionID = response.TransactionID
	m.Encode()
	d := time.Now().Add(time.Second)
	if err := c.Do(m, d, func(event AgentEvent) {
		if event.Error != nil {
			t.Error(event.Error)
		}
	}); err != nil {
		t.Error(err)
	}
}

func TestCloseErr_Error(t *testing.T) {
	for id, c := range []struct {
		Err CloseErr
		Out string
	}{
		{CloseErr{}, "failed to close: <nil> (connection), <nil> (agent)"},
		{CloseErr{
			AgentErr: io.ErrUnexpectedEOF,
		}, "failed to close: <nil> (connection), unexpected EOF (agent)"},
		{CloseErr{
			ConnectionErr: io.ErrUnexpectedEOF,
		}, "failed to close: unexpected EOF (connection), <nil> (agent)"},
	}{
		if out := c.Err.Error(); out != c.Out {
			t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)",
				id, c.Err, out, c.Out,
			)
		}
	}
}

func TestStopErr_Error(t *testing.T) {
	for id, c := range []struct {
		Err StopErr
		Out string
	}{
		{StopErr{}, "error while stopping due to <nil>: <nil>"},
		{StopErr{
			Err: io.ErrUnexpectedEOF,
		}, "error while stopping due to <nil>: unexpected EOF"},
		{StopErr{
			Cause: io.ErrUnexpectedEOF,
		}, "error while stopping due to unexpected EOF: <nil>"},
	}{
		if out := c.Err.Error(); out != c.Out {
			t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)",
				id, c.Err, out, c.Out,
			)
		}
	}
}<commit_msg>client: gofmt -s tests<commit_after>package stun

import (
	"io"
	"sync"
	"testing"
	"time"
)

type TestAgent struct {
	f chan AgentFn
}

func (n *TestAgent) Close() error {
	close(n.f)
	return nil
}

func (TestAgent) Collect(time.Time) error { return nil }

func (TestAgent) Process(m *Message) error { return nil }

func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error {
	n.f <- f
	return nil
}

func (n *TestAgent) Stop([TransactionIDSize]byte) error {
	return nil
}

type noopConnection struct{}

func (noopConnection) Write(b []byte) (int, error) {
	return len(b), nil
}

func (noopConnection) Read(b []byte) (int, error) {
	time.Sleep(time.Millisecond)
	return 0, io.EOF
}

func (noopConnection) Close() error {
	return nil
}

func BenchmarkClient_Do(b *testing.B) {
	b.ReportAllocs()
	agent := &TestAgent{
		f: make(chan AgentFn),
	}
	client := NewClient(ClientOptions{
		Agent:      agent,
		Connection: noopConnection{},
	})
	defer client.Close()
	go func() {
		e := AgentEvent{
			Error:   nil,
			Message: nil,
		}
		for f := range agent.f {
			f(e)
		}
	}()
	m := new(Message)
	m.Encode()
	noopF := func(event AgentEvent) {
		// pass
	}
	for i := 0; i < b.N; i++ {
		if err := client.Do(m, time.Time{}, noopF); err != nil {
			b.Fatal(err)
		}
	}
}

type testConnection struct {
	write   func([]byte) (int, error)
	b       []byte
	l       sync.Mutex
	stopped bool
}

func (t *testConnection) Write(b []byte) (int, error) {
	t.l.Unlock()
	return t.write(b)
}

func (t *testConnection) Close() error {
	t.stopped = true
	t.l.Unlock()
	return nil
}

func (t *testConnection) Read(b []byte) (int, error) {
	t.l.Lock()
	if t.stopped {
		return 0, io.EOF
	}
	return copy(b, t.b), nil
}

func TestClosedOrPanic(t *testing.T) {
	closedOrPanic(nil)
	closedOrPanic(ErrAgentClosed)
	func() {
		defer func() {
			r := recover()
			if r != io.EOF {
				t.Error(r)
			}
		}()
		closedOrPanic(io.EOF)
	}()
}

func TestClient_Do(t *testing.T) {
	response := MustBuild(TransactionID, BindingSuccess)
	response.Encode()
	conn := &testConnection{
		b: response.Raw,
		write: func(bytes []byte) (int, error) {
			return len(bytes), nil
		},
	}
	conn.l.Lock()
	c := NewClient(ClientOptions{
		Connection: conn,
	})
	defer func() {
		if err := c.Close(); err != nil {
			t.Error(err)
		}
		if err := c.Close(); err == nil {
			t.Error("second close should fail")
		}
	}()
	m := new(Message)
	m.TransactionID = response.TransactionID
	m.Encode()
	d := time.Now().Add(time.Second)
	if err := c.Do(m, d, func(event AgentEvent) {
		if event.Error != nil {
			t.Error(event.Error)
		}
	}); err != nil {
		t.Error(err)
	}
}

func TestCloseErr_Error(t *testing.T) {
	for id, c := range []struct {
		Err CloseErr
		Out string
	}{
		{CloseErr{}, "failed to close: <nil> (connection), <nil> (agent)"},
		{CloseErr{
			AgentErr: io.ErrUnexpectedEOF,
		}, "failed to close: <nil> (connection), unexpected EOF (agent)"},
		{CloseErr{
			ConnectionErr: io.ErrUnexpectedEOF,
		}, "failed to close: unexpected EOF (connection), <nil> (agent)"},
	} {
		if out := c.Err.Error(); out != c.Out {
			t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)",
				id, c.Err, out, c.Out,
			)
		}
	}
}

func TestStopErr_Error(t *testing.T) {
	for id, c := range []struct {
		Err StopErr
		Out string
	}{
		{StopErr{}, "error while stopping due to <nil>: <nil>"},
		{StopErr{
			Err: io.ErrUnexpectedEOF,
		}, "error while stopping due to <nil>: unexpected EOF"},
		{StopErr{
			Cause: io.ErrUnexpectedEOF,
		}, "error while stopping due to unexpected EOF: <nil>"},
	} {
		if out := c.Err.Error(); out != c.Out {
			t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)",
				id, c.Err, out, c.Out,
			)
		}
	}
}
<|endoftext|>