<commit_before>package main

import (
	"fmt"
	"log"
	"net"
	"runtime"
	"strings"

	"golang.org/x/net/ipv4" // "code.google.com/p/go.net/ipv4" // https://code.google.com/p/go/source/checkout?repo=net

	"rib-old/iface"
	"rib-old/route"
)

/*
TODO: Fetch interfaces names on windows:

C:\>netsh interface ipv4 show interfaces

Idx     Met         MTU          State                Name
---  ----------  ----------  ------------  ---------------------------
  1          50  4294967295  connected     Loopback Pseudo-Interface 1
 14          20        1500  connected     Local Area Connection
 10           5        1400  disconnected  Local Area Connection* 22
 19          20        1500  connected     VirtualBox Host-Only Network
*/

func localAddresses() {
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Print(fmt.Errorf("localAddresses: %v\n", err.Error()))
		return
	}
	for _, i := range ifaces {
		addrs, err := iface.GetInterfaceAddrs(i)
		if err != nil {
			log.Print(fmt.Errorf("localAddresses: %v\n", err.Error()))
			continue
		}
		for _, a := range addrs {
			log.Printf("index=%v iface=%v addr=[%v]\n", i.Index, i.Name, a)
		}
	}
}

func sendPrompt(out chan string, status int, paging bool) {

	if paging {
		out <- "\r\nENTER=more q=quit>"
		return
	}

	host := "hostname"
	var p string
	switch status {
	case USER:
		p = " login:"
	case PASS:
		host = ""
		p = "password:"
	case EXEC:
		p = ">"
	case ENAB:
		p = "#"
	case CONF:
		p = "(conf)#"
	case QUIT:
		return // no prompt
	default:
		p = "?"
	}

	// can't use send() since sendQueue() runs before sendPrompt().
	// output is flushed by caller
	out <- fmt.Sprintf("\r\n%s%s ", host, p)
}

func cmdQuit(root *CmdNode, c *TelnetClient, line string) {
	//c.userOut <- fmt.Sprintf("bye\r\n")
	sendNow(c, "\r\nbye\r\n")
	c.status = QUIT

	/*
		https://groups.google.com/d/msg/golang-nuts/JB_iiSQkmOk/dJNKSFQXUUQJ

		There is nothing wrong with having arbitrary numbers of senders, but if
		you do then it doesn't work to close the channel.  You need some other
		way to indicate EOF.

		Ian Lance Taylor
	*/
	//close(c.quit)

	log.Printf("cmdQuit: requesting intputLoop to quit")
	c.quitInput <- 1 // inputLoop will signal outputLoop to quit
}

func list(node *CmdNode, c *TelnetClient, depth int) {
	handler := "----"
	if node.Handler != nil {
		handler = "LEAF"
	}
	ident := strings.Repeat(" ", 4*depth)
	//c.userOut <- fmt.Sprintf("%s %d %s[%s] desc=[%s]\r\n", handler, node.MinLevel, ident, node.Path, node.Desc)
	sendln(c, fmt.Sprintf("%s %d %s[%s] desc=[%s]", handler, node.MinLevel, ident, node.Path, node.Desc))
	for _, n := range node.Children {
		list(n, c, depth+1)
	}
}

func cmdList(root *CmdNode, c *TelnetClient, line string) {
	list(root, c, 0)
}

func cmdReload(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowInt(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowIPAddr(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowIPInt(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowIPRoute(root *CmdNode, c *TelnetClient, line string) {
}

func execute(root *CmdNode, c *TelnetClient, line string) {
	//log.Printf("execute: [%v]", line)
	//c.userOut <- fmt.Sprintf("echo: [%v]\r\n", line)

	if line == "" {
		return
	}

	/*
		if strings.HasPrefix("list", line) {
			cmdList(root, c, line)
			return
		}

		if strings.HasPrefix("quit", line) {
			cmdQuit(root, c, line)
			return
		}
	*/

	node, err := cmdFind(root, line, c.status)
	if err != nil {
		//c.userOut <- fmt.Sprintf("command not found: %s\r\n", err)
		sendln(c, fmt.Sprintf("command not found: %s", err))
		return
	}

	if node.Handler == nil {
		sendln(c, fmt.Sprintf("command missing handler: [%s]", line))
		//c.userOut <- fmt.Sprintf("command missing handler: [%s]\r\n", line)
		return
	}

	if node.MinLevel > c.status {
		//c.userOut <- fmt.Sprintf("command level prohibited: [%s]\r\n", line)
		sendln(c, fmt.Sprintf("command level prohibited: [%s]", line))
		return
	}

	node.Handler(root, c, line)
}

func command(root *CmdNode, c *TelnetClient, line string) {
	log.Printf("rib command(): [%v]", line)

	switch c.status {
	case MOTD:
		// hello banner
		//c.userOut <- fmt.Sprintf("\r\nrib server ready\r\n")
		sendln(c, "")
		sendln(c, "rib server ready")
		c.status = USER
	case USER:
		c.echo <- false
		c.status = PASS
	case PASS:
		c.echo <- true
		c.status = EXEC
	case EXEC, ENAB, CONF:
		execute(root, c, line)
		if c.status == QUIT {
			// do not write anything to outputLoop, since it is no longer reading
			return
		}
	default:
		msg := fmt.Sprintf("unknown state for command: [%s]", line)
		log.Print(msg)
		//c.userOut <- msg
		sendln(c, msg)
	}

	paging := sendQueue(c)

	c.sendLine <- !paging

	sendPrompt(c.userOut, c.status, paging)

	flush(c)

	log.Printf("rib command(): flushed [%v]", line)
}

func keyCommand(root *CmdNode, c *TelnetClient, line string) {
	log.Printf("rib keyCommand(): [%v]", line)

	if line == "q" {
		// discard output queue
		c.outputQueue = c.outputQueue[:0]
	}

	c.userOut <- "\r\n"

	paging := sendQueue(c)

	log.Printf("rib keyCommand(): queue sent [%v]", line)

	c.sendLine <- !paging

	log.Printf("rib keyCommand(): send line mode sent [%v]", line)

	sendPrompt(c.userOut, c.status, paging)

	log.Printf("rib keyCommand(): prompt sent [%v]", line)

	flush(c)

	log.Printf("rib keyCommand(): flushed [%v]", line)
}

func sendQueue(c *TelnetClient) bool {
	sent := 0
	max := c.autoHeight - 2
	if max < 1 {
		max = 1
	}
	for i, m := range c.outputQueue {
		if i >= max {
			break
		}
		c.userOut <- m
		c.outputQueue[i] = ""
		sent++
	}
	//c.outputQueue = c.outputQueue[:0]
	log.Printf("sendQueue: total=%d sent=%d pending=%d height=%d", len(c.outputQueue), sent, len(c.outputQueue)-sent, c.autoHeight)
	c.outputQueue = c.outputQueue[sent:]
	return len(c.outputQueue) > 0
}

func send(c *TelnetClient, line string) {
	c.outputQueue = append(c.outputQueue, line)
}

func sendln(c *TelnetClient, line string) {
	send(c, fmt.Sprintf("%s\r\n", line))
}

func main() {
	log.Printf("runtime operating system: [%v]", runtime.GOOS)
	log.Printf("CPUs: NumCPU=%d GOMAXPROCS=%d", runtime.NumCPU(), runtime.GOMAXPROCS(0))

	log.Printf("IP version: %v", ipv4.Version)

	cmdRoot := CmdNode{Path: "", MinLevel: EXEC, Handler: nil}

	cmdInstall(&cmdRoot, "list", EXEC, cmdList, "List command tree")
	cmdInstall(&cmdRoot, "quit", EXEC, cmdQuit, "Quit session")
	cmdInstall(&cmdRoot, "reload", ENAB, cmdReload, "Reload")
	cmdInstall(&cmdRoot, "reload", ENAB, cmdReload, "Ugh") // duplicated command
	cmdInstall(&cmdRoot, "show interface", EXEC, cmdShowInt, "Show interfaces")
	cmdInstall(&cmdRoot, "show", EXEC, cmdShowInt, "Ugh") // duplicated command
	cmdInstall(&cmdRoot, "show ip address", EXEC, cmdShowIPAddr, "Show addresses")
	cmdInstall(&cmdRoot, "show ip interface", EXEC, cmdShowIPInt, "Show interfaces")
	cmdInstall(&cmdRoot, "show ip interface detail", EXEC, cmdShowIPInt, "Show interface detail")
	cmdInstall(&cmdRoot, "show ip route", EXEC, cmdShowIPRoute, "Show routing table")

	go listenTelnet(":1234")

	localAddresses()

	routeAdd, routeDel := route.Routes()

LOOP:
	for {
		log.Printf("rib main: ready")
		select {
		case r, ok := <-routeAdd:
			if !ok {
				log.Printf("rib main: quit requested from Routes()")
				break LOOP
			}
			log.Printf("rib main: route add: %v", r)
		case r := <-routeDel:
			log.Printf("rib main: route del: %v", r)
		case cmd := <-cmdInput:
			//log.Printf("rib main: command: len=%d [%s]", len(cmd.line), cmd.line)
			command(&cmdRoot, cmd.client, cmd.line)
		case cmd := <-keyInput:
			log.Printf("rib main: key command: len=%d [%s]", len(cmd.line), cmd.line)
			keyCommand(&cmdRoot, cmd.client, cmd.line)
		case c := <-inputClosed:
			// inputLoop hit closed connection. it's finished.
			// we should discard pending output (if any) and request
			// termination of input/output loops.
			log.Printf("rib main: inputLoop hit closed connection, requesting input/output to quit")
			c.quitInput <- 1  // request inputLoop to quit
			c.quitOutput <- 1 // request outputLoop to quit
		case s := <-autoHeight:
			s.client.autoHeight = s.height
			log.Printf("rib main: auto height=%d", s.client.autoHeight)
		}
	}
}
<commit_msg>Unused import.<commit_after>package main

import (
	"fmt"
	"log"
	"net"
	"runtime"
	"strings"

	//"golang.org/x/net/ipv4" // "code.google.com/p/go.net/ipv4" // https://code.google.com/p/go/source/checkout?repo=net

	"rib-old/iface"
	"rib-old/route"
)

/*
TODO: Fetch interfaces names on windows:

C:\>netsh interface ipv4 show interfaces

Idx     Met         MTU          State                Name
---  ----------  ----------  ------------  ---------------------------
  1          50  4294967295  connected     Loopback Pseudo-Interface 1
 14          20        1500  connected     Local Area Connection
 10           5        1400  disconnected  Local Area Connection* 22
 19          20        1500  connected     VirtualBox Host-Only Network
*/

func localAddresses() {
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Print(fmt.Errorf("localAddresses: %v\n", err.Error()))
		return
	}
	for _, i := range ifaces {
		addrs, err := iface.GetInterfaceAddrs(i)
		if err != nil {
			log.Print(fmt.Errorf("localAddresses: %v\n", err.Error()))
			continue
		}
		for _, a := range addrs {
			log.Printf("index=%v iface=%v addr=[%v]\n", i.Index, i.Name, a)
		}
	}
}

func sendPrompt(out chan string, status int, paging bool) {

	if paging {
		out <- "\r\nENTER=more q=quit>"
		return
	}

	host := "hostname"
	var p string
	switch status {
	case USER:
		p = " login:"
	case PASS:
		host = ""
		p = "password:"
	case EXEC:
		p = ">"
	case ENAB:
		p = "#"
	case CONF:
		p = "(conf)#"
	case QUIT:
		return // no prompt
	default:
		p = "?"
	}

	// can't use send() since sendQueue() runs before sendPrompt().
	// output is flushed by caller
	out <- fmt.Sprintf("\r\n%s%s ", host, p)
}

func cmdQuit(root *CmdNode, c *TelnetClient, line string) {
	//c.userOut <- fmt.Sprintf("bye\r\n")
	sendNow(c, "\r\nbye\r\n")
	c.status = QUIT

	/*
		https://groups.google.com/d/msg/golang-nuts/JB_iiSQkmOk/dJNKSFQXUUQJ

		There is nothing wrong with having arbitrary numbers of senders, but if
		you do then it doesn't work to close the channel.  You need some other
		way to indicate EOF.

		Ian Lance Taylor
	*/
	//close(c.quit)

	log.Printf("cmdQuit: requesting intputLoop to quit")
	c.quitInput <- 1 // inputLoop will signal outputLoop to quit
}

func list(node *CmdNode, c *TelnetClient, depth int) {
	handler := "----"
	if node.Handler != nil {
		handler = "LEAF"
	}
	ident := strings.Repeat(" ", 4*depth)
	//c.userOut <- fmt.Sprintf("%s %d %s[%s] desc=[%s]\r\n", handler, node.MinLevel, ident, node.Path, node.Desc)
	sendln(c, fmt.Sprintf("%s %d %s[%s] desc=[%s]", handler, node.MinLevel, ident, node.Path, node.Desc))
	for _, n := range node.Children {
		list(n, c, depth+1)
	}
}

func cmdList(root *CmdNode, c *TelnetClient, line string) {
	list(root, c, 0)
}

func cmdReload(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowInt(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowIPAddr(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowIPInt(root *CmdNode, c *TelnetClient, line string) {
}

func cmdShowIPRoute(root *CmdNode, c *TelnetClient, line string) {
}

func execute(root *CmdNode, c *TelnetClient, line string) {
	//log.Printf("execute: [%v]", line)
	//c.userOut <- fmt.Sprintf("echo: [%v]\r\n", line)

	if line == "" {
		return
	}

	/*
		if strings.HasPrefix("list", line) {
			cmdList(root, c, line)
			return
		}

		if strings.HasPrefix("quit", line) {
			cmdQuit(root, c, line)
			return
		}
	*/

	node, err := cmdFind(root, line, c.status)
	if err != nil {
		//c.userOut <- fmt.Sprintf("command not found: %s\r\n", err)
		sendln(c, fmt.Sprintf("command not found: %s", err))
		return
	}

	if node.Handler == nil {
		sendln(c, fmt.Sprintf("command missing handler: [%s]", line))
		//c.userOut <- fmt.Sprintf("command missing handler: [%s]\r\n", line)
		return
	}

	if node.MinLevel > c.status {
		//c.userOut <- fmt.Sprintf("command level prohibited: [%s]\r\n", line)
		sendln(c, fmt.Sprintf("command level prohibited: [%s]", line))
		return
	}

	node.Handler(root, c, line)
}

func command(root *CmdNode, c *TelnetClient, line string) {
	log.Printf("rib command(): [%v]", line)

	switch c.status {
	case MOTD:
		// hello banner
		//c.userOut <- fmt.Sprintf("\r\nrib server ready\r\n")
		sendln(c, "")
		sendln(c, "rib server ready")
		c.status = USER
	case USER:
		c.echo <- false
		c.status = PASS
	case PASS:
		c.echo <- true
		c.status = EXEC
	case EXEC, ENAB, CONF:
		execute(root, c, line)
		if c.status == QUIT {
			// do not write anything to outputLoop, since it is no longer reading
			return
		}
	default:
		msg := fmt.Sprintf("unknown state for command: [%s]", line)
		log.Print(msg)
		//c.userOut <- msg
		sendln(c, msg)
	}

	paging := sendQueue(c)

	c.sendLine <- !paging

	sendPrompt(c.userOut, c.status, paging)

	flush(c)

	log.Printf("rib command(): flushed [%v]", line)
}

func keyCommand(root *CmdNode, c *TelnetClient, line string) {
	log.Printf("rib keyCommand(): [%v]", line)

	if line == "q" {
		// discard output queue
		c.outputQueue = c.outputQueue[:0]
	}

	c.userOut <- "\r\n"

	paging := sendQueue(c)

	log.Printf("rib keyCommand(): queue sent [%v]", line)

	c.sendLine <- !paging

	log.Printf("rib keyCommand(): send line mode sent [%v]", line)

	sendPrompt(c.userOut, c.status, paging)

	log.Printf("rib keyCommand(): prompt sent [%v]", line)

	flush(c)

	log.Printf("rib keyCommand(): flushed [%v]", line)
}

func sendQueue(c *TelnetClient) bool {
	sent := 0
	max := c.autoHeight - 2
	if max < 1 {
		max = 1
	}
	for i, m := range c.outputQueue {
		if i >= max {
			break
		}
		c.userOut <- m
		c.outputQueue[i] = ""
		sent++
	}
	//c.outputQueue = c.outputQueue[:0]
	log.Printf("sendQueue: total=%d sent=%d pending=%d height=%d", len(c.outputQueue), sent, len(c.outputQueue)-sent, c.autoHeight)
	c.outputQueue = c.outputQueue[sent:]
	return len(c.outputQueue) > 0
}

func send(c *TelnetClient, line string) {
	c.outputQueue = append(c.outputQueue, line)
}

func sendln(c *TelnetClient, line string) {
	send(c, fmt.Sprintf("%s\r\n", line))
}

func main() {
	log.Printf("runtime operating system: [%v]", runtime.GOOS)
	log.Printf("CPUs: NumCPU=%d GOMAXPROCS=%d", runtime.NumCPU(), runtime.GOMAXPROCS(0))

	//log.Printf("IP version: %v", ipv4.Version)

	cmdRoot := CmdNode{Path: "", MinLevel: EXEC, Handler: nil}

	cmdInstall(&cmdRoot, "list", EXEC, cmdList, "List command tree")
	cmdInstall(&cmdRoot, "quit", EXEC, cmdQuit, "Quit session")
	cmdInstall(&cmdRoot, "reload", ENAB, cmdReload, "Reload")
	cmdInstall(&cmdRoot, "reload", ENAB, cmdReload, "Ugh") // duplicated command
	cmdInstall(&cmdRoot, "show interface", EXEC, cmdShowInt, "Show interfaces")
	cmdInstall(&cmdRoot, "show", EXEC, cmdShowInt, "Ugh") // duplicated command
	cmdInstall(&cmdRoot, "show ip address", EXEC, cmdShowIPAddr, "Show addresses")
	cmdInstall(&cmdRoot, "show ip interface", EXEC, cmdShowIPInt, "Show interfaces")
	cmdInstall(&cmdRoot, "show ip interface detail", EXEC, cmdShowIPInt, "Show interface detail")
	cmdInstall(&cmdRoot, "show ip route", EXEC, cmdShowIPRoute, "Show routing table")

	go listenTelnet(":1234")

	localAddresses()

	routeAdd, routeDel := route.Routes()

LOOP:
	for {
		log.Printf("rib main: ready")
		select {
		case r, ok := <-routeAdd:
			if !ok {
				log.Printf("rib main: quit requested from Routes()")
				break LOOP
			}
			log.Printf("rib main: route add: %v", r)
		case r := <-routeDel:
			log.Printf("rib main: route del: %v", r)
		case cmd := <-cmdInput:
			//log.Printf("rib main: command: len=%d [%s]", len(cmd.line), cmd.line)
			command(&cmdRoot, cmd.client, cmd.line)
		case cmd := <-keyInput:
			log.Printf("rib main: key command: len=%d [%s]", len(cmd.line), cmd.line)
			keyCommand(&cmdRoot, cmd.client, cmd.line)
		case c := <-inputClosed:
			// inputLoop hit closed connection. it's finished.
			// we should discard pending output (if any) and request
			// termination of input/output loops.
			log.Printf("rib main: inputLoop hit closed connection, requesting input/output to quit")
			c.quitInput <- 1  // request inputLoop to quit
			c.quitOutput <- 1 // request outputLoop to quit
		case s := <-autoHeight:
			s.client.autoHeight = s.height
			log.Printf("rib main: auto height=%d", s.client.autoHeight)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"sort"
)

const (
	VERSION = "0.0.1"
)

// sort interface-typed arrays by first-class functions
type ByFn struct{
	elems []interface{}
	comp func(a, b interface{}) bool
}
func (c ByFn) Len() int { return len(c.elems)  }
func (c ByFn) Less(i, j int) bool { return c.comp(c.elems[i], c.elems[j]) }
func (c ByFn) Swap(i, j int) { c.elems[i], c.elems[j] = c.elems[j], c.elems[i] }

// generic api requests
func apiReq(meth string, url string) interface{} {
	client := &http.Client{}
	req, err := http.NewRequest(meth, url, nil)
	req.SetBasicAuth("x", os.Getenv("HEROKU_API_KEY"))
	req.Header.Add("User-Agent", fmt.Sprintf("hk/%s", VERSION))
	req.Header.Add("Accept", "application/json")
	res, err := client.Do(req)
		if err != nil {
		panic(err)
	}
	if (res.StatusCode == 401) {
		error("Unauthorized")
	}
	if (res.StatusCode == 403) {
		error("Unauthorized")
	}
	if (res.StatusCode != 200) {
		fmt.Printf("%v\n", res)
		error("Unexpected error")
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	var data interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		panic(err)
	}
	return data
}

// error formatting
func error(msg string) {
	fmt.Fprintf(os.Stderr, "Error: %s.\n", msg)
	os.Exit(1)
}

func unrecArg(arg string) {
	error(fmt.Sprintf("Unrecognized argument '%s'", arg))
}

func unrecCmd(cmd string) {
	error(fmt.Sprintf("'%s' is not an hk command. See 'hk help'", cmd))
}

// info formatting
func cmdHelp(usage string, desc string) {
	fmt.Printf("Usage: %s\n\n", usage)
	fmt.Printf("%s.\n", desc)
	os.Exit(0)
}

// commands
func envHelp() {
	cmdHelp("hk env -a <app>", "Show all config vars")
}

func env() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See hk help env")
	}
	appName := os.Args[3]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	config := data.(map[string]interface{})
	for k, v := range config {
		fmt.Printf("%s=%v\n", k, v)
	}
	os.Exit(0)
}

func getHelp() {
	cmdHelp("hk get -a <app> <key>", "Get the value of a config var")
}

func get() {
	if (len(os.Args) != 5) || (os.Args[2] != "-a") {
		error("Invalid usage. See hk help get")
	}
	appName := os.Args[3]
	key := os.Args[4]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	config := data.(map[string]string)
	value, found := config[key]
	if !found {
		error(fmt.Sprintf("No such key as '%s'", key))
	}
	fmt.Println(value)
	os.Exit(0)
}

func infoHelp() {
	cmdHelp("hk info -a <app>", "Show app info")
}

func info() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See hk help info")
	}
	appName := os.Args[3]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s", appName))
	info := data.(map[string]interface{})
	fmt.Printf("Name:     %s\n", info["name"])
	fmt.Printf("Owner:    %s\n", info["owner_email"])
	fmt.Printf("Stack:    %s\n", info["stack"])
	fmt.Printf("Git URL:  %s\n", info["git_url"])
	fmt.Printf("Web URL:  %s\n", info["web_url"])
	os.Exit(0)
}

func listHelp() {
	cmdHelp("hk list", "List accessible apps")
}

func list() {
	if len(os.Args) != 2 {
		unrecArg(os.Args[2])
	}
	data := apiReq("GET", "https://api.heroku.com/apps")
	apps := data.([]interface{})
	for i := range apps {
		app := apps[i].(map[string]interface{})
		fmt.Printf("%s\n", app["name"])
	}
	os.Exit(0)
}

func psHelp() {
	cmdHelp("hk ps -a <app>", "List app processes")
}

func ps() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See hk help ps")
	}
	appName := os.Args[3]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s/ps", appName))
	processes := data.([]interface{})
	sort.Sort(ByFn{
		processes,
		func(a, b interface{}) bool {
			p1 := a.(map[string]interface{})["process"].(string)
			p2 := b.(map[string]interface{})["process"].(string)
		  return p1 < p2
	  }})
	fmt.Printf("Process           State       Command\n")
	fmt.Printf("----------------  ----------  ------------------------\n")
	for i := range processes {
		process := processes[i].(map[string]interface{})
		fmt.Printf("%-16s  %-10s  %s\n", process["process"], process["state"], process["command"])
	}
	os.Exit(0)
}

func versionHelp() {
	cmdHelp("hk version", "Show hk client version")
}

func version() {
	if len(os.Args) != 2 {
	  unrecArg(os.Args[2])
  }
	fmt.Printf("%s\n", VERSION)
	os.Exit(0)
}

func help() {
	if len(os.Args) <= 2 {
		usage()
	} else {
		cmd := os.Args[2]
		switch cmd {
	  case "env":
		  envHelp()
	  case "get":
			getHelp()
		case "info":
			infoHelp()
		case "list":
		  listHelp()
	  case "ps":
		  psHelp()
		case "version":
			versionHelp()
		}
		unrecCmd(cmd)
	}
}

// top-level usage
func usage() {
	fmt.Printf("Usage: hk <command> [-a <app>] [command-specific-options]\n\n")
	fmt.Printf("Supported hk commands are:\n")
	fmt.Printf("  addons          List add-ons\n")
	fmt.Printf("  addons-add      Add an add-on\n")
	fmt.Printf("  addons-open     Open an add-on page\n")
	fmt.Printf("  addons-remove   Remove an add-on \n")
	fmt.Printf("  create          Create an app\n")
	fmt.Printf("  destroy         Destroy an app\n")
	fmt.Printf("  env             List config vars\n")
	fmt.Printf("  get             Get config var\n")
	fmt.Printf("  help            Show this help\n")
	fmt.Printf("  info            Show app info\n")
	fmt.Printf("  list            List apps\n")
	fmt.Printf("  login           Log in\n")
	fmt.Printf("  logout          Log out\n")
	fmt.Printf("  logs            Show logs\n")
	fmt.Printf("  open            Open app\n")
	fmt.Printf("  pg              List databases\n")
	fmt.Printf("  pg-info         Show database info\n")
	fmt.Printf("  pg-promote      Promote a database\n")
	fmt.Printf("  ps-psql         Open a psql database shell\n")
	fmt.Printf("  pg-wait         Await a database\n")
	fmt.Printf("  ps              List processes\n")
	fmt.Printf("  release         Show release info\n")
	fmt.Printf("  releases        List releases\n")
	fmt.Printf("  rename          Rename an app\n")
	fmt.Printf("  restart         Restart processes\n")
	fmt.Printf("  rollback        Rollback to a previous release\n")
	fmt.Printf("  run             Run a process\n")
	fmt.Printf("  set             Set config var\n")
	fmt.Printf("  scale           Scale processes\n")
	fmt.Printf("  run             Run a process\n")
	fmt.Printf("  stop            Stop a process\n")
	fmt.Printf("  token           Show auth token\n")
	fmt.Printf("  unset           Unset config vars\n")
	fmt.Printf("  version         Display version\n\n")
	fmt.Printf("See 'hk help <command>' for more information on a specific command.\n")
	os.Exit(0)
}

// entry point
func main() {
	if len(os.Args) <= 1 {
		usage()
	} else {
		cmd := os.Args[1]
		switch cmd {
		case "env":
			env()
		case "get":
			get()
		case "help":
			help()
		case "info":
			info()
		case "list":
			list()
		case "ps":
			ps()
		case "version":
			version()
		}
		unrecCmd(cmd)
	}
}
<commit_msg>consistent command display<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"sort"
)

const (
	VERSION = "0.0.1"
)

// sort interface-typed arrays by first-class functions
type ByFn struct{
	elems []interface{}
	comp func(a, b interface{}) bool
}
func (c ByFn) Len() int { return len(c.elems)  }
func (c ByFn) Less(i, j int) bool { return c.comp(c.elems[i], c.elems[j]) }
func (c ByFn) Swap(i, j int) { c.elems[i], c.elems[j] = c.elems[j], c.elems[i] }

// generic api requests
func apiReq(meth string, url string) interface{} {
	client := &http.Client{}
	req, err := http.NewRequest(meth, url, nil)
	req.SetBasicAuth("x", os.Getenv("HEROKU_API_KEY"))
	req.Header.Add("User-Agent", fmt.Sprintf("hk/%s", VERSION))
	req.Header.Add("Accept", "application/json")
	res, err := client.Do(req)
		if err != nil {
		panic(err)
	}
	if (res.StatusCode == 401) {
		error("Unauthorized")
	}
	if (res.StatusCode == 403) {
		error("Unauthorized")
	}
	if (res.StatusCode != 200) {
		fmt.Printf("%v\n", res)
		error("Unexpected error")
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	var data interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		panic(err)
	}
	return data
}

// error formatting
func error(msg string) {
	fmt.Fprintf(os.Stderr, "Error: %s.\n", msg)
	os.Exit(1)
}

func unrecArg(arg string) {
	error(fmt.Sprintf("Unrecognized argument '%s'", arg))
}

func unrecCmd(cmd string) {
	error(fmt.Sprintf("'%s' is not an hk command. See 'hk help'", cmd))
}

// info formatting
func cmdHelp(usage string, desc string) {
	fmt.Printf("Usage: %s\n\n", usage)
	fmt.Printf("%s.\n", desc)
	os.Exit(0)
}

// commands
func envHelp() {
	cmdHelp("hk env -a <app>", "Show all config vars")
}

func env() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help env'")
	}
	appName := os.Args[3]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	config := data.(map[string]interface{})
	for k, v := range config {
		fmt.Printf("%s=%v\n", k, v)
	}
	os.Exit(0)
}

func getHelp() {
	cmdHelp("hk get -a <app> <key>", "Get the value of a config var")
}

func get() {
	if (len(os.Args) != 5) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help get'")
	}
	appName := os.Args[3]
	key := os.Args[4]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	config := data.(map[string]interface{})
	value, found := config[key].(string)
	if !found {
		error(fmt.Sprintf("No such key as '%s'", key))
	}
	fmt.Println(value)
	os.Exit(0)
}

func infoHelp() {
	cmdHelp("hk info -a <app>", "Show app info")
}

func info() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help info'")
	}
	appName := os.Args[3]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s", appName))
	info := data.(map[string]interface{})
	fmt.Printf("Name:     %s\n", info["name"])
	fmt.Printf("Owner:    %s\n", info["owner_email"])
	fmt.Printf("Stack:    %s\n", info["stack"])
	fmt.Printf("Git URL:  %s\n", info["git_url"])
	fmt.Printf("Web URL:  %s\n", info["web_url"])
	os.Exit(0)
}

func listHelp() {
	cmdHelp("hk list", "List accessible apps")
}

func list() {
	if len(os.Args) != 2 {
		unrecArg(os.Args[2])
	}
	data := apiReq("GET", "https://api.heroku.com/apps")
	apps := data.([]interface{})
	for i := range apps {
		app := apps[i].(map[string]interface{})
		fmt.Printf("%s\n", app["name"])
	}
	os.Exit(0)
}

func psHelp() {
	cmdHelp("hk ps -a <app>", "List app processes")
}

func ps() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help ps'")
	}
	appName := os.Args[3]
	data := apiReq("GET", fmt.Sprintf("https://api.heroku.com/apps/%s/ps", appName))
	processes := data.([]interface{})
	sort.Sort(ByFn{
		processes,
		func(a, b interface{}) bool {
			p1 := a.(map[string]interface{})["process"].(string)
			p2 := b.(map[string]interface{})["process"].(string)
		  return p1 < p2
	  }})
	fmt.Printf("Process           State       Command\n")
	fmt.Printf("----------------  ----------  ------------------------\n")
	for i := range processes {
		process := processes[i].(map[string]interface{})
		fmt.Printf("%-16s  %-10s  %s\n", process["process"], process["state"], process["command"])
	}
	os.Exit(0)
}

func versionHelp() {
	cmdHelp("hk version", "Show hk client version")
}

func version() {
	if len(os.Args) != 2 {
	  unrecArg(os.Args[2])
  }
	fmt.Printf("%s\n", VERSION)
	os.Exit(0)
}

func help() {
	if len(os.Args) <= 2 {
		usage()
	} else {
		cmd := os.Args[2]
		switch cmd {
	  case "env":
		  envHelp()
	  case "get":
			getHelp()
		case "info":
			infoHelp()
		case "list":
		  listHelp()
	  case "ps":
		  psHelp()
		case "version":
			versionHelp()
		}
		unrecCmd(cmd)
	}
}

// top-level usage
func usage() {
	fmt.Printf("Usage: hk <command> [-a <app>] [command-specific-options]\n\n")
	fmt.Printf("Supported hk commands are:\n")
	fmt.Printf("  addons          List add-ons\n")
	fmt.Printf("  addons-add      Add an add-on\n")
	fmt.Printf("  addons-open     Open an add-on page\n")
	fmt.Printf("  addons-remove   Remove an add-on \n")
	fmt.Printf("  create          Create an app\n")
	fmt.Printf("  destroy         Destroy an app\n")
	fmt.Printf("  env             List config vars\n")
	fmt.Printf("  get             Get config var\n")
	fmt.Printf("  help            Show this help\n")
	fmt.Printf("  info            Show app info\n")
	fmt.Printf("  list            List apps\n")
	fmt.Printf("  login           Log in\n")
	fmt.Printf("  logout          Log out\n")
	fmt.Printf("  logs            Show logs\n")
	fmt.Printf("  open            Open app\n")
	fmt.Printf("  pg              List databases\n")
	fmt.Printf("  pg-info         Show database info\n")
	fmt.Printf("  pg-promote      Promote a database\n")
	fmt.Printf("  ps-psql         Open a psql database shell\n")
	fmt.Printf("  pg-wait         Await a database\n")
	fmt.Printf("  ps              List processes\n")
	fmt.Printf("  release         Show release info\n")
	fmt.Printf("  releases        List releases\n")
	fmt.Printf("  rename          Rename an app\n")
	fmt.Printf("  restart         Restart processes\n")
	fmt.Printf("  rollback        Rollback to a previous release\n")
	fmt.Printf("  run             Run a process\n")
	fmt.Printf("  set             Set config var\n")
	fmt.Printf("  scale           Scale processes\n")
	fmt.Printf("  run             Run a process\n")
	fmt.Printf("  stop            Stop a process\n")
	fmt.Printf("  token           Show auth token\n")
	fmt.Printf("  unset           Unset config vars\n")
	fmt.Printf("  version         Display version\n\n")
	fmt.Printf("See 'hk help <command>' for more information on a specific command.\n")
	os.Exit(0)
}

// entry point
func main() {
	if len(os.Args) <= 1 {
		usage()
	} else {
		cmd := os.Args[1]
		switch cmd {
		case "env":
			env()
		case "get":
			get()
		case "help":
			help()
		case "info":
			info()
		case "list":
			list()
		case "ps":
			ps()
		case "version":
			version()
		}
		unrecCmd(cmd)
	}
}
<|endoftext|>


<commit_before>package stream

import (
	"bufio"
	"io"
	"os"
)

// Cat emits each line from each named file in order. If no arguments
// are specified, Cat copies its input to its output.
func Cat(filenames ...string) Filter {
	return FilterFunc(func(arg Arg) error {
		if len(filenames) == 0 {
			for s := range arg.In {
				arg.Out <- s
			}
			return nil
		}
		for _, f := range filenames {
			file, err := os.Open(f)
			if err == nil {
				err = splitIntoLines(file, arg)
				file.Close()
			}
			if err != nil {
				return err
			}
		}
		return nil
	})
}

// WriteLines prints each input item s followed by a newline to
// writer; and in addition it emits s.  Therefore WriteLines()
// can be used like the "tee" command, which can often be useful
// for debugging.
func WriteLines(writer io.Writer) Filter {
	return FilterFunc(func(arg Arg) error {
		b := bufio.NewWriter(writer)
		for s := range arg.In {
			if _, err := b.Write([]byte(s)); err != nil {
				return err
			}
			if err := b.WriteByte('\n'); err != nil {
				return err
			}
			arg.Out <- s
			if err := b.Flush(); err != nil {
				return err
			}
		}
		return b.Flush()
	})
}

// ReadLines emits each line found in reader.
func ReadLines(reader io.Reader) Filter {
	return FilterFunc(func(arg Arg) error {
		return splitIntoLines(reader, arg)
	})
}

func splitIntoLines(rd io.Reader, arg Arg) error {
	scanner := bufio.NewScanner(rd)
	for scanner.Scan() {
		arg.Out <- scanner.Text()
	}
	return scanner.Err()
}
<commit_msg>Removed write buffer from WriteLines<commit_after>package stream

import (
	"bufio"
	"io"
	"os"
)

// Cat emits each line from each named file in order. If no arguments
// are specified, Cat copies its input to its output.
func Cat(filenames ...string) Filter {
	return FilterFunc(func(arg Arg) error {
		if len(filenames) == 0 {
			for s := range arg.In {
				arg.Out <- s
			}
			return nil
		}
		for _, f := range filenames {
			file, err := os.Open(f)
			if err == nil {
				err = splitIntoLines(file, arg)
				file.Close()
			}
			if err != nil {
				return err
			}
		}
		return nil
	})
}

// WriteLines prints each input item s followed by a newline to
// writer; and in addition it emits s.  Therefore WriteLines()
// can be used like the "tee" command, which can often be useful
// for debugging.
func WriteLines(writer io.Writer) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			if _, err := writer.Write(append([]byte(s),'\n')); err != nil {
				return err
			}
			arg.Out <- s
		}
		return nil
	})
}

// ReadLines emits each line found in reader.
func ReadLines(reader io.Reader) Filter {
	return FilterFunc(func(arg Arg) error {
		return splitIntoLines(reader, arg)
	})
}

func splitIntoLines(rd io.Reader, arg Arg) error {
	scanner := bufio.NewScanner(rd)
	for scanner.Scan() {
		arg.Out <- scanner.Text()
	}
	return scanner.Err()
}
<|endoftext|>


<commit_before>package dry

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"os"
)

type CountingReader struct {
	Reader    io.Reader
	BytesRead int
}

func (r *CountingReader) Read(p []byte) (n int, err error) {
	n, err = r.Reader.Read(p)
	r.BytesRead += n
	return n, err
}

type CountingWriter struct {
	Writer       io.Writer
	BytesWritten int
}

func (r *CountingWriter) Write(p []byte) (n int, err error) {
	n, err = r.Writer.Write(p)
	r.BytesWritten += n
	return n, err
}

type CountingReadWriter struct {
	ReadWriter   io.ReadWriter
	BytesRead    int
	BytesWritten int
}

func (rw *CountingReadWriter) Read(p []byte) (n int, err error) {
	n, err = rw.ReadWriter.Read(p)
	rw.BytesRead += n
	return n, err
}

func (rw *CountingReadWriter) Write(p []byte) (n int, err error) {
	n, err = rw.ReadWriter.Write(p)
	rw.BytesWritten += n
	return n, err
}

// ReadBinary wraps binary.Read with a CountingReader and returns
// the acutal bytes read by it.
func ReadBinary(r io.Reader, order binary.ByteOrder, data interface{}) (n int, err error) {
	countingReader := CountingReader{Reader: r}
	err = binary.Read(&countingReader, order, data)
	return countingReader.BytesRead, err
}

// WriteFull calls writer.Write until all of data is written,
// or an is error returned.
func WriteFull(data []byte, writer io.Writer) (n int, err error) {
	dataSize := len(data)
	for n = 0; n < dataSize; {
		m, err := writer.Write(data[n:])
		n += m
		if err != nil {
			return n, err
		}
	}
	return dataSize, nil
}

// ReadLine reads unbuffered until a newline '\n' byte and removes
// an optional carriege return '\r' at the end of the line.
// In case of an error, the string up to the error is returned.
func ReadLine(reader io.Reader) (line string, err error) {
	buffer := bytes.NewBuffer(make([]byte, 0, 4096))
	p := make([]byte, 1)
	for {
		var n int
		n, err = reader.Read(p)
		if err != nil || p[0] == '\n' {
			break
		}
		if n > 0 {
			buffer.Write(p)
		}
	}
	data := buffer.Bytes()
	if len(data) > 0 && data[len(data)-1] == '\r' {
		data = data[:len(data)-1]
	}
	return string(data), err
}

// WaitForStdin blocks until input is available from os.Stdin.
// The first byte from os.Stdin is returned as result.
// If there are println arguments, then fmt.Println will be
// called with those before reading from os.Stdin.
func WaitForStdin(println ...interface{}) byte {
	if len(println) > 0 {
		fmt.Println(println...)
	}
	buffer := make([]byte, 1)
	os.Stdin.Read(buffer)
	return buffer[0]
}
<commit_msg>optimize ReadLine<commit_after>package dry

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"os"
)

type CountingReader struct {
	Reader    io.Reader
	BytesRead int
}

func (r *CountingReader) Read(p []byte) (n int, err error) {
	n, err = r.Reader.Read(p)
	r.BytesRead += n
	return n, err
}

type CountingWriter struct {
	Writer       io.Writer
	BytesWritten int
}

func (r *CountingWriter) Write(p []byte) (n int, err error) {
	n, err = r.Writer.Write(p)
	r.BytesWritten += n
	return n, err
}

type CountingReadWriter struct {
	ReadWriter   io.ReadWriter
	BytesRead    int
	BytesWritten int
}

func (rw *CountingReadWriter) Read(p []byte) (n int, err error) {
	n, err = rw.ReadWriter.Read(p)
	rw.BytesRead += n
	return n, err
}

func (rw *CountingReadWriter) Write(p []byte) (n int, err error) {
	n, err = rw.ReadWriter.Write(p)
	rw.BytesWritten += n
	return n, err
}

// ReadBinary wraps binary.Read with a CountingReader and returns
// the acutal bytes read by it.
func ReadBinary(r io.Reader, order binary.ByteOrder, data interface{}) (n int, err error) {
	countingReader := CountingReader{Reader: r}
	err = binary.Read(&countingReader, order, data)
	return countingReader.BytesRead, err
}

// WriteFull calls writer.Write until all of data is written,
// or an is error returned.
func WriteFull(data []byte, writer io.Writer) (n int, err error) {
	dataSize := len(data)
	for n = 0; n < dataSize; {
		m, err := writer.Write(data[n:])
		n += m
		if err != nil {
			return n, err
		}
	}
	return dataSize, nil
}

// ReadLine reads unbuffered until a newline '\n' byte and removes
// an optional carriege return '\r' at the end of the line.
// In case of an error, the string up to the error is returned.
func ReadLine(reader io.Reader) (line string, err error) {
	buffer := bytes.NewBuffer(make([]byte, 0, 4096))
	p := make([]byte, 1)
	for {
		var n int
		n, err = reader.Read(p)
		if err != nil || p[0] == '\n' {
			break
		}
		if n > 0 {
			buffer.WriteByte(p[0])
		}
	}
	data := buffer.Bytes()
	if len(data) > 0 && data[len(data)-1] == '\r' {
		data = data[:len(data)-1]
	}
	return string(data), err
}

// WaitForStdin blocks until input is available from os.Stdin.
// The first byte from os.Stdin is returned as result.
// If there are println arguments, then fmt.Println will be
// called with those before reading from os.Stdin.
func WaitForStdin(println ...interface{}) byte {
	if len(println) > 0 {
		fmt.Println(println...)
	}
	buffer := make([]byte, 1)
	os.Stdin.Read(buffer)
	return buffer[0]
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/wsxiaoys/terminal"
	"strings"
)

type consoleWriter struct {
	linesAfterLastStep int
	isInsideStep       bool
}

func newConsoleWriter() *consoleWriter {
	return &consoleWriter{linesAfterLastStep: 0, isInsideStep: false}
}

var currentConsoleWriter *consoleWriter

func getCurrentConsole() *consoleWriter {
	if currentConsoleWriter == nil {
		currentConsoleWriter = newConsoleWriter()
	}
	return currentConsoleWriter
}

func (writer *consoleWriter) Write(b []byte) (int, error) {
	message := string(b)
	if writer.isInsideStep {
		writer.linesAfterLastStep += strings.Count(message, "\n")
	}
	fmt.Print(message)
	return len(b), nil
}

func (writer *consoleWriter) writeString(value string) {
	writer.Write([]byte(value))
}

func (writer *consoleWriter) writeError(value string) {
	if writer.isInsideStep {
		writer.linesAfterLastStep += strings.Count(value, "\n")
	}
	terminal.Stdout.Colorf("@r%s", value)
}

func (writer *consoleWriter) writeSpecHeading(spec *specification) {
	formattedHeading := formatSpecHeading(spec.heading.value)
	writer.Write([]byte(formattedHeading))
}

func (writer *consoleWriter) writeItems(items []item) {
	for _, item := range items {
		writer.writeItem(item)
	}
}

func (writer *consoleWriter) writeItem(item item) {
	switch item.kind() {
	case commentKind:
		comment := item.(*comment)
		writer.writeComment(comment)
	case stepKind:
		step := item.(*step)
		writer.writeStep(step)
	case tableKind:
		table := item.(*table)
		writer.writeTable(table)
	}
}

func (writer *consoleWriter) writeComment(comment *comment) {
	terminal.Stdout.Colorf("@k%s\n\n", comment.value)
}

func (writer *consoleWriter) writeScenarioHeading(scenarioHeading string) {
	formattedHeading := formatScenarioHeading(scenarioHeading)
	writer.Write([]byte(formattedHeading))
}

func (writer *consoleWriter) writeStep(step *step) {
	terminal.Stdout.Colorf("@b%s", formatStep(step))
	writer.isInsideStep = true
	writer.linesAfterLastStep = 0
}

func (writer *consoleWriter) writeStepFinished(step *step, isPassed bool) {
	stepText := formatStep(step)
	linesInStepText := strings.Count(stepText, "\n")
	if linesInStepText == 0 {
		linesInStepText = 1
	}
	terminal.Stdout.Up(writer.linesAfterLastStep + linesInStepText)
	if isPassed {
		terminal.Stdout.Colorf("@g%s", stepText)
	} else {
		terminal.Stdout.Colorf("@r%s", stepText)
	}
	terminal.Stdout.Down(writer.linesAfterLastStep + linesInStepText)
	writer.isInsideStep = false
}

func (writer *consoleWriter) writeTable(table *table) {
	formattedTable := formatTable(table)
	terminal.Stdout.Colorf("@m%s", formattedTable)
}
<commit_msg>[console] Removing unwanted lines from comments in console message<commit_after>package main

import (
	"fmt"
	"github.com/wsxiaoys/terminal"
	"strings"
)

type consoleWriter struct {
	linesAfterLastStep int
	isInsideStep       bool
}

func newConsoleWriter() *consoleWriter {
	return &consoleWriter{linesAfterLastStep: 0, isInsideStep: false}
}

var currentConsoleWriter *consoleWriter

func getCurrentConsole() *consoleWriter {
	if currentConsoleWriter == nil {
		currentConsoleWriter = newConsoleWriter()
	}
	return currentConsoleWriter
}

func (writer *consoleWriter) Write(b []byte) (int, error) {
	message := string(b)
	if writer.isInsideStep {
		writer.linesAfterLastStep += strings.Count(message, "\n")
	}
	fmt.Print(message)
	return len(b), nil
}

func (writer *consoleWriter) writeString(value string) {
	writer.Write([]byte(value))
}

func (writer *consoleWriter) writeError(value string) {
	if writer.isInsideStep {
		writer.linesAfterLastStep += strings.Count(value, "\n")
	}
	terminal.Stdout.Colorf("@r%s", value)
}

func (writer *consoleWriter) writeSpecHeading(spec *specification) {
	formattedHeading := formatSpecHeading(spec.heading.value)
	writer.Write([]byte(formattedHeading))
}

func (writer *consoleWriter) writeItems(items []item) {
	for _, item := range items {
		writer.writeItem(item)
	}
}

func (writer *consoleWriter) writeItem(item item) {
	switch item.kind() {
	case commentKind:
		comment := item.(*comment)
		writer.writeComment(comment)
	case stepKind:
		step := item.(*step)
		writer.writeStep(step)
	case tableKind:
		table := item.(*table)
		writer.writeTable(table)
	}
}

func (writer *consoleWriter) writeComment(comment *comment) {
	terminal.Stdout.Colorf("@k%s", formatItem(comment))
}

func (writer *consoleWriter) writeScenarioHeading(scenarioHeading string) {
	formattedHeading := formatScenarioHeading(scenarioHeading)
	writer.Write([]byte(formattedHeading))
}

func (writer *consoleWriter) writeStep(step *step) {
	terminal.Stdout.Colorf("@b%s", formatStep(step))
	writer.isInsideStep = true
	writer.linesAfterLastStep = 0
}

func (writer *consoleWriter) writeStepFinished(step *step, isPassed bool) {
	stepText := formatStep(step)
	linesInStepText := strings.Count(stepText, "\n")
	if linesInStepText == 0 {
		linesInStepText = 1
	}
	terminal.Stdout.Up(writer.linesAfterLastStep + linesInStepText)
	if isPassed {
		terminal.Stdout.Colorf("@g%s", stepText)
	} else {
		terminal.Stdout.Colorf("@r%s", stepText)
	}
	terminal.Stdout.Down(writer.linesAfterLastStep + linesInStepText)
	writer.isInsideStep = false
}

func (writer *consoleWriter) writeTable(table *table) {
	formattedTable := formatTable(table)
	terminal.Stdout.Colorf("@m%s", formattedTable)
}
<|endoftext|>


<commit_before>// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

package main

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/la5nta/wl2k-go/transport"
	"github.com/la5nta/wl2k-go/transport/ardop"
	"github.com/la5nta/wl2k-go/transport/winmor"

	// Register other dialers
	_ "github.com/harenber/ptc-go/ptc"
	_ "github.com/la5nta/wl2k-go/transport/ax25"
	_ "github.com/la5nta/wl2k-go/transport/telnet"
)

var (
	wmTNC *winmor.TNC // Pointer to the WINMOR TNC used by Listen and Connect
	adTNC *ardop.TNC  // Pointer to the ARDOP TNC used by Listen and Connect
)

func hasSSID(str string) bool { return strings.Contains(str, "-") }

func connectAny(connectStr ...string) bool {
	for _, str := range connectStr {
		if Connect(str) {
			return true
		}
	}
	return false
}

func Connect(connectStr string) (success bool) {
	if connectStr == "" {
		return false
	} else if aliased, ok := config.ConnectAliases[connectStr]; ok {
		return Connect(aliased)
	}

	url, err := transport.ParseURL(connectStr)
	if err != nil {
		log.Println(err)
		return false
	}

	// Init TNCs
	switch url.Scheme {
	case "ardop":
		if err := initArdopTNC(); err != nil {
			log.Println(err)
			return
		}
	case "winmor":
		if err := initWinmorTNC(); err != nil {
			log.Println(err)
			return
		}
	}

	// Set default userinfo (mycall)
	if url.User == nil {
		url.SetUser(fOptions.MyCall)
	}

	// Set default host interface address
	if url.Host == "" {
		switch url.Scheme {
		case "ax25":
			url.Host = config.AX25.Port
		case "serial-tnc":
			url.Host = config.SerialTNC.Path
			if config.SerialTNC.Baudrate > 0 {
				url.Params.Set("hbaud", fmt.Sprint(config.SerialTNC.Baudrate))
			}
		case "pactor":
			url.Host = config.Pactor.Path
			if config.Pactor.Baudrate > 0 {
				url.Params.Set("baud", fmt.Sprint(config.Pactor.Baudrate))
			}
		}
	}

	if url.Scheme == "pactor" && config.Pactor.InitScript != "" && url.Params.Get("init_script") == "" {
		url.Params.Set("init_script", config.Pactor.InitScript)
	}

	// Radio Only?
	radioOnly := fOptions.RadioOnly
	if v := url.Params.Get("radio_only"); v != "" {
		radioOnly, _ = strconv.ParseBool(v)
	}
	if radioOnly {
		if hasSSID(fOptions.MyCall) {
			log.Println("Radio Only does not support callsign with SSID")
			return
		}

		switch url.Scheme {
		case "ax25", "serial-tnc":
			log.Printf("Radio-Only is not available for %s", url.Scheme)
			return
		default:
			url.SetUser(url.User.Username() + "-T")
		}
	}

	// QSY
	var revertFreq func()
	if freq := url.Params.Get("freq"); freq != "" {
		revertFreq, err = qsy(url.Scheme, freq)
		if err != nil {
			log.Printf("Unable to QSY: %s", err)
			return
		}
		defer revertFreq()
	}
	var currFreq Frequency
	if vfo, ok := VFOForTransport(url.Scheme); ok {
		f, _ := vfo.GetFreq()
		currFreq = Frequency(f)
	}

	// Wait for a clear channel
	switch url.Scheme {
	case "ardop":
		waitBusy(adTNC)
	case "winmor":
		waitBusy(wmTNC)
	}

	// Catch interrupts (signals) while dialing, so users can abort ardop/winmor connects.
	doneHandleInterrupt := handleInterrupt()

	log.Printf("Connecting to %s (%s)...", url.Target, url.Scheme)
	conn, err := transport.DialURL(url)

	close(doneHandleInterrupt)

	eventLog.LogConn("connect "+connectStr, currFreq, conn, err)

	if err != nil {
		log.Printf("Unable to establish connection to remote: %s", err)
		return
	}

	err = exchange(conn, url.Target, false)
	if err != nil {
		log.Printf("Exchange failed: %s", err)
	} else {
		log.Println("Disconnected.")
		success = true
	}

	return
}

func qsy(method, addr string) (revert func(), err error) {
	noop := func() {}

	var rigName string
	switch method {
	case MethodWinmor:
		rigName = config.Winmor.Rig
	case MethodArdop:
		rigName = config.Ardop.Rig
	case MethodAX25:
		rigName = config.AX25.Rig
	case MethodPactor:
		rigName = config.Pactor.Rig
	default:
		return noop, fmt.Errorf("Not supported with transport '%s'", method)
	}

	if rigName == "" {
		return noop, fmt.Errorf("Missing rig reference in config section for %s, don't know which rig to qsy", method)
	}

	var ok bool
	rig, ok := rigs[rigName]
	if !ok {
		return noop, fmt.Errorf("Hamlib rig '%s' not loaded.", rigName)
	}

	log.Printf("QSY %s: %s", method, addr)

	_, oldFreq, err := setFreq(rig, addr)
	if err != nil {
		return noop, err
	}

	time.Sleep(3 * time.Second)

	return func() {
		time.Sleep(time.Second)
		log.Printf("QSX %s: %.3f", method, float64(oldFreq)/1e3)
		rig.SetFreq(oldFreq)
	}, nil
}

func waitBusy(b transport.BusyChannelChecker) {
	printed := false

	for b.Busy() {
		if !printed && fOptions.IgnoreBusy {
			log.Println("Ignoring busy channel!")
			break
		} else if !printed {
			log.Println("Waiting for clear channel...")
			printed = true
		}
		time.Sleep(300 * time.Millisecond)
	}
}

func initWinmorTNC() error {
	if wmTNC != nil && wmTNC.Ping() == nil {
		return nil
	}

	if wmTNC != nil {
		wmTNC.Close()
	}

	var err error
	wmTNC, err = winmor.Open(config.Winmor.Addr, fOptions.MyCall, config.Locator)
	if err != nil {
		return fmt.Errorf("WINMOR TNC initialization failed: %s", err)
	}

	if config.Winmor.DriveLevel != 0 {
		if err := wmTNC.SetDriveLevel(config.Winmor.DriveLevel); err != nil {
			log.Println("Failed to set WINMOR drive level:", err)
		}
	}

	if v, err := wmTNC.Version(); err != nil {
		return fmt.Errorf("WINMOR TNC initialization failed: %s", err)
	} else {
		log.Printf("WINMOR TNC v%s initialized", v)
	}

	transport.RegisterDialer("winmor", wmTNC)

	if !config.Winmor.PTTControl {
		return nil
	}

	rig, ok := rigs[config.Winmor.Rig]
	if !ok {
		return fmt.Errorf("Unable to set PTT rig '%s': Not defined or not loaded.", config.Winmor.Rig)
	}
	wmTNC.SetPTT(rig)

	return nil
}

func initArdopTNC() error {
	if adTNC != nil && adTNC.Ping() == nil {
		return nil
	}

	if adTNC != nil {
		adTNC.Close()
	}

	var err error
	adTNC, err = ardop.OpenTCP(config.Ardop.Addr, fOptions.MyCall, config.Locator)
	if err != nil {
		return fmt.Errorf("ARDOP TNC initialization failed: %s", err)
	}

	if !config.Ardop.ARQBandwidth.IsZero() {
		if err := adTNC.SetARQBandwidth(config.Ardop.ARQBandwidth); err != nil {
			return fmt.Errorf("Unable to set ARQ bandwidth for ardop TNC: %s", err)
		}
	}

	if err := adTNC.SetCWID(config.Ardop.CWID); err != nil {
		return fmt.Errorf("Unable to configure CWID for ardop TNC: %s", err)
	}

	if v, err := adTNC.Version(); err != nil {
		return fmt.Errorf("ARDOP TNC initialization failed: %s", err)
	} else {
		log.Printf("ARDOP TNC (%s) initialized", v)
	}

	transport.RegisterDialer("ardop", adTNC)

	if !config.Ardop.PTTControl {
		return nil
	}

	rig, ok := rigs[config.Ardop.Rig]
	if !ok {
		return fmt.Errorf("Unable to set PTT rig '%s': Not defined or not loaded.", config.Ardop.Rig)
	}

	adTNC.SetPTT(rig)
	return nil
}
<commit_msg>Implement new initialisation call from refactored pactor driver<commit_after>// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

package main

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/la5nta/wl2k-go/transport"
	"github.com/la5nta/wl2k-go/transport/ardop"
	"github.com/la5nta/wl2k-go/transport/winmor"
	"github.com/harenber/ptc-go/ptc"

	// Register other dialers
	_ "github.com/la5nta/wl2k-go/transport/ax25"
	_ "github.com/la5nta/wl2k-go/transport/telnet"
)

var (
	wmTNC *winmor.TNC // Pointer to the WINMOR TNC used by Listen and Connect
	adTNC *ardop.TNC  // Pointer to the ARDOP TNC used by Listen and Connect
	pModem *pactor.Modem
)

func hasSSID(str string) bool { return strings.Contains(str, "-") }

func connectAny(connectStr ...string) bool {
	for _, str := range connectStr {
		if Connect(str) {
			return true
		}
	}
	return false
}

func Connect(connectStr string) (success bool) {
	if connectStr == "" {
		return false
	} else if aliased, ok := config.ConnectAliases[connectStr]; ok {
		return Connect(aliased)
	}

	url, err := transport.ParseURL(connectStr)
	if err != nil {
		log.Println(err)
		return false
	}

	// Init TNCs
	switch url.Scheme {
	case "ardop":
		if err := initArdopTNC(); err != nil {
			log.Println(err)
			return
		}
	case "winmor":
		if err := initWinmorTNC(); err != nil {
			log.Println(err)
			return
		}
	case "pactor":
		if err := initPactorModem(); err != nil {
			log.Println(err)
			return
		}
	}

	// Set default userinfo (mycall)
	if url.User == nil {
		url.SetUser(fOptions.MyCall)
	}

	// Set default host interface address
	if url.Host == "" {
		switch url.Scheme {
		case "ax25":
			url.Host = config.AX25.Port
		case "serial-tnc":
			url.Host = config.SerialTNC.Path
			if config.SerialTNC.Baudrate > 0 {
				url.Params.Set("hbaud", fmt.Sprint(config.SerialTNC.Baudrate))
			}
		}
	}

	// Radio Only?
	radioOnly := fOptions.RadioOnly
	if v := url.Params.Get("radio_only"); v != "" {
		radioOnly, _ = strconv.ParseBool(v)
	}
	if radioOnly {
		if hasSSID(fOptions.MyCall) {
			log.Println("Radio Only does not support callsign with SSID")
			return
		}

		switch url.Scheme {
		case "ax25", "serial-tnc":
			log.Printf("Radio-Only is not available for %s", url.Scheme)
			return
		default:
			url.SetUser(url.User.Username() + "-T")
		}
	}

	// QSY
	var revertFreq func()
	if freq := url.Params.Get("freq"); freq != "" {
		revertFreq, err = qsy(url.Scheme, freq)
		if err != nil {
			log.Printf("Unable to QSY: %s", err)
			return
		}
		defer revertFreq()
	}
	var currFreq Frequency
	if vfo, ok := VFOForTransport(url.Scheme); ok {
		f, _ := vfo.GetFreq()
		currFreq = Frequency(f)
	}

	// Wait for a clear channel
	switch url.Scheme {
	case "ardop":
		waitBusy(adTNC)
	case "winmor":
		waitBusy(wmTNC)
	}

	// Catch interrupts (signals) while dialing, so users can abort ardop/winmor connects.
	doneHandleInterrupt := handleInterrupt()

	log.Printf("Connecting to %s (%s)...", url.Target, url.Scheme)
	conn, err := transport.DialURL(url)

	close(doneHandleInterrupt)

	eventLog.LogConn("connect "+connectStr, currFreq, conn, err)

	if err != nil {
		log.Printf("Unable to establish connection to remote: %s", err)
		return
	}

	err = exchange(conn, url.Target, false)
	if err != nil {
		log.Printf("Exchange failed: %s", err)
	} else {
		log.Println("Disconnected.")
		success = true
	}

	return
}

func qsy(method, addr string) (revert func(), err error) {
	noop := func() {}

	var rigName string
	switch method {
	case MethodWinmor:
		rigName = config.Winmor.Rig
	case MethodArdop:
		rigName = config.Ardop.Rig
	case MethodAX25:
		rigName = config.AX25.Rig
	case MethodPactor:
		rigName = config.Pactor.Rig
	default:
		return noop, fmt.Errorf("Not supported with transport '%s'", method)
	}

	if rigName == "" {
		return noop, fmt.Errorf("Missing rig reference in config section for %s, don't know which rig to qsy", method)
	}

	var ok bool
	rig, ok := rigs[rigName]
	if !ok {
		return noop, fmt.Errorf("Hamlib rig '%s' not loaded.", rigName)
	}

	log.Printf("QSY %s: %s", method, addr)

	_, oldFreq, err := setFreq(rig, addr)
	if err != nil {
		return noop, err
	}

	time.Sleep(3 * time.Second)

	return func() {
		time.Sleep(time.Second)
		log.Printf("QSX %s: %.3f", method, float64(oldFreq)/1e3)
		rig.SetFreq(oldFreq)
	}, nil
}

func waitBusy(b transport.BusyChannelChecker) {
	printed := false

	for b.Busy() {
		if !printed && fOptions.IgnoreBusy {
			log.Println("Ignoring busy channel!")
			break
		} else if !printed {
			log.Println("Waiting for clear channel...")
			printed = true
		}
		time.Sleep(300 * time.Millisecond)
	}
}

func initWinmorTNC() error {
	if wmTNC != nil && wmTNC.Ping() == nil {
		return nil
	}

	if wmTNC != nil {
		wmTNC.Close()
	}

	var err error
	wmTNC, err = winmor.Open(config.Winmor.Addr, fOptions.MyCall, config.Locator)
	if err != nil {
		return fmt.Errorf("WINMOR TNC initialization failed: %s", err)
	}

	if config.Winmor.DriveLevel != 0 {
		if err := wmTNC.SetDriveLevel(config.Winmor.DriveLevel); err != nil {
			log.Println("Failed to set WINMOR drive level:", err)
		}
	}

	if v, err := wmTNC.Version(); err != nil {
		return fmt.Errorf("WINMOR TNC initialization failed: %s", err)
	} else {
		log.Printf("WINMOR TNC v%s initialized", v)
	}

	transport.RegisterDialer("winmor", wmTNC)

	if !config.Winmor.PTTControl {
		return nil
	}

	rig, ok := rigs[config.Winmor.Rig]
	if !ok {
		return fmt.Errorf("Unable to set PTT rig '%s': Not defined or not loaded.", config.Winmor.Rig)
	}
	wmTNC.SetPTT(rig)

	return nil
}

func initArdopTNC() error {
	if adTNC != nil && adTNC.Ping() == nil {
		return nil
	}

	if adTNC != nil {
		adTNC.Close()
	}

	var err error
	adTNC, err = ardop.OpenTCP(config.Ardop.Addr, fOptions.MyCall, config.Locator)
	if err != nil {
		return fmt.Errorf("ARDOP TNC initialization failed: %s", err)
	}

	if !config.Ardop.ARQBandwidth.IsZero() {
		if err := adTNC.SetARQBandwidth(config.Ardop.ARQBandwidth); err != nil {
			return fmt.Errorf("Unable to set ARQ bandwidth for ardop TNC: %s", err)
		}
	}

	if err := adTNC.SetCWID(config.Ardop.CWID); err != nil {
		return fmt.Errorf("Unable to configure CWID for ardop TNC: %s", err)
	}

	if v, err := adTNC.Version(); err != nil {
		return fmt.Errorf("ARDOP TNC initialization failed: %s", err)
	} else {
		log.Printf("ARDOP TNC (%s) initialized", v)
	}

	transport.RegisterDialer("ardop", adTNC)

	if !config.Ardop.PTTControl {
		return nil
	}

	rig, ok := rigs[config.Ardop.Rig]
	if !ok {
		return fmt.Errorf("Unable to set PTT rig '%s': Not defined or not loaded.", config.Ardop.Rig)
	}

	adTNC.SetPTT(rig)
	return nil
}

func initPactorModem() error {
	if pModem !=  nil {
		pModem.Close()
	}

	pModem, err := pactor.OpenModem(config.Pactor.Path, config.Pactor.Baudrate, fOptions.MyCall, config.Pactor.InitScript)
	if err != nil {
		return fmt.Errorf("Pactor initialization failed: %s", err)
	}

	transport.RegisterDialer("pactor", pModem)

	return nil
}
<|endoftext|>


<commit_before>package repo

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/kopia/repo/block"
	"github.com/kopia/repo/storage"
)

// ConnectOptions specifies options when persisting configuration to connect to a repository.
type ConnectOptions struct {
	block.CachingOptions
}

// Connect connects to the repository in the specified storage and persists the configuration and credentials in the file provided.
func Connect(ctx context.Context, configFile string, st storage.Storage, password string, opt ConnectOptions) error {
	formatBytes, err := st.GetBlock(ctx, FormatBlockID, 0, -1)
	if err != nil {
		return fmt.Errorf("unable to read format block: %v", err)
	}

	f, err := parseFormatBlock(formatBytes)
	if err != nil {
		return err
	}

	var lc LocalConfig
	lc.Storage = st.ConnectionInfo()

	if err = setupCaching(configFile, &lc, opt.CachingOptions, f.UniqueID); err != nil {
		return fmt.Errorf("unable to set up caching: %v", err)
	}

	d, err := json.MarshalIndent(&lc, "", "  ")
	if err != nil {
		return err
	}

	if err = os.MkdirAll(filepath.Dir(configFile), 0700); err != nil {
		return fmt.Errorf("unable to create config directory: %v", err)
	}

	if err = ioutil.WriteFile(configFile, d, 0600); err != nil {
		return fmt.Errorf("unable to write config file: %v", err)
	}

	// now verify that the repository can be opened with the provided config file.
	r, err := Open(ctx, configFile, password, nil)
	if err != nil {
		return err
	}

	return r.Close(ctx)
}

func setupCaching(configPath string, lc *LocalConfig, opt block.CachingOptions, uniqueID []byte) error {
	if opt.MaxCacheSizeBytes == 0 {
		lc.Caching = block.CachingOptions{}
		return nil
	}

	if opt.CacheDirectory == "" {
		cacheDir, err := os.UserCacheDir()
		if err != nil {
			return fmt.Errorf("unable to determine cache directory: %v", err)
		}

		h := sha256.New()
		h.Write(uniqueID)
		h.Write([]byte(configPath))
		lc.Caching.CacheDirectory = filepath.Join(cacheDir, hex.EncodeToString(h.Sum(nil))[0:16])
	} else {
		absCacheDir, err := filepath.Abs(opt.CacheDirectory)
		if err != nil {
			return err
		}

		lc.Caching.CacheDirectory = absCacheDir
	}
	lc.Caching.MaxCacheSizeBytes = opt.MaxCacheSizeBytes
	lc.Caching.MaxListCacheDurationSec = opt.MaxListCacheDurationSec

	log.Debugf("Creating cache directory '%v' with max size %v", lc.Caching.CacheDirectory, lc.Caching.MaxCacheSizeBytes)
	if err := os.MkdirAll(lc.Caching.CacheDirectory, 0700); err != nil {
		log.Warningf("unablet to create cache directory: %v", err)
	}
	return nil
}

// Disconnect removes the specified configuration file and any local cache directories.
func Disconnect(configFile string) error {
	cfg, err := loadConfigFromFile(configFile)
	if err != nil {
		return err
	}

	if cfg.Caching.CacheDirectory != "" {
		if err = os.RemoveAll(cfg.Caching.CacheDirectory); err != nil {
			log.Warningf("unable to to remove cache directory: %v", err)
		}
	}

	return os.Remove(configFile)
}
<commit_msg>repo: fixed default cache dir<commit_after>package repo

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/kopia/repo/block"
	"github.com/kopia/repo/storage"
)

// ConnectOptions specifies options when persisting configuration to connect to a repository.
type ConnectOptions struct {
	block.CachingOptions
}

// Connect connects to the repository in the specified storage and persists the configuration and credentials in the file provided.
func Connect(ctx context.Context, configFile string, st storage.Storage, password string, opt ConnectOptions) error {
	formatBytes, err := st.GetBlock(ctx, FormatBlockID, 0, -1)
	if err != nil {
		return fmt.Errorf("unable to read format block: %v", err)
	}

	f, err := parseFormatBlock(formatBytes)
	if err != nil {
		return err
	}

	var lc LocalConfig
	lc.Storage = st.ConnectionInfo()

	if err = setupCaching(configFile, &lc, opt.CachingOptions, f.UniqueID); err != nil {
		return fmt.Errorf("unable to set up caching: %v", err)
	}

	d, err := json.MarshalIndent(&lc, "", "  ")
	if err != nil {
		return err
	}

	if err = os.MkdirAll(filepath.Dir(configFile), 0700); err != nil {
		return fmt.Errorf("unable to create config directory: %v", err)
	}

	if err = ioutil.WriteFile(configFile, d, 0600); err != nil {
		return fmt.Errorf("unable to write config file: %v", err)
	}

	// now verify that the repository can be opened with the provided config file.
	r, err := Open(ctx, configFile, password, nil)
	if err != nil {
		return err
	}

	return r.Close(ctx)
}

func setupCaching(configPath string, lc *LocalConfig, opt block.CachingOptions, uniqueID []byte) error {
	if opt.MaxCacheSizeBytes == 0 {
		lc.Caching = block.CachingOptions{}
		return nil
	}

	if opt.CacheDirectory == "" {
		cacheDir, err := os.UserCacheDir()
		if err != nil {
			return fmt.Errorf("unable to determine cache directory: %v", err)
		}

		h := sha256.New()
		h.Write(uniqueID)
		h.Write([]byte(configPath))
		lc.Caching.CacheDirectory = filepath.Join(cacheDir, "kopia", hex.EncodeToString(h.Sum(nil))[0:16])
	} else {
		absCacheDir, err := filepath.Abs(opt.CacheDirectory)
		if err != nil {
			return err
		}

		lc.Caching.CacheDirectory = absCacheDir
	}
	lc.Caching.MaxCacheSizeBytes = opt.MaxCacheSizeBytes
	lc.Caching.MaxListCacheDurationSec = opt.MaxListCacheDurationSec

	log.Debugf("Creating cache directory '%v' with max size %v", lc.Caching.CacheDirectory, lc.Caching.MaxCacheSizeBytes)
	if err := os.MkdirAll(lc.Caching.CacheDirectory, 0700); err != nil {
		log.Warningf("unablet to create cache directory: %v", err)
	}
	return nil
}

// Disconnect removes the specified configuration file and any local cache directories.
func Disconnect(configFile string) error {
	cfg, err := loadConfigFromFile(configFile)
	if err != nil {
		return err
	}

	if cfg.Caching.CacheDirectory != "" {
		if err = os.RemoveAll(cfg.Caching.CacheDirectory); err != nil {
			log.Warningf("unable to to remove cache directory: %v", err)
		}
	}

	return os.Remove(configFile)
}
<|endoftext|>


<commit_before>package workers

import (
	"fmt"
	"github.com/APTrust/exchange/constants"
	"github.com/APTrust/exchange/context"
	"github.com/APTrust/exchange/models"
	"github.com/APTrust/exchange/network"
	//	"github.com/APTrust/exchange/util"
	//	"github.com/APTrust/exchange/util/fileutil"
	//	"github.com/APTrust/exchange/util/storage"
	//	"github.com/APTrust/exchange/validation"
	"github.com/nsqio/go-nsq"
	"os"
	//	"strings"
	"time"
)

// TODO: Move constants to config file?

// Standard retrieval is 3-5 hours
const RETRIEVAL_OPTION = "Standard"

// Keep the files in S3 up to 5 days, in case we're
// having system problems and we need to attempt the
// restore multiple times.
const DAYS_TO_KEEP_IN_S3 = 5

// Requests that an object be restored from Glacier to S3. This is
// the first step toward restoring a Glacier-only bag.
type APTGlacierRestoreInit struct {
	// Context includes logging, config, network connections, and
	// other general resources for the worker.
	Context *context.Context
	// RequestChannel is for requesting an item be moved from Glacier
	// into S3.
	RequestChannel chan *models.GlacierRestoreState
	// CleanupChannel is for housekeeping, like updating NSQ.
	CleanupChannel chan *models.GlacierRestoreState
}

func NewGlacierRestore(_context *context.Context) *APTGlacierRestoreInit {
	restorer := &APTGlacierRestoreInit{
		Context: _context,
	}
	// Set up buffered channels
	restorerBufferSize := _context.Config.GlacierRestoreWorker.NetworkConnections * 4
	workerBufferSize := _context.Config.GlacierRestoreWorker.Workers * 10
	restorer.RequestChannel = make(chan *models.GlacierRestoreState, restorerBufferSize)
	restorer.CleanupChannel = make(chan *models.GlacierRestoreState, workerBufferSize)
	// Set up a limited number of go routines
	for i := 0; i < _context.Config.GlacierRestoreWorker.NetworkConnections; i++ {
		go restorer.requestRestore()
	}
	for i := 0; i < _context.Config.GlacierRestoreWorker.Workers; i++ {
		go restorer.cleanup()
	}
	return restorer
}

// This is the callback that NSQ workers use to handle messages from NSQ.
func (restorer *APTGlacierRestoreInit) HandleMessage(message *nsq.Message) error {
	// TODO: Set up GlacierRestoreState
	workItem, err := GetWorkItem(message, restorer.Context)
	if err != nil {
		restorer.Context.MessageLog.Error(err.Error())
		return err
	}
	var state *models.GlacierRestoreState
	if workItem.WorkItemStateId != nil && *workItem.WorkItemStateId != 0 {
		workItemState, err := GetWorkItemState(workItem, restorer.Context, false)
		if err != nil {
			restorer.Context.MessageLog.Error(err.Error())
			return err
		}
		if workItemState != nil && workItemState.HasData() {
			state, err := workItemState.GlacierRestoreState()
			if err != nil {
				restorer.Context.MessageLog.Error(err.Error())
				return err
			}
			state.NSQMessage = message
			state.WorkItem = workItem
		}
	} else {
		state = models.NewGlacierRestoreState(message, workItem)
	}
	restorer.RequestChannel <- state
	return nil
}

func (restorer *APTGlacierRestoreInit) requestRestore() {
	for state := range restorer.RequestChannel {
		state.WorkSummary.ClearErrors()
		state.WorkSummary.Attempted = true
		state.WorkSummary.AttemptNumber += 1
		state.WorkSummary.Start()
		// s3Client := network.NewS3Head(
		// 	os.Getenv("AWS_ACCESS_KEY_ID"),
		// 	os.Getenv("AWS_SECRET_ACCESS_KEY"),
		// 	region,
		// 	bucket)
		// if WorkItem has a GenericFileIdentifier, this is a
		// single-file restore. Otherwise, it's an object restore.
		// Request retrieval from Glacier
		// Update GlacierRestoreState
		// Push to CleanupChannel
	}
}

func (restorer *APTGlacierRestoreInit) getIntellectualObject(state *models.GlacierRestoreState) (*models.IntellectualObject, error) {
	// Get object with files (second param) but no events (third param)
	resp := restorer.Context.PharosClient.IntellectualObjectGet(state.WorkItem.ObjectIdentifier, true, false)
	if resp.Error != nil {
		return nil, resp.Error
	}
	obj := resp.IntellectualObject()
	if obj == nil {
		return nil, fmt.Errorf("Pharos returned nil for IntellectualObject %s",
			state.WorkItem.ObjectIdentifier)
	}
	return obj, nil
}

func (restorer *APTGlacierRestoreInit) getGenericFile(state *models.GlacierRestoreState) (*models.GenericFile, error) {
	resp := restorer.Context.PharosClient.GenericFileGet(state.WorkItem.GenericFileIdentifier, false)
	if resp.Error != nil {
		return nil, resp.Error
	}
	gf := resp.GenericFile()
	if gf == nil {
		return nil, fmt.Errorf("Pharos returned nil for GenericFile %s",
			state.WorkItem.GenericFileIdentifier)
	}
	return gf, nil

}

func (restorer *APTGlacierRestoreInit) cleanup() {
	//for restoreState := range restorer.RequestChannel {
	// Update WorkItem in Pharos
	// Push to NSQ's restoration channel for packaging, etc.
	//}
}

func (restorer *APTGlacierRestoreInit) requestAllFiles(state *models.GlacierRestoreState) {
	if state.WorkItem.GenericFileIdentifier != "" {
		gfIdentifier := state.WorkItem.GenericFileIdentifier
		resp := restorer.Context.PharosClient.GenericFileGet(gfIdentifier, false)
		if resp.Error != nil {
			state.WorkSummary.AddError("Error getting GenericFile %s from Pharos: %v", gfIdentifier, resp.Error)
			return
		}
		genericFile := resp.GenericFile()
		if genericFile == nil {
			state.WorkSummary.AddError("Pharos returned nil for GenericFile %s", gfIdentifier)
			return
		}
		restorer.requestFile(state, genericFile)
	} else if state.WorkItem.ObjectIdentifier != "" {
		objIdentifier := state.WorkItem.ObjectIdentifier
		resp := restorer.Context.PharosClient.IntellectualObjectGet(objIdentifier, true, false)
		if resp.Error != nil {
			state.WorkSummary.AddError("Error getting IntellectualObject %s from Pharos: %v", objIdentifier, resp.Error)
			return
		}
		obj := resp.IntellectualObject()
		if obj == nil {
			state.WorkSummary.AddError("Pharos returned nil for IntellectualObject %s", objIdentifier)
			return
		}
		restorer.Context.MessageLog.Info("Object %s has %d files", obj.Identifier, len(obj.GenericFiles))
		for _, genericFile := range obj.GenericFiles {
			restorer.requestFile(state, genericFile)
		}
	} else {
		state.WorkSummary.AddError("Cannot process WorkItem %d: no file identifier or object identifier.", state.WorkItem.Id)
		return
	}
}

func (restorer *APTGlacierRestoreInit) requestFile(state *models.GlacierRestoreState, gf *models.GenericFile) {
	details, err := restorer.getRequestDetails(gf)
	if err != nil {
		state.WorkSummary.AddError(err.Error())
		return
	}

	glacierRestoreRequest := restorer.getRequestRecord(state, gf, details)
	if glacierRestoreRequest == nil {
		// Prior request was accepted and is in progress
		return
	}
	restorer.initializeRetrieval(state, gf, details, glacierRestoreRequest)
}

func (restorer *APTGlacierRestoreInit) getRequestDetails(gf *models.GenericFile) (map[string]string, error) {
	details := make(map[string]string)
	fileUUID, err := gf.PreservationStorageFileName()
	if err != nil {
		return nil, fmt.Errorf("File %s: %v. URI is %s", gf.Identifier, err, gf.URI)
	}
	details["fileUUID"] = fileUUID
	details["region"] = restorer.Context.Config.GlacierRegionVA
	details["bucket"] = restorer.Context.Config.GlacierBucketVA
	if gf.StorageOption == constants.StorageGlacierOH {
		details["region"] = restorer.Context.Config.GlacierRegionOH
		details["bucket"] = restorer.Context.Config.GlacierBucketOH
	} else if gf.StorageOption == constants.StorageGlacierOR {
		details["region"] = restorer.Context.Config.GlacierRegionOR
		details["bucket"] = restorer.Context.Config.GlacierBucketOR
	} else {
		return nil, fmt.Errorf("Cannot restore file %s because StorageOption is %s", gf.Identifier, gf.StorageOption)
	}
	return details, nil
}

func (restorer *APTGlacierRestoreInit) getRequestRecord(state *models.GlacierRestoreState, gf *models.GenericFile, details map[string]string) *models.GlacierRestoreRequest {
	glacierRestoreRequest := state.FindRequest(gf.Identifier)
	if glacierRestoreRequest != nil {
		if glacierRestoreRequest.RequestAccepted {
			restorer.Context.MessageLog.Info("Skipping %s: retrieval request was accepted earlier.", gf.Identifier)
			return nil
		} else {
			restorer.Context.MessageLog.Info("Retrying existing request for %s, which was not previously accepted", gf.Identifier)
		}
	} else {
		restorer.Context.MessageLog.Info("Creating new request for %s", gf.Identifier)
		glacierRestoreRequest = &models.GlacierRestoreRequest{
			GenericFileIdentifier: gf.Identifier,
			GlacierBucket:         details["bucket"],
			GlacierKey:            details["fileUUID"],
			RequestAccepted:       false,
			SomeoneElseRequested:  false,
		}
		state.Requests = append(state.Requests, glacierRestoreRequest)
	}
	return glacierRestoreRequest
}

func (restorer *APTGlacierRestoreInit) initializeRetrieval(state *models.GlacierRestoreState, gf *models.GenericFile, details map[string]string, glacierRestoreRequest *models.GlacierRestoreRequest) {

	restorer.Context.MessageLog.Info("Requesting Glacier retrieval of %s at %s (%s)",
		gf.Identifier, gf.URI, gf.StorageOption)

	restoreClient := network.NewS3Restore(
		os.Getenv("AWS_ACCESS_KEY_ID"),
		os.Getenv("AWS_SECRET_ACCESS_KEY"),
		details["region"],
		details["bucket"],
		details["fileUUID"],
		RETRIEVAL_OPTION,
		DAYS_TO_KEEP_IN_S3)
	now := time.Now().UTC()
	estimatedDeletionFromS3 := now.AddDate(0, 0, DAYS_TO_KEEP_IN_S3)
	someoneElseRequested := false
	restoreClient.Restore()
	if restoreClient.ErrorMessage != "" {
		if restoreClient.RestoreAlreadyInProgress {
			// Although we checked for this above, this case can occur when
			// an outside service requests Glacier retrieval.
			restorer.Context.MessageLog.Info("Retrieval of %s was requested earlier (probably by someone else) and is already in progress.", gf.Identifier)
			someoneElseRequested = true
		} else {
			state.WorkSummary.AddError("Glacier retrieval request returned an error for %s at %s: %v", gf.Identifier, gf.URI, restoreClient.ErrorMessage)
			return
		}
	}

	// Update this info. It's a pointer, so it will be saved with GlacierRestoreState.
	glacierRestoreRequest.RequestAccepted = (restoreClient.ErrorMessage == "")
	glacierRestoreRequest.RequestedAt = now
	glacierRestoreRequest.EstimatedDeletionFromS3 = estimatedDeletionFromS3
	glacierRestoreRequest.SomeoneElseRequested = someoneElseRequested
}
<commit_msg>Working on Glacier restore init<commit_after>package workers

import (
	"fmt"
	"github.com/APTrust/exchange/constants"
	"github.com/APTrust/exchange/context"
	"github.com/APTrust/exchange/models"
	"github.com/APTrust/exchange/network"
	//	"github.com/APTrust/exchange/util"
	//	"github.com/APTrust/exchange/util/fileutil"
	//	"github.com/APTrust/exchange/util/storage"
	//	"github.com/APTrust/exchange/validation"
	"github.com/nsqio/go-nsq"
	"os"
	//	"strings"
	"time"
)

// TODO: Move constants to config file?

// Standard retrieval is 3-5 hours
const RETRIEVAL_OPTION = "Standard"

// Keep the files in S3 up to 5 days, in case we're
// having system problems and we need to attempt the
// restore multiple times.
const DAYS_TO_KEEP_IN_S3 = 5

// Requests that an object be restored from Glacier to S3. This is
// the first step toward restoring a Glacier-only bag.
type APTGlacierRestoreInit struct {
	// Context includes logging, config, network connections, and
	// other general resources for the worker.
	Context *context.Context
	// RequestChannel is for requesting an item be moved from Glacier
	// into S3.
	RequestChannel chan *models.GlacierRestoreState
	// CleanupChannel is for housekeeping, like updating NSQ.
	CleanupChannel chan *models.GlacierRestoreState
}

func NewGlacierRestore(_context *context.Context) *APTGlacierRestoreInit {
	restorer := &APTGlacierRestoreInit{
		Context: _context,
	}
	// Set up buffered channels
	restorerBufferSize := _context.Config.GlacierRestoreWorker.NetworkConnections * 4
	workerBufferSize := _context.Config.GlacierRestoreWorker.Workers * 10
	restorer.RequestChannel = make(chan *models.GlacierRestoreState, restorerBufferSize)
	restorer.CleanupChannel = make(chan *models.GlacierRestoreState, workerBufferSize)
	// Set up a limited number of go routines
	for i := 0; i < _context.Config.GlacierRestoreWorker.NetworkConnections; i++ {
		go restorer.requestRestore()
	}
	for i := 0; i < _context.Config.GlacierRestoreWorker.Workers; i++ {
		go restorer.cleanup()
	}
	return restorer
}

// This is the callback that NSQ workers use to handle messages from NSQ.
func (restorer *APTGlacierRestoreInit) HandleMessage(message *nsq.Message) error {
	// TODO: Set up GlacierRestoreState
	workItem, err := GetWorkItem(message, restorer.Context)
	if err != nil {
		restorer.Context.MessageLog.Error(err.Error())
		return err
	}
	var state *models.GlacierRestoreState
	if workItem.WorkItemStateId != nil && *workItem.WorkItemStateId != 0 {
		workItemState, err := GetWorkItemState(workItem, restorer.Context, false)
		if err != nil {
			restorer.Context.MessageLog.Error(err.Error())
			return err
		}
		if workItemState != nil && workItemState.HasData() {
			state, err := workItemState.GlacierRestoreState()
			if err != nil {
				restorer.Context.MessageLog.Error(err.Error())
				return err
			}
			state.NSQMessage = message
			state.WorkItem = workItem
		}
	} else {
		state = models.NewGlacierRestoreState(message, workItem)
	}
	restorer.RequestChannel <- state
	return nil
}

func (restorer *APTGlacierRestoreInit) requestRestore() {
	for state := range restorer.RequestChannel {
		state.WorkSummary.ClearErrors()
		state.WorkSummary.Attempted = true
		state.WorkSummary.AttemptNumber += 1
		state.WorkSummary.Start()

		if state.WorkItem.GenericFileIdentifier != "" {
			gf, err := restorer.getGenericFile(state)
			if err != nil {
				state.WorkSummary.AddError(err.Error())
				restorer.CleanupChannel <- state
				continue
			}
			restorer.requestFile(state, gf)
		} else {
			restorer.requestObject(state)
		}

		// Request retrieval from Glacier
		// Update GlacierRestoreState
		// Push to CleanupChannel
	}
}

func (restorer *APTGlacierRestoreInit) requestObject(state *models.GlacierRestoreState) {
	obj, err := restorer.getIntellectualObject(state)
	if err != nil {
		state.WorkSummary.AddError(err.Error())
		return
	}
	for _, gf := range obj.GenericFiles {
		needsRestoreRequest, err := restorer.restoreRequestNeeded(state, gf)
		if err != nil {
			state.WorkSummary.AddError(err.Error())
			continue
		}
		if needsRestoreRequest {
			err = restorer.requestRestoration(state, gf)
			if err != nil {
				state.WorkSummary.AddError(err.Error())
			}
		}
	}
}

func (restorer *APTGlacierRestoreInit) restoreRequestNeeded(state *models.GlacierRestoreState, gf *models.GenericFile) (bool, error) {
	needsRestoreRequest := false
	s3Client, err := restorer.getS3HeadClient(gf.StorageOption)
	if err != nil {
		return needsRestoreRequest, err
	}
	fileUUID, err := gf.PreservationStorageFileName()
	if err != nil {
		return needsRestoreRequest, err
	}
	s3Client.Head(fileUUID)
	if s3Client.ErrorMessage != "" {
		err = fmt.Errorf("S3 HEAD request for file %s (%s) returned error: %s",
			fileUUID, gf.Identifier, s3Client.ErrorMessage)
		return needsRestoreRequest, err
	}
	restoreRequestInfo, err := s3Client.GetRestoreRequestInfo()
	if err != nil {
		return needsRestoreRequest, err
	}

	glacierRestoreRequest := state.FindRequest(gf.Identifier)
	if glacierRestoreRequest == nil {
		glacierRestoreRequest = &models.GlacierRestoreRequest{
			GenericFileIdentifier: gf.Identifier,
			GlacierBucket:         s3Client.BucketName,
			GlacierKey:            fileUUID,
		}
		state.Requests = append(state.Requests, glacierRestoreRequest)
	}

	if restoreRequestInfo.RequestInProgress {
		// TODO: log and go on

	} else if restoreRequestInfo.RequestIsComplete {
		// TODO: log and update expiry date
		glacierRestoreRequest.IsAvailableInS3 = true
		glacierRestoreRequest.EstimatedDeletionFromS3 = restoreRequestInfo.S3ExpiryDate
	} else {
		// TODO: log
		needsRestoreRequest = true
	}
	return needsRestoreRequest, nil
}

func (restorer *APTGlacierRestoreInit) requestRestoration(state *models.GlacierRestoreState, gf *models.GenericFile) error {
	// TODO: Implement this
	return nil
}

func (restorer *APTGlacierRestoreInit) getS3HeadClient(storageOption string) (*network.S3Head, error) {
	region, bucket, err := restorer.Context.Config.StorageRegionAndBucketFor(storageOption)
	if err != nil {
		return nil, err
	}
	client := network.NewS3Head(
		os.Getenv("AWS_ACCESS_KEY_ID"),
		os.Getenv("AWS_SECRET_ACCESS_KEY"),
		region,
		bucket)
	return client, nil
}

func (restorer *APTGlacierRestoreInit) getIntellectualObject(state *models.GlacierRestoreState) (*models.IntellectualObject, error) {
	// Get object with files (second param) but no events (third param)
	resp := restorer.Context.PharosClient.IntellectualObjectGet(state.WorkItem.ObjectIdentifier, true, false)
	if resp.Error != nil {
		return nil, resp.Error
	}
	obj := resp.IntellectualObject()
	if obj == nil {
		return nil, fmt.Errorf("Pharos returned nil for IntellectualObject %s",
			state.WorkItem.ObjectIdentifier)
	}
	return obj, nil
}

func (restorer *APTGlacierRestoreInit) getGenericFile(state *models.GlacierRestoreState) (*models.GenericFile, error) {
	resp := restorer.Context.PharosClient.GenericFileGet(state.WorkItem.GenericFileIdentifier, false)
	if resp.Error != nil {
		return nil, resp.Error
	}
	gf := resp.GenericFile()
	if gf == nil {
		return nil, fmt.Errorf("Pharos returned nil for GenericFile %s",
			state.WorkItem.GenericFileIdentifier)
	}
	return gf, nil
}

func (restorer *APTGlacierRestoreInit) cleanup() {
	//for restoreState := range restorer.RequestChannel {
	// Update WorkItem in Pharos
	// Push to NSQ's restoration channel for packaging, etc.
	//}
}

func (restorer *APTGlacierRestoreInit) requestAllFiles(state *models.GlacierRestoreState) {
	if state.WorkItem.GenericFileIdentifier != "" {
		gfIdentifier := state.WorkItem.GenericFileIdentifier
		resp := restorer.Context.PharosClient.GenericFileGet(gfIdentifier, false)
		if resp.Error != nil {
			state.WorkSummary.AddError("Error getting GenericFile %s from Pharos: %v", gfIdentifier, resp.Error)
			return
		}
		genericFile := resp.GenericFile()
		if genericFile == nil {
			state.WorkSummary.AddError("Pharos returned nil for GenericFile %s", gfIdentifier)
			return
		}
		restorer.requestFile(state, genericFile)
	} else if state.WorkItem.ObjectIdentifier != "" {
		objIdentifier := state.WorkItem.ObjectIdentifier
		resp := restorer.Context.PharosClient.IntellectualObjectGet(objIdentifier, true, false)
		if resp.Error != nil {
			state.WorkSummary.AddError("Error getting IntellectualObject %s from Pharos: %v", objIdentifier, resp.Error)
			return
		}
		obj := resp.IntellectualObject()
		if obj == nil {
			state.WorkSummary.AddError("Pharos returned nil for IntellectualObject %s", objIdentifier)
			return
		}
		restorer.Context.MessageLog.Info("Object %s has %d files", obj.Identifier, len(obj.GenericFiles))
		for _, genericFile := range obj.GenericFiles {
			restorer.requestFile(state, genericFile)
		}
	} else {
		state.WorkSummary.AddError("Cannot process WorkItem %d: no file identifier or object identifier.", state.WorkItem.Id)
		return
	}
}

func (restorer *APTGlacierRestoreInit) requestFile(state *models.GlacierRestoreState, gf *models.GenericFile) {
	details, err := restorer.getRequestDetails(gf)
	if err != nil {
		state.WorkSummary.AddError(err.Error())
		return
	}

	glacierRestoreRequest := restorer.getRequestRecord(state, gf, details)
	if glacierRestoreRequest == nil {
		// Prior request was accepted and is in progress
		return
	}
	restorer.initializeRetrieval(state, gf, details, glacierRestoreRequest)
}

func (restorer *APTGlacierRestoreInit) getRequestDetails(gf *models.GenericFile) (map[string]string, error) {
	details := make(map[string]string)
	fileUUID, err := gf.PreservationStorageFileName()
	if err != nil {
		return nil, fmt.Errorf("File %s: %v. URI is %s", gf.Identifier, err, gf.URI)
	}
	details["fileUUID"] = fileUUID
	details["region"] = restorer.Context.Config.GlacierRegionVA
	details["bucket"] = restorer.Context.Config.GlacierBucketVA
	if gf.StorageOption == constants.StorageGlacierOH {
		details["region"] = restorer.Context.Config.GlacierRegionOH
		details["bucket"] = restorer.Context.Config.GlacierBucketOH
	} else if gf.StorageOption == constants.StorageGlacierOR {
		details["region"] = restorer.Context.Config.GlacierRegionOR
		details["bucket"] = restorer.Context.Config.GlacierBucketOR
	} else {
		return nil, fmt.Errorf("Cannot restore file %s because StorageOption is %s", gf.Identifier, gf.StorageOption)
	}
	return details, nil
}

func (restorer *APTGlacierRestoreInit) getRequestRecord(state *models.GlacierRestoreState, gf *models.GenericFile, details map[string]string) *models.GlacierRestoreRequest {
	glacierRestoreRequest := state.FindRequest(gf.Identifier)
	if glacierRestoreRequest != nil {
		if glacierRestoreRequest.RequestAccepted {
			restorer.Context.MessageLog.Info("Skipping %s: retrieval request was accepted earlier.", gf.Identifier)
			return nil
		} else {
			restorer.Context.MessageLog.Info("Retrying existing request for %s, which was not previously accepted", gf.Identifier)
		}
	} else {
		restorer.Context.MessageLog.Info("Creating new request for %s", gf.Identifier)
		glacierRestoreRequest = &models.GlacierRestoreRequest{
			GenericFileIdentifier: gf.Identifier,
			GlacierBucket:         details["bucket"],
			GlacierKey:            details["fileUUID"],
			RequestAccepted:       false,
			SomeoneElseRequested:  false,
		}
		state.Requests = append(state.Requests, glacierRestoreRequest)
	}
	return glacierRestoreRequest
}

func (restorer *APTGlacierRestoreInit) initializeRetrieval(state *models.GlacierRestoreState, gf *models.GenericFile, details map[string]string, glacierRestoreRequest *models.GlacierRestoreRequest) {

	restorer.Context.MessageLog.Info("Requesting Glacier retrieval of %s at %s (%s)",
		gf.Identifier, gf.URI, gf.StorageOption)

	restoreClient := network.NewS3Restore(
		os.Getenv("AWS_ACCESS_KEY_ID"),
		os.Getenv("AWS_SECRET_ACCESS_KEY"),
		details["region"],
		details["bucket"],
		details["fileUUID"],
		RETRIEVAL_OPTION,
		DAYS_TO_KEEP_IN_S3)
	now := time.Now().UTC()
	estimatedDeletionFromS3 := now.AddDate(0, 0, DAYS_TO_KEEP_IN_S3)
	someoneElseRequested := false
	restoreClient.Restore()
	if restoreClient.ErrorMessage != "" {
		if restoreClient.RestoreAlreadyInProgress {
			// Although we checked for this above, this case can occur when
			// an outside service requests Glacier retrieval.
			restorer.Context.MessageLog.Info("Retrieval of %s was requested earlier (probably by someone else) and is already in progress.", gf.Identifier)
			someoneElseRequested = true
		} else {
			state.WorkSummary.AddError("Glacier retrieval request returned an error for %s at %s: %v", gf.Identifier, gf.URI, restoreClient.ErrorMessage)
			return
		}
	}

	// Update this info. It's a pointer, so it will be saved with GlacierRestoreState.
	glacierRestoreRequest.RequestAccepted = (restoreClient.ErrorMessage == "")
	glacierRestoreRequest.RequestedAt = now
	glacierRestoreRequest.EstimatedDeletionFromS3 = estimatedDeletionFromS3
	glacierRestoreRequest.SomeoneElseRequested = someoneElseRequested
}
<|endoftext|>


<commit_before>// Package console provides GopherJS wrappers for the JavaScript
// console.
//
// Some functions support format specifiers. The following specifiers
// are supported:
//
//     %s – Formats the value as a string
//     %d – Formats the value as an integer
//     %i – Same as %d
//     %f – Formats the value as a floating point value.
//     %o – Formats the value as an expandable DOM element (as in the Elements panel).
//     %O – Formats the value as an expandable JavaScript object.
//     %c – Formats the output string according to CSS styles you provide.
//
// This package does not provide functions for the aliases
// console.debug and console.info – use Log instead.
//
// For a more detailed explanation of the APIs, see for example
// Google's documentation at
// https://developers.google.com/chrome-developer-tools/docs/console-api.
//
// Portions of this documentation are modifications based on work
// created and shared by Google and used according to terms described
// in the Creative Commons 3.0 Attribution License.
package console

import (
	"bytes"

	"github.com/neelance/gopherjs/js"
)

var c = js.Global.Get("console")

// Assert writes msg to the console if b is false.
func Assert(b bool, msg interface{}) {
	c.Call("assert", b, msg)
}

// Clear clears the console.
func Clear() {
	c.Call("clear")
}

// Count writes the number of times that it has been invoked at the
// same line and with the same label.
func Count(label string) {
	c.Call("count", label)
}

// Dir prints a JavaScript representation of the specified object. If
// the object being logged is an HTML element, then the properties of
// its DOM representation are displayed.
func Dir(obj interface{}) {
	c.Call("dir", obj)
}

// DirXML Prints an XML representation of the specified object. For
// HTML elements, calling this method is equivalent to calling Log.
func DirXML(obj interface{}) {
	c.Call("dirxml", obj)
}

// Error is like Log but also includes a stack trace.
func Error(objs ...interface{}) {
	c.Call("error", objs...)
}

// Group starts a new logging group with an optional title.
//
// All console output that occurs after calling this function appears
// in the same visual group. Groups can be nested.
//
// The title will be generated according to the rules of the Log
// function.
func Group(objs ...interface{}) {
	c.Call("group", objs...)
}

// GroupCollapsed is like Group, except that the newly created
// group starts collapsed instead of open.
func GroupCollapsed(objs ...interface{}) {
	c.Call("groupCollapsed", objs...)
}

// GroupEnd closes the currently active logging group.
func GroupEnd() {
	c.Call("groupEnd")
}

// Log displays a message in the console. You pass one or more objects
// to this method, each of which are evaluated and concatenated into a
// space-delimited string. The first parameter you pass to Log may
// contain format specifiers.
func Log(objs ...interface{}) {
	c.Call("log", objs...)
}

// Profile starts a new CPU profile with an optional label.
func Profile(label interface{}) {
	c.Call("profile", label)
}

// ProfileEnd stops the currently running CPU profile, if any.
func ProfileEnd() {
	c.Call("profileEnd")
}

// Time starts a new timer with an associated label. Calling TimeEnd
// with the same label will stop the timer and print the elapsed time.
func Time(label interface{}) {
	c.Call("time", label)
}

// TimeEnd ends a timer that was started with Time and prints the
// elapsed time.
func TimeEnd(label interface{}) {
	c.Call("timeEnd", label)
}

// Timestamp adds an event to the timeline during a recording session.
func Timestamp(label interface{}) {
	c.Call("timeStamp", label)
}

// Trace prints a stack trace, starting from the point where Trace was
// called.
func Trace() {
	c.Call("trace")
}

// Warn is like Log but displays a different icon alongside the
// message.
func Warn(objs ...interface{}) {
	c.Call("warn", objs...)
}

// Writer implements an io.Writer on top of the JavaScript console.
// Writes will be buffered until a newline is encountered, which will
// cause flushing up to the newline.
type Writer struct {
	buf *bytes.Buffer
}

func (w *Writer) Write(buf []byte) (n int, err error) {
	if len(buf) == 0 {
		return 0, nil
	}

	for i := len(buf); i >= 0; i-- {
		if buf[i] == '\n' {
			w.buf.Write(buf[:i])
			Log(w.buf.String())
			w.buf.Reset()
			w.buf.Write(buf[i+1:])
			break
		}
	}

	return len(buf), nil
}

func (w *Writer) WriteString(s string) (n int, err error) {
	return w.Write([]byte(s))
}

// Flush will flush the current line to the console.
func (w *Writer) Flush() {
	w.Write([]byte{'\n'})
}

func New() *Writer {
	return &Writer{buf: new(bytes.Buffer)}
}
<commit_msg>fix gopherjs/js import path<commit_after>// Package console provides GopherJS wrappers for the JavaScript
// console.
//
// Some functions support format specifiers. The following specifiers
// are supported:
//
//     %s – Formats the value as a string
//     %d – Formats the value as an integer
//     %i – Same as %d
//     %f – Formats the value as a floating point value.
//     %o – Formats the value as an expandable DOM element (as in the Elements panel).
//     %O – Formats the value as an expandable JavaScript object.
//     %c – Formats the output string according to CSS styles you provide.
//
// This package does not provide functions for the aliases
// console.debug and console.info – use Log instead.
//
// For a more detailed explanation of the APIs, see for example
// Google's documentation at
// https://developers.google.com/chrome-developer-tools/docs/console-api.
//
// Portions of this documentation are modifications based on work
// created and shared by Google and used according to terms described
// in the Creative Commons 3.0 Attribution License.
package console

import (
	"bytes"

	"github.com/gopherjs/gopherjs/js"
)

var c = js.Global.Get("console")

// Assert writes msg to the console if b is false.
func Assert(b bool, msg interface{}) {
	c.Call("assert", b, msg)
}

// Clear clears the console.
func Clear() {
	c.Call("clear")
}

// Count writes the number of times that it has been invoked at the
// same line and with the same label.
func Count(label string) {
	c.Call("count", label)
}

// Dir prints a JavaScript representation of the specified object. If
// the object being logged is an HTML element, then the properties of
// its DOM representation are displayed.
func Dir(obj interface{}) {
	c.Call("dir", obj)
}

// DirXML Prints an XML representation of the specified object. For
// HTML elements, calling this method is equivalent to calling Log.
func DirXML(obj interface{}) {
	c.Call("dirxml", obj)
}

// Error is like Log but also includes a stack trace.
func Error(objs ...interface{}) {
	c.Call("error", objs...)
}

// Group starts a new logging group with an optional title.
//
// All console output that occurs after calling this function appears
// in the same visual group. Groups can be nested.
//
// The title will be generated according to the rules of the Log
// function.
func Group(objs ...interface{}) {
	c.Call("group", objs...)
}

// GroupCollapsed is like Group, except that the newly created
// group starts collapsed instead of open.
func GroupCollapsed(objs ...interface{}) {
	c.Call("groupCollapsed", objs...)
}

// GroupEnd closes the currently active logging group.
func GroupEnd() {
	c.Call("groupEnd")
}

// Log displays a message in the console. You pass one or more objects
// to this method, each of which are evaluated and concatenated into a
// space-delimited string. The first parameter you pass to Log may
// contain format specifiers.
func Log(objs ...interface{}) {
	c.Call("log", objs...)
}

// Profile starts a new CPU profile with an optional label.
func Profile(label interface{}) {
	c.Call("profile", label)
}

// ProfileEnd stops the currently running CPU profile, if any.
func ProfileEnd() {
	c.Call("profileEnd")
}

// Time starts a new timer with an associated label. Calling TimeEnd
// with the same label will stop the timer and print the elapsed time.
func Time(label interface{}) {
	c.Call("time", label)
}

// TimeEnd ends a timer that was started with Time and prints the
// elapsed time.
func TimeEnd(label interface{}) {
	c.Call("timeEnd", label)
}

// Timestamp adds an event to the timeline during a recording session.
func Timestamp(label interface{}) {
	c.Call("timeStamp", label)
}

// Trace prints a stack trace, starting from the point where Trace was
// called.
func Trace() {
	c.Call("trace")
}

// Warn is like Log but displays a different icon alongside the
// message.
func Warn(objs ...interface{}) {
	c.Call("warn", objs...)
}

// Writer implements an io.Writer on top of the JavaScript console.
// Writes will be buffered until a newline is encountered, which will
// cause flushing up to the newline.
type Writer struct {
	buf *bytes.Buffer
}

func (w *Writer) Write(buf []byte) (n int, err error) {
	if len(buf) == 0 {
		return 0, nil
	}

	for i := len(buf); i >= 0; i-- {
		if buf[i] == '\n' {
			w.buf.Write(buf[:i])
			Log(w.buf.String())
			w.buf.Reset()
			w.buf.Write(buf[i+1:])
			break
		}
	}

	return len(buf), nil
}

func (w *Writer) WriteString(s string) (n int, err error) {
	return w.Write([]byte(s))
}

// Flush will flush the current line to the console.
func (w *Writer) Flush() {
	w.Write([]byte{'\n'})
}

func New() *Writer {
	return &Writer{buf: new(bytes.Buffer)}
}
<|endoftext|>