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

package menus

import (
	"sort"
	"strconv"
	"sync"

	"github.com/beevik/etree"
	"github.com/hexya-erp/hexya/hexya/actions"
)

// Registry is the menu Collection of the application
var (
	Registry     *Collection
	bootstrapMap map[string]*Menu
)

// A Collection is a hierarchical and sortable Collection of menus
type Collection struct {
	sync.RWMutex
	Menus    []*Menu
	menusMap map[string]*Menu
}

func (mc *Collection) Len() int {
	return len(mc.Menus)
}

func (mc *Collection) Swap(i, j int) {
	mc.Menus[i], mc.Menus[j] = mc.Menus[j], mc.Menus[i]
}

func (mc *Collection) Less(i, j int) bool {
	return mc.Menus[i].Sequence < mc.Menus[j].Sequence
}

// Add adds a menu to the menu Collection
func (mc *Collection) Add(m *Menu) {
	if m.Action != nil {
		m.HasAction = true
	}
	var targetCollection *Collection
	if m.Parent != nil {
		if m.Parent.Children == nil {
			m.Parent.Children = NewCollection()
		}
		targetCollection = m.Parent.Children
		m.Parent.HasChildren = true
	} else {
		targetCollection = mc
	}
	m.ParentCollection = targetCollection
	targetCollection.Menus = append(targetCollection.Menus, m)
	sort.Sort(targetCollection)

	// We add the menu to the Registry which is the top collection
	mc.Lock()
	defer mc.Unlock()
	Registry.menusMap[m.ID] = m
}

// GetByID returns the Menu with the given id
func (mc *Collection) GetByID(id string) *Menu {
	return mc.menusMap[id]
}

// NewCollection returns a pointer to a new
// Collection instance
func NewCollection() *Collection {
	res := Collection{
		menusMap: make(map[string]*Menu),
	}
	return &res
}

// A Menu is the representation of a single menu item
type Menu struct {
	ID               string
	Name             string
	ParentID         string
	Parent           *Menu
	ParentCollection *Collection
	Children         *Collection
	Sequence         uint8
	ActionID         string
	Action           *actions.Action
	HasChildren      bool
	HasAction        bool
	names            map[string]string
}

// TranslatedName returns the translated name of this menu
// in the given language
func (m Menu) TranslatedName(lang string) string {
	res, ok := m.names[lang]
	if !ok {
		res = m.Name
	}
	return res
}

// LoadFromEtree reads the menu given etree.Element, creates or updates the menu
// and adds it to the menu registry if it not already.
func LoadFromEtree(element *etree.Element) {
	AddMenuToMapFromEtree(element, bootstrapMap)
}

// AddMenuToMapFromEtree reads the menu from the given element
// and adds it to the given map.
func AddMenuToMapFromEtree(element *etree.Element, mMap map[string]*Menu) map[string]*Menu {
	seq, _ := strconv.Atoi(element.SelectAttrValue("sequence", "10"))
	menu := Menu{
		ID:       element.SelectAttrValue("id", "NO_ID"),
		ActionID: element.SelectAttrValue("action", ""),
		Name:     element.SelectAttrValue("name", ""),
		ParentID: element.SelectAttrValue("parent", ""),
		Sequence: uint8(seq),
	}
	mMap[menu.ID] = &menu
	return mMap
}
<commit_msg>Added web_icon support on menus<commit_after>// Copyright 2016 NDP Systèmes. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package menus

import (
	"sort"
	"strconv"
	"sync"

	"github.com/beevik/etree"
	"github.com/hexya-erp/hexya/hexya/actions"
)

// Registry is the menu Collection of the application
var (
	Registry     *Collection
	bootstrapMap map[string]*Menu
)

// A Collection is a hierarchical and sortable Collection of menus
type Collection struct {
	sync.RWMutex
	Menus    []*Menu
	menusMap map[string]*Menu
}

func (mc *Collection) Len() int {
	return len(mc.Menus)
}

func (mc *Collection) Swap(i, j int) {
	mc.Menus[i], mc.Menus[j] = mc.Menus[j], mc.Menus[i]
}

func (mc *Collection) Less(i, j int) bool {
	return mc.Menus[i].Sequence < mc.Menus[j].Sequence
}

// Add adds a menu to the menu Collection
func (mc *Collection) Add(m *Menu) {
	if m.Action != nil {
		m.HasAction = true
	}
	var targetCollection *Collection
	if m.Parent != nil {
		if m.Parent.Children == nil {
			m.Parent.Children = NewCollection()
		}
		targetCollection = m.Parent.Children
		m.Parent.HasChildren = true
	} else {
		targetCollection = mc
	}
	m.ParentCollection = targetCollection
	targetCollection.Menus = append(targetCollection.Menus, m)
	sort.Sort(targetCollection)

	// We add the menu to the Registry which is the top collection
	mc.Lock()
	defer mc.Unlock()
	Registry.menusMap[m.ID] = m
}

// GetByID returns the Menu with the given id
func (mc *Collection) GetByID(id string) *Menu {
	return mc.menusMap[id]
}

// NewCollection returns a pointer to a new
// Collection instance
func NewCollection() *Collection {
	res := Collection{
		menusMap: make(map[string]*Menu),
	}
	return &res
}

// A Menu is the representation of a single menu item
type Menu struct {
	ID               string
	Name             string
	ParentID         string
	Parent           *Menu
	ParentCollection *Collection
	Children         *Collection
	Sequence         uint8
	ActionID         string
	Action           *actions.Action
	HasChildren      bool
	HasAction        bool
	WebIcon          string
	names            map[string]string
}

// TranslatedName returns the translated name of this menu
// in the given language
func (m Menu) TranslatedName(lang string) string {
	res, ok := m.names[lang]
	if !ok {
		res = m.Name
	}
	return res
}

// LoadFromEtree reads the menu given etree.Element, creates or updates the menu
// and adds it to the menu registry if it not already.
func LoadFromEtree(element *etree.Element) {
	AddMenuToMapFromEtree(element, bootstrapMap)
}

// AddMenuToMapFromEtree reads the menu from the given element
// and adds it to the given map.
func AddMenuToMapFromEtree(element *etree.Element, mMap map[string]*Menu) map[string]*Menu {
	seq, _ := strconv.Atoi(element.SelectAttrValue("sequence", "10"))
	menu := Menu{
		ID:       element.SelectAttrValue("id", "NO_ID"),
		ActionID: element.SelectAttrValue("action", ""),
		Name:     element.SelectAttrValue("name", ""),
		ParentID: element.SelectAttrValue("parent", ""),
		WebIcon:  element.SelectAttrValue("web_icon", ""),
		Sequence: uint8(seq),
	}
	mMap[menu.ID] = &menu
	return mMap
}
<|endoftext|>


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

package tailsamplingprocessor

import (
	"context"
	"time"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/config/configmodels"
	"go.opentelemetry.io/collector/consumer"
	"go.opentelemetry.io/collector/processor/processorhelper"
)

const (
	// The value of "type" Tail Sampling in configuration.
	typeStr = "tail_sampling"
)

// NewFactory returns a new factory for the Tail Sampling processor.
func NewFactory() component.ProcessorFactory {
	return processorhelper.NewFactory(
		typeStr,
		createDefaultConfig,
		processorhelper.WithTraces(createTraceProcessor))
}

func createDefaultConfig() configmodels.Processor {
	return &Config{
		ProcessorSettings: configmodels.ProcessorSettings{
			TypeVal: typeStr,
			NameVal: typeStr,
		},
		DecisionWait: 30 * time.Second,
		NumTraces:    50000,
	}
}

func createTraceProcessor(
	_ context.Context,
	params component.ProcessorCreateParams,
	cfg configmodels.Processor,
	nextConsumer consumer.TracesConsumer,
) (component.TracesProcessor, error) {
	tCfg := cfg.(*Config)
	return newTraceProcessor(params.Logger, nextConsumer, *tCfg)
}
<commit_msg>Expose tail sampling processor metrics (#1533)<commit_after>// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tailsamplingprocessor

import (
	"context"
	"sync"
	"time"

	"go.opencensus.io/stats/view"
	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/config/configmodels"
	"go.opentelemetry.io/collector/config/configtelemetry"
	"go.opentelemetry.io/collector/consumer"
	"go.opentelemetry.io/collector/processor/processorhelper"
)

const (
	// The value of "type" Tail Sampling in configuration.
	typeStr = "tail_sampling"
)

var onceMetrics sync.Once

// NewFactory returns a new factory for the Tail Sampling processor.
func NewFactory() component.ProcessorFactory {
	onceMetrics.Do(func() {
		// TODO: this is hardcoding the metrics level and skips error handling
		_ = view.Register(SamplingProcessorMetricViews(configtelemetry.LevelNormal)...)
	})

	return processorhelper.NewFactory(
		typeStr,
		createDefaultConfig,
		processorhelper.WithTraces(createTraceProcessor))
}

func createDefaultConfig() configmodels.Processor {
	return &Config{
		ProcessorSettings: configmodels.ProcessorSettings{
			TypeVal: typeStr,
			NameVal: typeStr,
		},
		DecisionWait: 30 * time.Second,
		NumTraces:    50000,
	}
}

func createTraceProcessor(
	_ context.Context,
	params component.ProcessorCreateParams,
	cfg configmodels.Processor,
	nextConsumer consumer.TracesConsumer,
) (component.TracesProcessor, error) {
	tCfg := cfg.(*Config)
	return newTraceProcessor(params.Logger, nextConsumer, *tCfg)
}
<|endoftext|>


<commit_before>package integration

import . "gopkg.in/check.v1"

func (s *QemuSuite) TestCustomDocker(c *C) {
	s.RunQemu(c, "--cloud-config", "./tests/assets/test_05/cloud-config.yml")

	s.CheckCall(c, `
set -ex

docker version | grep 1.12.6

sudo ros engine list | grep 1.12.6 | grep current
(sudo ros engine switch invalid 2>&1 || true) | grep "invalid is not a valid engine"
(sudo ros engine enable invalid 2>&1 || true) | grep "invalid is not a valid engine"

docker run -d --restart=always nginx
docker ps | grep nginx`)

	s.CheckCall(c, `
set -ex

sudo ros engine switch docker-1.13.1
/usr/sbin/wait-for-docker
docker version | grep 1.13.1
sudo ros engine list | grep 1.13.1 | grep current
docker ps | grep nginx`)

	s.Reboot(c)

	s.CheckCall(c, `
set -ex

docker version | grep 1.13.1
sudo ros engine list | grep 1.13.1 | grep current
docker ps | grep nginx`)
}

func (s *QemuSuite) TestCustomDockerInPersistentConsole(c *C) {
	s.RunQemu(c, "--cloud-config", "./tests/assets/test_25/cloud-config.yml")

	s.CheckCall(c, `
set -ex

apt-get --version
docker version | grep 17.06.0-ce
sudo ros engine list | grep 17.06.0-ce | grep current
docker run -d --restart=always nginx
docker ps | grep nginx`)

	s.CheckCall(c, `
set -ex

sudo ros engine switch docker-1.12.6
/usr/sbin/wait-for-docker
docker version | grep 1.12.6
sudo ros engine list | grep 1.12.6 | grep current
docker ps | grep nginx`)

	s.Reboot(c)

	s.CheckCall(c, `
set -ex

docker version | grep 1.12.6
sudo ros engine list | grep 1.12.6 | grep current
docker ps | grep nginx`)
}
<commit_msg>Add a little more sleep between tests<commit_after>package integration

import . "gopkg.in/check.v1"

func (s *QemuSuite) TestCustomDocker(c *C) {
	s.RunQemu(c, "--cloud-config", "./tests/assets/test_05/cloud-config.yml")

	s.CheckCall(c, `
set -ex

docker version | grep 1.12.6

sudo ros engine list | grep 1.12.6 | grep current
(sudo ros engine switch invalid 2>&1 || true) | grep "invalid is not a valid engine"
(sudo ros engine enable invalid 2>&1 || true) | grep "invalid is not a valid engine"

docker run -d --restart=always nginx
docker ps | grep nginx`)

	s.CheckCall(c, `
set -ex

sudo ros engine switch docker-1.13.1
/usr/sbin/wait-for-docker
docker version | grep 1.13.1
sudo ros engine list | grep 1.13.1 | grep current
docker ps | grep nginx`)

	s.Reboot(c)

	s.CheckCall(c, `
set -ex

docker version | grep 1.13.1
sudo ros engine list | grep 1.13.1 | grep current
docker ps | grep nginx`)
}

func (s *QemuSuite) TestCustomDockerInPersistentConsole(c *C) {
	s.RunQemu(c, "--cloud-config", "./tests/assets/test_25/cloud-config.yml")

	s.CheckCall(c, `
set -ex

apt-get --version
docker version | grep 17.06.1-ce
sudo ros engine list | grep 17.06.1-ce | grep current
docker run -d --restart=always nginx
docker ps | grep nginx`)

	s.CheckCall(c, `
set -ex

sudo ros engine switch docker-1.12.6
/usr/sbin/wait-for-docker
docker version | grep 1.12.6
sudo ros engine list | grep 1.12.6 | grep current
docker ps | grep nginx`)

	s.Reboot(c)

	s.CheckCall(c, `
set -ex

docker version | grep 1.12.6
sudo ros engine list | grep 1.12.6 | grep current
docker ps | grep nginx`)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"unicode"
)

func supportedProtocol(proto string) bool {
	switch strings.ToUpper(proto) {
	case "TELNET", "SSH":
		return true
	default:
		return false
	}
}

// Using the phonebook mapping, fake out dialing a standard phone number
// (ATDT5551212)
func dialNumber(phone string) (connection, error) {

	host, protocol, username, password, err := phonebook.Lookup(phone)
	if err != nil {
		return nil, err
	}

	logger.Printf("Dialing address book entry: %+v", host)

	if !supportedProtocol(protocol) {
		return nil, fmt.Errorf("Unsupported protocol '%s'", protocol)
	}

	switch strings.ToUpper(protocol) {
	case "SSH":
		return dialSSH(host, logger, username, password)
	case "TELNET":
		return dialTelnet(host, logger)
	}
	return nil, fmt.Errorf("Unknown protocol")
}

func dialStoredNumber(idxstr string) (connection, error) {

	index, err := strconv.Atoi(idxstr)
	if err != nil {
		logger.Print(err)
		return nil, err
	}

	phone, err := phonebook.LookupStoredNumber(index)
	if err != nil {
		logger.Print("Error: ", err)
		return nil, ERROR // We want ATDS to return ERROR.
	}
	logger.Print("-- phone number ", phone)
	return dialNumber(phone)
}

// Returns host|username|password
func splitATDE(cmd string) (string, string, string, error) {
	s := strings.Split(cmd, "|")
	if len(s) != 3 {
		return "", "", "", fmt.Errorf("Malformated ATDE command")
	}
	return s[0], s[1], s[2], nil
}

// ATD command (ATD, ATDT, ATDP, ATDL and the extensions ATDH (host) and ATDE (SSH)
// See http://www.messagestick.net/modem/Hayes_Ch1-1.html on ATD... result codes
func dial(to string) error {
	var conn connection
	var err error
	var clean_to string

	pickup()

	cmd := to[1]
	if cmd == 'L' {
		return dial(m.lastDialed)
	}

	// Now we know the dial command isn't Dial Last (ATDL), save
	// this number as last dialed
	m.lastDialed = to

	// Strip out dial modifiers we don't need.
	r := strings.NewReplacer(
		",", "",
		"@", "",
		"W", "",
		" ", "",
		"!", "",
		";", "")

	// Is this ATD<number>?  If so, dial it
	if unicode.IsDigit(rune(cmd)) {
		clean_to = r.Replace(to[1:])
		conn, err = dialNumber(clean_to)
	} else { // ATD<modifier>

		clean_to = r.Replace(to[2:])

		switch cmd {
		case 'H': // Hostname (ATDH hostname)
			logger.Print("Opening telnet connection to: ", clean_to)
			conn, err = dialTelnet(clean_to, logger)
		case 'E': // Encrypted host (ATDE hostname)
			logger.Print("Opening SSH connection to: ", clean_to)
			host, user, pw, e := splitATDE(clean_to)
			if e != nil {
				logger.Print(e)
				conn = nil
				err = e
			} else {
				conn, err = dialSSH(host, logger, user, pw)
			}
		case 'T', 'P': // Fake number from address book (ATDT 5551212)
			logger.Print("Dialing fake number: ", clean_to)
			conn, err = dialNumber(clean_to)
		case 'S': // Stored number (ATDS3)
			conn, err = dialStoredNumber(clean_to[1:])
		default:
			logger.Printf("Dial mode '%c' not supported\n", cmd)
			hangup()
			err = fmt.Errorf("Dial mode '%c' not supported", cmd)
		}
	}

	// if we're connected, setup the connected state in the modem,
	// otherwise return a BUSY or NO_ANSWER result code.
	if err != nil {
		hangup()
		if err == ERROR {
			return ERROR
		}
		if err, ok := err.(net.Error); ok && err.Timeout() {
			return NO_ANSWER
		}
		return BUSY
	}

	// By default, conn.Mode() will return DATAMODE here.
	// Override and stay in command mode if ; present in the
	// original command string
	err = CONNECT
	m.connectSpeed = 38400  // We only go fast...
	if strings.Contains(to, ";") {
		conn.SetMode(COMMANDMODE)
		err = OK
	}

	// Remote answered, hand off conneciton to handleCalls()
	callChannel <- conn
	return err
}

func parseDial(cmd string) (string, int, error) {
	var s string
	var c int

	if len(cmd) <= 1 {
		return "", 0, fmt.Errorf("Bad/unsupported dial command: %s", cmd)
	}

	c = 1 // Skip the 'D'

	// Parse 'ATD555555'
	if unicode.IsDigit(rune(cmd[c])) {
		e := strings.LastIndexAny(cmd, "0123456789,;@!")
		if e == -1 {
			return "", 0, fmt.Errorf("Bad phone number: %s", cmd)
		}
		s = fmt.Sprintf("D%s", cmd[1:e+1])
		return s, len(s), nil
	}

	switch cmd[c] {
	case 'T', 't', 'P', 'p': // Number dialing
		e := strings.LastIndexAny(cmd, "0123456789,;@!")
		if e == -1 {
			return "", 0, fmt.Errorf("Bad phone number: %s", cmd)
		}
		s = fmt.Sprintf("DT%s", cmd[2:e+1])
		return s, len(s), nil
	case 'H', 'h': // Host Dialing
		s = fmt.Sprintf("DH%s", cmd[c+1:])
		return s, len(s), nil
	case 'E', 'e': // Encrypted host Dialing
		s = fmt.Sprintf("DE%s", cmd[c+1:])
		return s, len(s), nil
	case 'L', 'l': // Dial last number
		s = fmt.Sprintf("DL")
		return s, len(s), nil
	case 'S', 's': // Dial stored number
		s = fmt.Sprintf("DS%s", cmd[c+1:])
		return s, len(s), nil
	}

	return "", 0, fmt.Errorf("Bad/unsupported dial command: %s", cmd)
}
<commit_msg>Fix string parsing bug in dialStoredNumber()<commit_after>package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"unicode"
)

func supportedProtocol(proto string) bool {
	switch strings.ToUpper(proto) {
	case "TELNET", "SSH":
		return true
	default:
		return false
	}
}

// Using the phonebook mapping, fake out dialing a standard phone number
// (ATDT5551212)
func dialNumber(phone string) (connection, error) {

	host, protocol, username, password, err := phonebook.Lookup(phone)
	if err != nil {
		return nil, err
	}

	logger.Printf("Dialing address book entry: %+v", host)

	if !supportedProtocol(protocol) {
		return nil, fmt.Errorf("Unsupported protocol '%s'", protocol)
	}

	switch strings.ToUpper(protocol) {
	case "SSH":
		return dialSSH(host, logger, username, password)
	case "TELNET":
		return dialTelnet(host, logger)
	}
	return nil, fmt.Errorf("Unknown protocol")
}

func dialStoredNumber(idxstr string) (connection, error) {

	index, err := strconv.Atoi(idxstr)
	if err != nil {
		logger.Print(err)
		return nil, err
	}

	phone, err := phonebook.LookupStoredNumber(index)
	if err != nil {
		logger.Print("Error: ", err)
		return nil, ERROR // We want ATDS to return ERROR.
	}
	logger.Print("-- phone number ", phone)
	return dialNumber(phone)
}

// Returns host|username|password
func splitATDE(cmd string) (string, string, string, error) {
	s := strings.Split(cmd, "|")
	if len(s) != 3 {
		return "", "", "", fmt.Errorf("Malformated ATDE command")
	}
	return s[0], s[1], s[2], nil
}

// ATD command (ATD, ATDT, ATDP, ATDL and the extensions ATDH (host) and ATDE (SSH)
// See http://www.messagestick.net/modem/Hayes_Ch1-1.html on ATD... result codes
func dial(to string) error {
	var conn connection
	var err error
	var clean_to string

	pickup()

	cmd := to[1]
	if cmd == 'L' {
		return dial(m.lastDialed)
	}

	// Now we know the dial command isn't Dial Last (ATDL), save
	// this number as last dialed
	m.lastDialed = to

	// Strip out dial modifiers we don't need.
	r := strings.NewReplacer(
		",", "",
		"@", "",
		"W", "",
		" ", "",
		"!", "",
		";", "")

	// Is this ATD<number>?  If so, dial it
	if unicode.IsDigit(rune(cmd)) {
		clean_to = r.Replace(to[1:])
		conn, err = dialNumber(clean_to)
	} else { // ATD<modifier>

		clean_to = r.Replace(to[2:])

		switch cmd {
		case 'H': // Hostname (ATDH hostname)
			logger.Print("Opening telnet connection to: ", clean_to)
			conn, err = dialTelnet(clean_to, logger)
		case 'E': // Encrypted host (ATDE hostname)
			logger.Print("Opening SSH connection to: ", clean_to)
			host, user, pw, e := splitATDE(clean_to)
			if e != nil {
				logger.Print(e)
				conn = nil
				err = e
			} else {
				conn, err = dialSSH(host, logger, user, pw)
			}
		case 'T', 'P': // Fake number from address book (ATDT 5551212)
			logger.Print("Dialing fake number: ", clean_to)
			conn, err = dialNumber(clean_to)
		case 'S': // Stored number (ATDS3)
			conn, err = dialStoredNumber(clean_to)
		default:
			logger.Printf("Dial mode '%c' not supported\n", cmd)
			hangup()
			err = fmt.Errorf("Dial mode '%c' not supported", cmd)
		}
	}

	// if we're connected, setup the connected state in the modem,
	// otherwise return a BUSY or NO_ANSWER result code.
	if err != nil {
		hangup()
		if err == ERROR {
			return ERROR
		}
		if err, ok := err.(net.Error); ok && err.Timeout() {
			return NO_ANSWER
		}
		return BUSY
	}

	// By default, conn.Mode() will return DATAMODE here.
	// Override and stay in command mode if ; present in the
	// original command string
	err = CONNECT
	m.connectSpeed = 38400  // We only go fast...
	if strings.Contains(to, ";") {
		conn.SetMode(COMMANDMODE)
		err = OK
	}

	// Remote answered, hand off conneciton to handleCalls()
	callChannel <- conn
	return err
}

func parseDial(cmd string) (string, int, error) {
	var s string
	var c int

	if len(cmd) <= 1 {
		return "", 0, fmt.Errorf("Bad/unsupported dial command: %s", cmd)
	}

	c = 1 // Skip the 'D'

	// Parse 'ATD555555'
	if unicode.IsDigit(rune(cmd[c])) {
		e := strings.LastIndexAny(cmd, "0123456789,;@!")
		if e == -1 {
			return "", 0, fmt.Errorf("Bad phone number: %s", cmd)
		}
		s = fmt.Sprintf("D%s", cmd[1:e+1])
		return s, len(s), nil
	}

	switch cmd[c] {
	case 'T', 't', 'P', 'p': // Number dialing
		e := strings.LastIndexAny(cmd, "0123456789,;@!")
		if e == -1 {
			return "", 0, fmt.Errorf("Bad phone number: %s", cmd)
		}
		s = fmt.Sprintf("DT%s", cmd[2:e+1])
		return s, len(s), nil
	case 'H', 'h': // Host Dialing
		s = fmt.Sprintf("DH%s", cmd[c+1:])
		return s, len(s), nil
	case 'E', 'e': // Encrypted host Dialing
		s = fmt.Sprintf("DE%s", cmd[c+1:])
		return s, len(s), nil
	case 'L', 'l': // Dial last number
		s = fmt.Sprintf("DL")
		return s, len(s), nil
	case 'S', 's': // Dial stored number
		s = fmt.Sprintf("DS%s", cmd[c+1:])
		return s, len(s), nil
	}

	return "", 0, fmt.Errorf("Bad/unsupported dial command: %s", cmd)
}
<|endoftext|>


<commit_before>package docker

import (
	"testing"
	"time"

	godocker "github.com/fsouza/go-dockerclient"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestExportImage(t *testing.T) {
	mockDocker := new(mockDockerclient)

	containerID := "container ID"

	mockDocker.On("CreateContainer", mock.Anything).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.On("ExportContainer", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
		id := args.Get(0).(godocker.ExportContainerOptions).ID
		assert.Equal(t, id, containerID)
	})
	mockDocker.On("RemoveContainer", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
		id := args.Get(0).(godocker.RemoveContainerOptions).ID
		force := args.Get(0).(godocker.RemoveContainerOptions).Force
		assert.Equal(t, id, containerID)
		assert.Equal(t, force, true)
	})

	client := &Client{docker: mockDocker}
	r, err := client.ExportImage("aaaaaaaaaaaa")
	defer r.Close()

	time.Sleep(10 * time.Millisecond) // wait for finishing goroutine

	assert.NoError(t, err)
}
<commit_msg>Fix test failure<commit_after>package docker

import (
	"testing"
	"time"

	godocker "github.com/fsouza/go-dockerclient"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestExportImage(t *testing.T) {
	mockDocker := new(mockDockerclient)

	containerID := "container ID"

	mockDocker.On("CreateContainer", mock.Anything).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.On("StartContainer", containerID, mock.Anything).Return(nil)
	mockDocker.On("WaitContainer", containerID).Return(0, nil)
	mockDocker.On("ExportContainer", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
		id := args.Get(0).(godocker.ExportContainerOptions).ID
		assert.Equal(t, id, containerID)
	})
	mockDocker.On("RemoveContainer", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
		id := args.Get(0).(godocker.RemoveContainerOptions).ID
		force := args.Get(0).(godocker.RemoveContainerOptions).Force
		assert.Equal(t, id, containerID)
		assert.Equal(t, force, true)
	})

	client := &Client{docker: mockDocker}
	r, err := client.ExportImage("aaaaaaaaaaaa")
	defer r.Close()

	time.Sleep(10 * time.Millisecond) // wait for finishing goroutine

	assert.NoError(t, err)
}
<|endoftext|>


<commit_before>package factory

import (
	"testing"

	"github.com/diyan/assimilator/db"
	"github.com/diyan/assimilator/db/store"
	"github.com/diyan/assimilator/models"

	"github.com/gocraft/dbr"
	"github.com/labstack/echo"
	"github.com/stretchr/testify/require"
)

type TestFactory struct {
	t                *testing.T
	tx               *dbr.Tx
	Reset            func()
	SaveOrganization func(org models.Organization)
	SaveProject      func(project models.Project)
	SaveTags         func(tags ...*models.TagKey)
}

func New(t *testing.T, server *echo.Echo) TestFactory {
	noError := require.New(t).NoError
	ctx := server.NewContext(nil, nil)
	// TODO remove hack
	ctx.Set("conf.Config", MakeAppConfig())
	tx, err := db.GetTx(ctx)
	noError(err)
	tf := TestFactory{
		t:  t,
		tx: tx,
	}
	tf.Reset = func() {
		err := tf.tx.Rollback()
		noError(err)
	}

	// TODO Tricky implementation. Mock *dbr.Tx in the test Echo instance
	server.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			c.Set("dbr.Tx", tx)
			return next(c)
		}
	})

	orgStore := store.NewOrganizationStore(ctx)
	projectStore := store.NewProjectStore(ctx)
	tf.SaveOrganization = func(org models.Organization) {
		noError(orgStore.SaveOrganization(org))
	}
	tf.SaveProject = func(project models.Project) {
		noError(projectStore.SaveProject(project))
	}
	tf.SaveTags = func(tags ...*models.TagKey) {
		noError(projectStore.SaveTags(tags...))
	}
	return tf
}
<commit_msg>Refactor TestFactory<commit_after>package factory

import (
	"testing"

	"github.com/diyan/assimilator/db"
	"github.com/diyan/assimilator/db/store"
	"github.com/diyan/assimilator/models"

	"github.com/gocraft/dbr"
	"github.com/labstack/echo"
	"github.com/stretchr/testify/require"
)

type TestFactory struct {
	t   *testing.T
	tx  *dbr.Tx
	ctx echo.Context
}

func New(t *testing.T, server *echo.Echo) TestFactory {
	noError := require.New(t).NoError
	tx, err := db.New(MakeAppConfig())
	noError(err)
	MockDB(server, tx)
	ctx := server.NewContext(nil, nil)
	db.ToE(ctx, tx)
	tf := TestFactory{
		t:   t,
		tx:  tx,
		ctx: ctx,
	}
	return tf
}

func (tf TestFactory) noError(err error, msgAndArgs ...interface{}) {
	require.New(tf.t).NoError(err, msgAndArgs)
}

func (tf TestFactory) Reset() {
	err := tf.tx.Rollback()
	tf.noError(err)
}

func (tf TestFactory) SaveOrganization(org models.Organization) {
	orgStore := store.NewOrganizationStore(tf.ctx)
	tf.noError(orgStore.SaveOrganization(org))
}

func (tf TestFactory) SaveProject(project models.Project) {
	projectStore := store.NewProjectStore(tf.ctx)
	tf.noError(projectStore.SaveProject(project))
}

func (tf TestFactory) SaveTags(tags ...*models.TagKey) {
	projectStore := store.NewProjectStore(tf.ctx)
	tf.noError(projectStore.SaveTags(tags...))
}

// MockDB adds early middleware that mock DB transaction to the test Echo instance
// TODO consider move this to the db or db_test package
func MockDB(server *echo.Echo, tx *dbr.Tx) {
	server.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			db.ToE(c, tx)
			return next(c)
		}
	})
}
<|endoftext|>


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

package graph

import "golang.org/x/net/context"

// TODO(jacobsa): Comments.
type Visistor interface {
	// TODO(jacobsa): Comments.
	Visit(ctx context.Context, node string) (adjacent []string, err error)
}

// TODO(jacobsa): Comments.
func Traverse(
	ctx context.Context,
	roots []string,
	visitor Visistor) (err error)
<commit_msg>Added documentation.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graph

import "golang.org/x/net/context"

type Visitor interface {
	// Process the supplied node and return a list of adjacent nodes.
	Visit(ctx context.Context, node string) (adjacent []string, err error)
}

// Invoke v.Visit on each node in the connected graph(s) containing the
// supplied search root nodes, whose edges are defined by the output of
// v.Visit. Use the supplied degree of parallelism.
//
// It is guaranteed that if a node N is fed to v.Visit, then either:
//
//  *  N is an element of roots, or
//  *  There exists an adjacent node N' such that v.Visit(N') was called and
//     returned successfully.
//
// In particular, if the graph is a rooted tree and searching starts at the
// root, then parents will be successfully visited before children are visited.
func Traverse(
	ctx context.Context,
	parallelism int,
	roots []string,
	v Visitor) (err error)
<|endoftext|>


<commit_before>package httpClient

import (
	"net/http"
	"time"

	"github.com/koffeinsource/kaffeeshare/data"
	"golang.org/x/net/context"
	"google.golang.org/appengine/urlfetch"
)

// Get creates an HTTP client for the GAE
func Get(con *data.Context) *http.Client {
	var timeout time.Time
	timeout = time.Now().Add(60 * time.Second)
	c, _ := context.WithDeadline(con.C, timeout)
	s := &urlfetch.Transport{
		Context: c,
		//AllowInvalidServerCertificate: true,
	}
	h := &http.Client{
		Transport: s,
	}
	return h
}

// GetWithLongDeadline creates an HTTP client with a long deadline
func GetWithLongDeadline(con *data.Context) *http.Client {
	var timeout time.Time
	timeout = time.Now().Add(60 * time.Second)
	c, _ := context.WithDeadline(con.C, timeout)
	s := &urlfetch.Transport{
		Context: c,
		//AllowInvalidServerCertificate: true,
	}
	h := &http.Client{
		Transport: s,
	}
	return h
}
<commit_msg>Re-enabled invalid https certificates.<commit_after>package httpClient

import (
	"net/http"
	"time"

	"github.com/koffeinsource/kaffeeshare/data"
	"golang.org/x/net/context"
	"google.golang.org/appengine/urlfetch"
)

// TODO extract urlfetch.Transport in own function

// Get creates an HTTP client for the GAE
func Get(con *data.Context) *http.Client {
	var timeout time.Time
	timeout = time.Now().Add(60 * time.Second)
	c, _ := context.WithDeadline(con.C, timeout)
	s := &urlfetch.Transport{
		Context: c,
		AllowInvalidServerCertificate: true,
	}
	h := &http.Client{
		Transport: s,
	}
	return h
}

// GetWithLongDeadline creates an HTTP client with a long deadline
func GetWithLongDeadline(con *data.Context) *http.Client {
	var timeout time.Time
	timeout = time.Now().Add(60 * time.Second)
	c, _ := context.WithDeadline(con.C, timeout)
	s := &urlfetch.Transport{
		Context: c,
		AllowInvalidServerCertificate: true,
	}
	h := &http.Client{
		Transport: s,
	}
	return h
}
<|endoftext|>


<commit_before>// Package httputil offers functions to read and download files via HTTP.
package httputil

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

var (
	// DefaultTransport specifies the http.RoundTripper that is used for any network traffic, and may be replaced with a dummy implementation for unit testing.
	DefaultTransport = http.DefaultTransport
	UserAgent = "Bazelisk"
	linkPattern = regexp.MustCompile(`<(.*?)>; rel="(\w+)"`)

	RetryClock = Clock(&realClock{})
	MaxRetries = 4
	// MaxRequestDuration defines the maximum amount of time that a request and its retries may take in total
	MaxRequestDuration = time.Second * 10
	retryHeaders = []string{"Retry-After", "X-RateLimit-Reset", "Rate-Limit-Reset"}
)

type Clock interface {
	Sleep(time.Duration)
	Now() time.Time
}

type realClock struct {}

func (*realClock) Sleep(d time.Duration) {
	time.Sleep(d)
}

func (*realClock) Now() time.Time {
	return time.Now()
}

// ReadRemoteFile returns the contents of the given file, using the supplied Authorization token, if set. It also returns the HTTP headers.
// If the request fails with a transient error it will retry the request for at most MaxRetries times.
// It obeys HTTP headers such as "Retry-After" when calculating the start time of the next attempt.
// If no such header is present, it uses an exponential backoff strategy.
func ReadRemoteFile(url string, token string) ([]byte, http.Header, error) {
	res, err := get(url, token)
	if err != nil {
		return nil, nil, fmt.Errorf("could not fetch %s: %v", url, err)
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return nil, res.Header, fmt.Errorf("unexpected status code while reading %s: %v", url, res.StatusCode)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, res.Header, fmt.Errorf("failed to read content at %s: %v", url, err)
	}
	return body, res.Header, nil
}

func get(url, token string) (*http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("could not create request: %v", err)
	}

	req.Header.Set("User-Agent", UserAgent)
	if token != "" {
		req.Header.Set("Authorization", "token "+token)
	}
	client := &http.Client{Transport: DefaultTransport}
	deadline := RetryClock.Now().Add(MaxRequestDuration)
	lastStatus := 0
	for attempt := 0; attempt <= MaxRetries; attempt++ {
		res, err := client.Do(req)
		// Do not retry on success and permanent/fatal errors
		if err != nil || !shouldRetry(res) {
			return res, err
		}

		lastStatus = res.StatusCode
		waitFor, err := getWaitPeriod(res, attempt)
		if err != nil {
			return nil, err
		}

		nextTryAt := RetryClock.Now().Add(waitFor)
		if nextTryAt.After(deadline) {
			return nil, fmt.Errorf("unable to complete request to %s within %v", url, MaxRequestDuration)
		}
		if attempt < MaxRetries {
			RetryClock.Sleep(waitFor)
		}
	}
	return nil, fmt.Errorf("unable to complete request to %s after %d retries. Most recent status: %d", url, MaxRetries, lastStatus)
}

func shouldRetry(res *http.Response) bool {
	return res.StatusCode == 429 || (500 <= res.StatusCode && res.StatusCode <= 504)
}

func getWaitPeriod(res *http.Response, attempt int) (time.Duration, error) {
	// Check if the server told us when to retry
	for _, header := range retryHeaders {
		if value := res.Header[header]; len(value) > 0 {
			return parseRetryHeader(value[0])
		}
	}
	// Let's just use exponential backoff: 1s + d1, 2s + d2, 4s + d3, 8s + d4 with dx being a random value in [0ms, 500ms]
	return time.Duration(1 << attempt) * time.Second + time.Duration(rand.Intn(500)) * time.Millisecond, nil
}

func parseRetryHeader(value string) (time.Duration, error) {
	// Depending on the server the header value can be a number of seconds (how long to wait) or an actual date (when to retry).
	if seconds, err := strconv.Atoi(value); err == nil {
		return time.Second * time.Duration(seconds), nil
	}
	t, err := http.ParseTime(value)
	if err != nil {
		return 0, err
	}
	return time.Until(t), nil
}

// DownloadBinary downloads a file from the given URL into the specified location, marks it executable and returns its full path.
func DownloadBinary(originURL, destDir, destFile string) (string, error) {
	err := os.MkdirAll(destDir, 0755)
	if err != nil {
		return "", fmt.Errorf("could not create directory %s: %v", destDir, err)
	}
	destinationPath := filepath.Join(destDir, destFile)

	if _, err := os.Stat(destinationPath); err != nil {
		tmpfile, err := ioutil.TempFile(destDir, "download")
		if err != nil {
			return "", fmt.Errorf("could not create temporary file: %v", err)
		}
		defer func() {
			err := tmpfile.Close()
			if err == nil {
				os.Remove(tmpfile.Name())
			}
		}()

		log.Printf("Downloading %s...", originURL)
		resp, err := get(originURL, "")
		if err != nil {
			return "", fmt.Errorf("HTTP GET %s failed: %v", originURL, err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			return "", fmt.Errorf("HTTP GET %s failed with error %v", originURL, resp.StatusCode)
		}

		_, err = io.Copy(tmpfile, resp.Body)
		if err != nil {
			return "", fmt.Errorf("could not copy from %s to %s: %v", originURL, tmpfile.Name(), err)
		}

		err = os.Chmod(tmpfile.Name(), 0755)
		if err != nil {
			return "", fmt.Errorf("could not chmod file %s: %v", tmpfile.Name(), err)
		}

		tmpfile.Close()
		err = os.Rename(tmpfile.Name(), destinationPath)
		if err != nil {
			return "", fmt.Errorf("could not move %s to %s: %v", tmpfile.Name(), destinationPath, err)
		}
	}

	return destinationPath, nil
}

type ContentMerger func([][]byte) ([]byte, error)

// MaybeDownload downloads a file from the given url and caches the result under bazeliskHome.
// It skips the download if the file already exists and is not outdated.
// Parameter ´description´ is only used to provide better error messages.
func MaybeDownload(bazeliskHome, url, filename, description, token string, merger ContentMerger) ([]byte, error) {
	cachePath := filepath.Join(bazeliskHome, filename)
	if cacheStat, err := os.Stat(cachePath); err == nil {
		if time.Since(cacheStat.ModTime()).Hours() < 1 {
			res, err := ioutil.ReadFile(cachePath)
			if err != nil {
				return nil, fmt.Errorf("could not read %s: %v", cachePath, err)
			}
			return res, nil
		}
	}

	contents := make([][]byte, 0)
	nextUrl := url
	for nextUrl != "" {
		// We could also use go-github here, but I can't get it to build with Bazel's rules_go and it pulls in a lot of dependencies.
		body, headers, err := ReadRemoteFile(nextUrl, token)
		if err != nil {
			return nil, fmt.Errorf("could not download %s: %v", description, err)
		}
		contents = append(contents, body)
		nextUrl = getNextUrl(headers)
	}

	merged, err := merger(contents)
	if err != nil {
		return nil, fmt.Errorf("failed to merge %d chunks from %s: %v", len(contents), url, err)
	}

	err = ioutil.WriteFile(cachePath, merged, 0666)
	if err != nil {
		return nil, fmt.Errorf("could not create %s: %v", cachePath, err)
	}

	return merged, nil
}

func getNextUrl(headers http.Header) string {
	links := headers["Link"]
	if len(links) != 1 {
		return ""
	}
	for _, m := range linkPattern.FindAllStringSubmatch(links[0], -1) {
		if m[2] == "next" {
			return m[1]
		}
	}
	return ""
}
<commit_msg>Increase httputil.MaxRequestDuration to 30s (#257)<commit_after>// Package httputil offers functions to read and download files via HTTP.
package httputil

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

var (
	// DefaultTransport specifies the http.RoundTripper that is used for any network traffic, and may be replaced with a dummy implementation for unit testing.
	DefaultTransport = http.DefaultTransport
	UserAgent = "Bazelisk"
	linkPattern = regexp.MustCompile(`<(.*?)>; rel="(\w+)"`)

	RetryClock = Clock(&realClock{})
	MaxRetries = 4
	// MaxRequestDuration defines the maximum amount of time that a request and its retries may take in total
	MaxRequestDuration = time.Second * 30
	retryHeaders = []string{"Retry-After", "X-RateLimit-Reset", "Rate-Limit-Reset"}
)

type Clock interface {
	Sleep(time.Duration)
	Now() time.Time
}

type realClock struct {}

func (*realClock) Sleep(d time.Duration) {
	time.Sleep(d)
}

func (*realClock) Now() time.Time {
	return time.Now()
}

// ReadRemoteFile returns the contents of the given file, using the supplied Authorization token, if set. It also returns the HTTP headers.
// If the request fails with a transient error it will retry the request for at most MaxRetries times.
// It obeys HTTP headers such as "Retry-After" when calculating the start time of the next attempt.
// If no such header is present, it uses an exponential backoff strategy.
func ReadRemoteFile(url string, token string) ([]byte, http.Header, error) {
	res, err := get(url, token)
	if err != nil {
		return nil, nil, fmt.Errorf("could not fetch %s: %v", url, err)
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return nil, res.Header, fmt.Errorf("unexpected status code while reading %s: %v", url, res.StatusCode)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, res.Header, fmt.Errorf("failed to read content at %s: %v", url, err)
	}
	return body, res.Header, nil
}

func get(url, token string) (*http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("could not create request: %v", err)
	}

	req.Header.Set("User-Agent", UserAgent)
	if token != "" {
		req.Header.Set("Authorization", "token "+token)
	}
	client := &http.Client{Transport: DefaultTransport}
	deadline := RetryClock.Now().Add(MaxRequestDuration)
	lastStatus := 0
	for attempt := 0; attempt <= MaxRetries; attempt++ {
		res, err := client.Do(req)
		// Do not retry on success and permanent/fatal errors
		if err != nil || !shouldRetry(res) {
			return res, err
		}

		lastStatus = res.StatusCode
		waitFor, err := getWaitPeriod(res, attempt)
		if err != nil {
			return nil, err
		}

		nextTryAt := RetryClock.Now().Add(waitFor)
		if nextTryAt.After(deadline) {
			return nil, fmt.Errorf("unable to complete request to %s within %v", url, MaxRequestDuration)
		}
		if attempt < MaxRetries {
			RetryClock.Sleep(waitFor)
		}
	}
	return nil, fmt.Errorf("unable to complete request to %s after %d retries. Most recent status: %d", url, MaxRetries, lastStatus)
}

func shouldRetry(res *http.Response) bool {
	return res.StatusCode == 429 || (500 <= res.StatusCode && res.StatusCode <= 504)
}

func getWaitPeriod(res *http.Response, attempt int) (time.Duration, error) {
	// Check if the server told us when to retry
	for _, header := range retryHeaders {
		if value := res.Header[header]; len(value) > 0 {
			return parseRetryHeader(value[0])
		}
	}
	// Let's just use exponential backoff: 1s + d1, 2s + d2, 4s + d3, 8s + d4 with dx being a random value in [0ms, 500ms]
	return time.Duration(1 << attempt) * time.Second + time.Duration(rand.Intn(500)) * time.Millisecond, nil
}

func parseRetryHeader(value string) (time.Duration, error) {
	// Depending on the server the header value can be a number of seconds (how long to wait) or an actual date (when to retry).
	if seconds, err := strconv.Atoi(value); err == nil {
		return time.Second * time.Duration(seconds), nil
	}
	t, err := http.ParseTime(value)
	if err != nil {
		return 0, err
	}
	return time.Until(t), nil
}

// DownloadBinary downloads a file from the given URL into the specified location, marks it executable and returns its full path.
func DownloadBinary(originURL, destDir, destFile string) (string, error) {
	err := os.MkdirAll(destDir, 0755)
	if err != nil {
		return "", fmt.Errorf("could not create directory %s: %v", destDir, err)
	}
	destinationPath := filepath.Join(destDir, destFile)

	if _, err := os.Stat(destinationPath); err != nil {
		tmpfile, err := ioutil.TempFile(destDir, "download")
		if err != nil {
			return "", fmt.Errorf("could not create temporary file: %v", err)
		}
		defer func() {
			err := tmpfile.Close()
			if err == nil {
				os.Remove(tmpfile.Name())
			}
		}()

		log.Printf("Downloading %s...", originURL)
		resp, err := get(originURL, "")
		if err != nil {
			return "", fmt.Errorf("HTTP GET %s failed: %v", originURL, err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			return "", fmt.Errorf("HTTP GET %s failed with error %v", originURL, resp.StatusCode)
		}

		_, err = io.Copy(tmpfile, resp.Body)
		if err != nil {
			return "", fmt.Errorf("could not copy from %s to %s: %v", originURL, tmpfile.Name(), err)
		}

		err = os.Chmod(tmpfile.Name(), 0755)
		if err != nil {
			return "", fmt.Errorf("could not chmod file %s: %v", tmpfile.Name(), err)
		}

		tmpfile.Close()
		err = os.Rename(tmpfile.Name(), destinationPath)
		if err != nil {
			return "", fmt.Errorf("could not move %s to %s: %v", tmpfile.Name(), destinationPath, err)
		}
	}

	return destinationPath, nil
}

type ContentMerger func([][]byte) ([]byte, error)

// MaybeDownload downloads a file from the given url and caches the result under bazeliskHome.
// It skips the download if the file already exists and is not outdated.
// Parameter ´description´ is only used to provide better error messages.
func MaybeDownload(bazeliskHome, url, filename, description, token string, merger ContentMerger) ([]byte, error) {
	cachePath := filepath.Join(bazeliskHome, filename)
	if cacheStat, err := os.Stat(cachePath); err == nil {
		if time.Since(cacheStat.ModTime()).Hours() < 1 {
			res, err := ioutil.ReadFile(cachePath)
			if err != nil {
				return nil, fmt.Errorf("could not read %s: %v", cachePath, err)
			}
			return res, nil
		}
	}

	contents := make([][]byte, 0)
	nextUrl := url
	for nextUrl != "" {
		// We could also use go-github here, but I can't get it to build with Bazel's rules_go and it pulls in a lot of dependencies.
		body, headers, err := ReadRemoteFile(nextUrl, token)
		if err != nil {
			return nil, fmt.Errorf("could not download %s: %v", description, err)
		}
		contents = append(contents, body)
		nextUrl = getNextUrl(headers)
	}

	merged, err := merger(contents)
	if err != nil {
		return nil, fmt.Errorf("failed to merge %d chunks from %s: %v", len(contents), url, err)
	}

	err = ioutil.WriteFile(cachePath, merged, 0666)
	if err != nil {
		return nil, fmt.Errorf("could not create %s: %v", cachePath, err)
	}

	return merged, nil
}

func getNextUrl(headers http.Header) string {
	links := headers["Link"]
	if len(links) != 1 {
		return ""
	}
	for _, m := range linkPattern.FindAllStringSubmatch(links[0], -1) {
		if m[2] == "next" {
			return m[1]
		}
	}
	return ""
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/erroneousboat/termui"
	"github.com/nlopes/slack"
	termbox "github.com/nsf/termbox-go"

	"github.com/erroneousboat/slack-term/context"
	"github.com/erroneousboat/slack-term/views"
)

var timer *time.Timer

// actionMap binds specific action names to the function counterparts,
// these action names can then be used to bind them to specific keys
// in the Config.
var actionMap = map[string]func(*context.AppContext){
	"space":               actionSpace,
	"backspace":           actionBackSpace,
	"delete":              actionDelete,
	"cursor-right":        actionMoveCursorRight,
	"cursor-left":         actionMoveCursorLeft,
	"send":                actionSend,
	"quit":                actionQuit,
	"mode-insert":         actionInsertMode,
	"mode-command":        actionCommandMode,
	"mode-search":         actionSearchMode,
	"clear-input":         actionClearInput,
	"channel-up":          actionMoveCursorUpChannels,
	"channel-down":        actionMoveCursorDownChannels,
	"channel-top":         actionMoveCursorTopChannels,
	"channel-bottom":      actionMoveCursorBottomChannels,
	"channel-search-next": actionSearchNextChannels,
	"channel-search-prev": actionSearchPrevChannels,
	"chat-up":             actionScrollUpChat,
	"chat-down":           actionScrollDownChat,
	"help":                actionHelp,
}

func RegisterEventHandlers(ctx *context.AppContext) {
	eventHandler(ctx)
	messageHandler(ctx)
}

func eventHandler(ctx *context.AppContext) {
	go func() {
		for {
			ctx.EventQueue <- termbox.PollEvent()
		}
	}()

	go func() {
		for {
			ev := <-ctx.EventQueue
			handleTermboxEvents(ctx, ev)
			handleMoreTermboxEvents(ctx, ev)

			// Place your debugging statements here
			if ctx.Debug {
				ctx.View.Debug.Println(
					"event received",
				)
			}
		}
	}()
}

func handleTermboxEvents(ctx *context.AppContext, ev termbox.Event) bool {
	switch ev.Type {
	case termbox.EventKey:
		actionKeyEvent(ctx, ev)
	case termbox.EventResize:
		actionResizeEvent(ctx, ev)
	}

	return true
}

func handleMoreTermboxEvents(ctx *context.AppContext, ev termbox.Event) bool {
	for {
		select {
		case ev := <-ctx.EventQueue:
			ok := handleTermboxEvents(ctx, ev)
			if !ok {
				return false
			}
		default:
			return true
		}
	}
}

func messageHandler(ctx *context.AppContext) {
	go func() {
		for {
			select {
			case msg := <-ctx.Service.RTM.IncomingEvents:
				switch ev := msg.Data.(type) {
				case *slack.MessageEvent:
					// Construct message
					msg := ctx.Service.CreateMessageFromMessageEvent(ev)

					// Add message to the selected channel
					if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {

						// reverse order of messages, mainly done
						// when attachments are added to message
						for i := len(msg) - 1; i >= 0; i-- {
							ctx.View.Chat.AddMessage(
								msg[i].ToString(),
							)
						}

						termui.Render(ctx.View.Chat)

						// TODO: set Chat.Offset to 0, to automatically scroll
						// down?
					}

					// Set new message indicator for channel, I'm leaving
					// this here because I also want to be notified when
					// I'm currently in a channel but not in the terminal
					// window (tmux). But only create a notification when
					// it comes from someone else but the current user.
					if ev.User != ctx.Service.CurrentUserID {
						actionNewMessage(ctx, ev.Channel)
					}
				case *slack.PresenceChangeEvent:
					actionSetPresence(ctx, ev.User, ev.Presence)
				}
			}
		}
	}()
}

func actionKeyEvent(ctx *context.AppContext, ev termbox.Event) {

	keyStr := getKeyString(ev)

	// Get the action name (actionStr) from the key that
	// has been pressed. If this is found try to uncover
	// the associated function with this key and execute
	// it.
	actionStr, ok := ctx.Config.KeyMap[ctx.Mode][keyStr]
	if ok {
		action, ok := actionMap[actionStr]
		if ok {
			action(ctx)
		}
	} else {
		if ctx.Mode == context.InsertMode && ev.Ch != 0 {
			actionInput(ctx.View, ev.Ch)
		} else if ctx.Mode == context.SearchMode && ev.Ch != 0 {
			actionSearch(ctx, ev.Ch)
		}
	}
}

func actionResizeEvent(ctx *context.AppContext, ev termbox.Event) {
	termui.Body.Width = termui.TermWidth()

	// Vertical resize components
	ctx.View.Channels.List.Height = termui.TermHeight() - ctx.View.Input.Par.Height
	ctx.View.Chat.List.Height = termui.TermHeight() - ctx.View.Input.Par.Height
	ctx.View.Debug.List.Height = termui.TermHeight() - ctx.View.Input.Par.Height

	termui.Body.Align()
	termui.Render(termui.Body)
}

func actionInput(view *views.View, key rune) {
	view.Input.Insert(key)
	termui.Render(view.Input)
}

func actionClearInput(ctx *context.AppContext) {
	// Clear input
	ctx.View.Input.Clear()
	ctx.View.Refresh()

	// Set command mode
	actionCommandMode(ctx)
}

func actionSpace(ctx *context.AppContext) {
	actionInput(ctx.View, ' ')
}

func actionBackSpace(ctx *context.AppContext) {
	ctx.View.Input.Backspace()
	termui.Render(ctx.View.Input)
}

func actionDelete(ctx *context.AppContext) {
	ctx.View.Input.Delete()
	termui.Render(ctx.View.Input)
}

func actionMoveCursorRight(ctx *context.AppContext) {
	ctx.View.Input.MoveCursorRight()
	termui.Render(ctx.View.Input)
}

func actionMoveCursorLeft(ctx *context.AppContext) {
	ctx.View.Input.MoveCursorLeft()
	termui.Render(ctx.View.Input)
}

func actionSend(ctx *context.AppContext) {
	if !ctx.View.Input.IsEmpty() {

		// Clear message before sending, to combat
		// quick succession of actionSend
		message := ctx.View.Input.GetText()
		ctx.View.Input.Clear()
		ctx.View.Refresh()

		// Send message
		ctx.Service.SendMessage(
			ctx.View.Channels.SelectedChannel,
			message,
		)

		// Clear notification icon if there is any
		ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
		ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
		termui.Render(ctx.View.Channels)
	}
}

// actionSearch will search through the channels based on the users
// input. A time is implemented to make sure the actual searching
// and changing of channels is done when the user's typing is paused.
func actionSearch(ctx *context.AppContext, key rune) {
	actionInput(ctx.View, key)

	go func() {
		if timer != nil {
			timer.Stop()
		}

		timer = time.NewTimer(time.Second / 4)
		<-timer.C

		// Only actually search when the time expires
		term := ctx.View.Input.GetText()
		ctx.View.Channels.Search(term)
		actionChangeChannel(ctx)
	}()
}

// actionQuit will exit the program by using os.Exit, this is
// done because we are using a custom termui EvtStream. Which
// we won't be able to call termui.StopLoop() on. See main.go
// for the customEvtStream and why this is done.
func actionQuit(ctx *context.AppContext) {
	termbox.Close()
	os.Exit(0)
}

func actionInsertMode(ctx *context.AppContext) {
	ctx.Mode = context.InsertMode
	ctx.View.Mode.SetInsertMode()
}

func actionCommandMode(ctx *context.AppContext) {
	ctx.Mode = context.CommandMode
	ctx.View.Mode.SetCommandMode()
}

func actionSearchMode(ctx *context.AppContext) {
	ctx.Mode = context.SearchMode
	ctx.View.Mode.SetSearchMode()
}

func actionGetMessages(ctx *context.AppContext) {
	msgs := ctx.Service.GetMessages(
		ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
		ctx.View.Chat.GetMaxItems(),
	)

	var strMsgs []string
	for _, msg := range msgs {
		strMsgs = append(strMsgs, msg.ToString())
	}

	ctx.View.Chat.SetMessages(strMsgs)

	termui.Render(ctx.View.Chat)
}

// actionMoveCursorUpChannels will execute the actionChangeChannel
// function. A timer is implemented to support fast scrolling through
// the list without executing the actionChangeChannel event
func actionMoveCursorUpChannels(ctx *context.AppContext) {
	go func() {
		if timer != nil {
			timer.Stop()
		}

		ctx.View.Channels.MoveCursorUp()
		termui.Render(ctx.View.Channels)

		timer = time.NewTimer(time.Second / 4)
		<-timer.C

		// Only actually change channel when the timer expires
		actionChangeChannel(ctx)
	}()
}

// actionMoveCursorDownChannels will execute the actionChangeChannel
// function. A timer is implemented to support fast scrolling through
// the list without executing the actionChangeChannel event
func actionMoveCursorDownChannels(ctx *context.AppContext) {
	go func() {
		if timer != nil {
			timer.Stop()
		}

		ctx.View.Channels.MoveCursorDown()
		termui.Render(ctx.View.Channels)

		timer = time.NewTimer(time.Second / 4)
		<-timer.C

		// Only actually change channel when the timer expires
		actionChangeChannel(ctx)
	}()
}

func actionMoveCursorTopChannels(ctx *context.AppContext) {
	ctx.View.Channels.MoveCursorTop()
	actionChangeChannel(ctx)
}

func actionMoveCursorBottomChannels(ctx *context.AppContext) {
	ctx.View.Channels.MoveCursorBottom()
	actionChangeChannel(ctx)
}

func actionSearchNextChannels(ctx *context.AppContext) {
	ctx.View.Channels.SearchNext()
	actionChangeChannel(ctx)
}

func actionSearchPrevChannels(ctx *context.AppContext) {
	ctx.View.Channels.SearchPrev()
	actionChangeChannel(ctx)
}

func actionChangeChannel(ctx *context.AppContext) {
	// Clear messages from Chat pane
	ctx.View.Chat.ClearMessages()

	// Get messages of the SelectedChannel, and get the count of messages
	// that fit into the Chat component
	msgs := ctx.Service.GetMessages(
		ctx.Service.GetSlackChannel(ctx.View.Channels.SelectedChannel),
		ctx.View.Chat.GetMaxItems(),
	)

	var strMsgs []string
	for _, msg := range msgs {
		strMsgs = append(strMsgs, msg.ToString())
	}

	// Set messages for the channel
	ctx.View.Chat.SetMessages(strMsgs)

	// Set channel name for the Chat pane
	ctx.View.Chat.SetBorderLabel(
		ctx.Service.Channels[ctx.View.Channels.SelectedChannel].GetChannelName(),
	)

	// Clear notification icon if there is any
	ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
	ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())

	termui.Render(ctx.View.Channels)
	termui.Render(ctx.View.Chat)
}

func actionNewMessage(ctx *context.AppContext, channelID string) {
	ctx.Service.MarkAsUnread(channelID)
	ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
	termui.Render(ctx.View.Channels)
	fmt.Print("\a")
}

func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
	ctx.Service.SetPresenceChannelEvent(channelID, presence)
	ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
	termui.Render(ctx.View.Channels)
}

func actionScrollUpChat(ctx *context.AppContext) {
	ctx.View.Chat.ScrollUp()
	termui.Render(ctx.View.Chat)
}

func actionScrollDownChat(ctx *context.AppContext) {
	ctx.View.Chat.ScrollDown()
	termui.Render(ctx.View.Chat)
}

func actionHelp(ctx *context.AppContext) {
	ctx.View.Chat.Help(ctx.Config)
	termui.Render(ctx.View.Chat)
}

// GetKeyString will return a string that resembles the key event from
// termbox. This is blatanly copied from termui because it is an unexported
// function.
//
// See:
// - https://github.com/gizak/termui/blob/a7e3aeef4cdf9fa2edb723b1541cb69b7bb089ea/events.go#L31-L72
// - https://github.com/nsf/termbox-go/blob/master/api_common.go
func getKeyString(e termbox.Event) string {
	var ek string

	k := string(e.Ch)
	pre := ""
	mod := ""

	if e.Mod == termbox.ModAlt {
		mod = "M-"
	}
	if e.Ch == 0 {
		if e.Key > 0xFFFF-12 {
			k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
		} else if e.Key > 0xFFFF-25 {
			ks := []string{"<insert>", "<delete>", "<home>", "<end>", "<previous>", "<next>", "<up>", "<down>", "<left>", "<right>"}
			k = ks[0xFFFF-int(e.Key)-12]
		}

		if e.Key <= 0x7F {
			pre = "C-"
			k = string('a' - 1 + int(e.Key))
			kmap := map[termbox.Key][2]string{
				termbox.KeyCtrlSpace:     {"C-", "<space>"},
				termbox.KeyBackspace:     {"", "<backspace>"},
				termbox.KeyTab:           {"", "<tab>"},
				termbox.KeyEnter:         {"", "<enter>"},
				termbox.KeyEsc:           {"", "<escape>"},
				termbox.KeyCtrlBackslash: {"C-", "\\"},
				termbox.KeyCtrlSlash:     {"C-", "/"},
				termbox.KeySpace:         {"", "<space>"},
				termbox.KeyCtrl8:         {"C-", "8"},
			}
			if sk, ok := kmap[e.Key]; ok {
				pre = sk[0]
				k = sk[1]
			}
		}
	}

	ek = pre + mod + k
	return ek
}
<commit_msg>Limit resizing functionality<commit_after>package handlers

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/erroneousboat/termui"
	"github.com/nlopes/slack"
	termbox "github.com/nsf/termbox-go"

	"github.com/erroneousboat/slack-term/context"
	"github.com/erroneousboat/slack-term/views"
)

var timer *time.Timer

// actionMap binds specific action names to the function counterparts,
// these action names can then be used to bind them to specific keys
// in the Config.
var actionMap = map[string]func(*context.AppContext){
	"space":               actionSpace,
	"backspace":           actionBackSpace,
	"delete":              actionDelete,
	"cursor-right":        actionMoveCursorRight,
	"cursor-left":         actionMoveCursorLeft,
	"send":                actionSend,
	"quit":                actionQuit,
	"mode-insert":         actionInsertMode,
	"mode-command":        actionCommandMode,
	"mode-search":         actionSearchMode,
	"clear-input":         actionClearInput,
	"channel-up":          actionMoveCursorUpChannels,
	"channel-down":        actionMoveCursorDownChannels,
	"channel-top":         actionMoveCursorTopChannels,
	"channel-bottom":      actionMoveCursorBottomChannels,
	"channel-search-next": actionSearchNextChannels,
	"channel-search-prev": actionSearchPrevChannels,
	"chat-up":             actionScrollUpChat,
	"chat-down":           actionScrollDownChat,
	"help":                actionHelp,
}

func RegisterEventHandlers(ctx *context.AppContext) {
	eventHandler(ctx)
	messageHandler(ctx)
}

func eventHandler(ctx *context.AppContext) {
	go func() {
		for {
			ctx.EventQueue <- termbox.PollEvent()
		}
	}()

	go func() {
		for {
			ev := <-ctx.EventQueue
			handleTermboxEvents(ctx, ev)
			handleMoreTermboxEvents(ctx, ev)

			// Place your debugging statements here
			if ctx.Debug {
				ctx.View.Debug.Println(
					"event received",
				)
			}
		}
	}()
}

func handleTermboxEvents(ctx *context.AppContext, ev termbox.Event) bool {
	switch ev.Type {
	case termbox.EventKey:
		actionKeyEvent(ctx, ev)
	case termbox.EventResize:
		actionResizeEvent(ctx, ev)
	}

	return true
}

func handleMoreTermboxEvents(ctx *context.AppContext, ev termbox.Event) bool {
	for {
		select {
		case ev := <-ctx.EventQueue:
			ok := handleTermboxEvents(ctx, ev)
			if !ok {
				return false
			}
		default:
			return true
		}
	}
}

func messageHandler(ctx *context.AppContext) {
	go func() {
		for {
			select {
			case msg := <-ctx.Service.RTM.IncomingEvents:
				switch ev := msg.Data.(type) {
				case *slack.MessageEvent:
					// Construct message
					msg := ctx.Service.CreateMessageFromMessageEvent(ev)

					// Add message to the selected channel
					if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {

						// reverse order of messages, mainly done
						// when attachments are added to message
						for i := len(msg) - 1; i >= 0; i-- {
							ctx.View.Chat.AddMessage(
								msg[i].ToString(),
							)
						}

						termui.Render(ctx.View.Chat)

						// TODO: set Chat.Offset to 0, to automatically scroll
						// down?
					}

					// Set new message indicator for channel, I'm leaving
					// this here because I also want to be notified when
					// I'm currently in a channel but not in the terminal
					// window (tmux). But only create a notification when
					// it comes from someone else but the current user.
					if ev.User != ctx.Service.CurrentUserID {
						actionNewMessage(ctx, ev.Channel)
					}
				case *slack.PresenceChangeEvent:
					actionSetPresence(ctx, ev.User, ev.Presence)
				}
			}
		}
	}()
}

func actionKeyEvent(ctx *context.AppContext, ev termbox.Event) {

	keyStr := getKeyString(ev)

	// Get the action name (actionStr) from the key that
	// has been pressed. If this is found try to uncover
	// the associated function with this key and execute
	// it.
	actionStr, ok := ctx.Config.KeyMap[ctx.Mode][keyStr]
	if ok {
		action, ok := actionMap[actionStr]
		if ok {
			action(ctx)
		}
	} else {
		if ctx.Mode == context.InsertMode && ev.Ch != 0 {
			actionInput(ctx.View, ev.Ch)
		} else if ctx.Mode == context.SearchMode && ev.Ch != 0 {
			actionSearch(ctx, ev.Ch)
		}
	}
}

func actionResizeEvent(ctx *context.AppContext, ev termbox.Event) {
	// When terminal window is too small termui will panic, here
	// we won't resize when the terminal window is too small.
	if termui.TermWidth() < 25 || termui.TermHeight() < 5 {
		return
	}

	termui.Body.Width = termui.TermWidth()

	// Vertical resize components
	ctx.View.Channels.List.Height = termui.TermHeight() - ctx.View.Input.Par.Height
	ctx.View.Chat.List.Height = termui.TermHeight() - ctx.View.Input.Par.Height
	ctx.View.Debug.List.Height = termui.TermHeight() - ctx.View.Input.Par.Height

	termui.Body.Align()
	termui.Render(termui.Body)
}

func actionInput(view *views.View, key rune) {
	view.Input.Insert(key)
	termui.Render(view.Input)
}

func actionClearInput(ctx *context.AppContext) {
	// Clear input
	ctx.View.Input.Clear()
	ctx.View.Refresh()

	// Set command mode
	actionCommandMode(ctx)
}

func actionSpace(ctx *context.AppContext) {
	actionInput(ctx.View, ' ')
}

func actionBackSpace(ctx *context.AppContext) {
	ctx.View.Input.Backspace()
	termui.Render(ctx.View.Input)
}

func actionDelete(ctx *context.AppContext) {
	ctx.View.Input.Delete()
	termui.Render(ctx.View.Input)
}

func actionMoveCursorRight(ctx *context.AppContext) {
	ctx.View.Input.MoveCursorRight()
	termui.Render(ctx.View.Input)
}

func actionMoveCursorLeft(ctx *context.AppContext) {
	ctx.View.Input.MoveCursorLeft()
	termui.Render(ctx.View.Input)
}

func actionSend(ctx *context.AppContext) {
	if !ctx.View.Input.IsEmpty() {

		// Clear message before sending, to combat
		// quick succession of actionSend
		message := ctx.View.Input.GetText()
		ctx.View.Input.Clear()
		ctx.View.Refresh()

		// Send message
		ctx.Service.SendMessage(
			ctx.View.Channels.SelectedChannel,
			message,
		)

		// Clear notification icon if there is any
		ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
		ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
		termui.Render(ctx.View.Channels)
	}
}

// actionSearch will search through the channels based on the users
// input. A time is implemented to make sure the actual searching
// and changing of channels is done when the user's typing is paused.
func actionSearch(ctx *context.AppContext, key rune) {
	actionInput(ctx.View, key)

	go func() {
		if timer != nil {
			timer.Stop()
		}

		timer = time.NewTimer(time.Second / 4)
		<-timer.C

		// Only actually search when the time expires
		term := ctx.View.Input.GetText()
		ctx.View.Channels.Search(term)
		actionChangeChannel(ctx)
	}()
}

// actionQuit will exit the program by using os.Exit, this is
// done because we are using a custom termui EvtStream. Which
// we won't be able to call termui.StopLoop() on. See main.go
// for the customEvtStream and why this is done.
func actionQuit(ctx *context.AppContext) {
	termbox.Close()
	os.Exit(0)
}

func actionInsertMode(ctx *context.AppContext) {
	ctx.Mode = context.InsertMode
	ctx.View.Mode.SetInsertMode()
}

func actionCommandMode(ctx *context.AppContext) {
	ctx.Mode = context.CommandMode
	ctx.View.Mode.SetCommandMode()
}

func actionSearchMode(ctx *context.AppContext) {
	ctx.Mode = context.SearchMode
	ctx.View.Mode.SetSearchMode()
}

func actionGetMessages(ctx *context.AppContext) {
	msgs := ctx.Service.GetMessages(
		ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
		ctx.View.Chat.GetMaxItems(),
	)

	var strMsgs []string
	for _, msg := range msgs {
		strMsgs = append(strMsgs, msg.ToString())
	}

	ctx.View.Chat.SetMessages(strMsgs)

	termui.Render(ctx.View.Chat)
}

// actionMoveCursorUpChannels will execute the actionChangeChannel
// function. A timer is implemented to support fast scrolling through
// the list without executing the actionChangeChannel event
func actionMoveCursorUpChannels(ctx *context.AppContext) {
	go func() {
		if timer != nil {
			timer.Stop()
		}

		ctx.View.Channels.MoveCursorUp()
		termui.Render(ctx.View.Channels)

		timer = time.NewTimer(time.Second / 4)
		<-timer.C

		// Only actually change channel when the timer expires
		actionChangeChannel(ctx)
	}()
}

// actionMoveCursorDownChannels will execute the actionChangeChannel
// function. A timer is implemented to support fast scrolling through
// the list without executing the actionChangeChannel event
func actionMoveCursorDownChannels(ctx *context.AppContext) {
	go func() {
		if timer != nil {
			timer.Stop()
		}

		ctx.View.Channels.MoveCursorDown()
		termui.Render(ctx.View.Channels)

		timer = time.NewTimer(time.Second / 4)
		<-timer.C

		// Only actually change channel when the timer expires
		actionChangeChannel(ctx)
	}()
}

func actionMoveCursorTopChannels(ctx *context.AppContext) {
	ctx.View.Channels.MoveCursorTop()
	actionChangeChannel(ctx)
}

func actionMoveCursorBottomChannels(ctx *context.AppContext) {
	ctx.View.Channels.MoveCursorBottom()
	actionChangeChannel(ctx)
}

func actionSearchNextChannels(ctx *context.AppContext) {
	ctx.View.Channels.SearchNext()
	actionChangeChannel(ctx)
}

func actionSearchPrevChannels(ctx *context.AppContext) {
	ctx.View.Channels.SearchPrev()
	actionChangeChannel(ctx)
}

func actionChangeChannel(ctx *context.AppContext) {
	// Clear messages from Chat pane
	ctx.View.Chat.ClearMessages()

	// Get messages of the SelectedChannel, and get the count of messages
	// that fit into the Chat component
	msgs := ctx.Service.GetMessages(
		ctx.Service.GetSlackChannel(ctx.View.Channels.SelectedChannel),
		ctx.View.Chat.GetMaxItems(),
	)

	var strMsgs []string
	for _, msg := range msgs {
		strMsgs = append(strMsgs, msg.ToString())
	}

	// Set messages for the channel
	ctx.View.Chat.SetMessages(strMsgs)

	// Set channel name for the Chat pane
	ctx.View.Chat.SetBorderLabel(
		ctx.Service.Channels[ctx.View.Channels.SelectedChannel].GetChannelName(),
	)

	// Clear notification icon if there is any
	ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
	ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())

	termui.Render(ctx.View.Channels)
	termui.Render(ctx.View.Chat)
}

func actionNewMessage(ctx *context.AppContext, channelID string) {
	ctx.Service.MarkAsUnread(channelID)
	ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
	termui.Render(ctx.View.Channels)
	fmt.Print("\a")
}

func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
	ctx.Service.SetPresenceChannelEvent(channelID, presence)
	ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
	termui.Render(ctx.View.Channels)
}

func actionScrollUpChat(ctx *context.AppContext) {
	ctx.View.Chat.ScrollUp()
	termui.Render(ctx.View.Chat)
}

func actionScrollDownChat(ctx *context.AppContext) {
	ctx.View.Chat.ScrollDown()
	termui.Render(ctx.View.Chat)
}

func actionHelp(ctx *context.AppContext) {
	ctx.View.Chat.Help(ctx.Config)
	termui.Render(ctx.View.Chat)
}

// GetKeyString will return a string that resembles the key event from
// termbox. This is blatanly copied from termui because it is an unexported
// function.
//
// See:
// - https://github.com/gizak/termui/blob/a7e3aeef4cdf9fa2edb723b1541cb69b7bb089ea/events.go#L31-L72
// - https://github.com/nsf/termbox-go/blob/master/api_common.go
func getKeyString(e termbox.Event) string {
	var ek string

	k := string(e.Ch)
	pre := ""
	mod := ""

	if e.Mod == termbox.ModAlt {
		mod = "M-"
	}
	if e.Ch == 0 {
		if e.Key > 0xFFFF-12 {
			k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
		} else if e.Key > 0xFFFF-25 {
			ks := []string{"<insert>", "<delete>", "<home>", "<end>", "<previous>", "<next>", "<up>", "<down>", "<left>", "<right>"}
			k = ks[0xFFFF-int(e.Key)-12]
		}

		if e.Key <= 0x7F {
			pre = "C-"
			k = string('a' - 1 + int(e.Key))
			kmap := map[termbox.Key][2]string{
				termbox.KeyCtrlSpace:     {"C-", "<space>"},
				termbox.KeyBackspace:     {"", "<backspace>"},
				termbox.KeyTab:           {"", "<tab>"},
				termbox.KeyEnter:         {"", "<enter>"},
				termbox.KeyEsc:           {"", "<escape>"},
				termbox.KeyCtrlBackslash: {"C-", "\\"},
				termbox.KeyCtrlSlash:     {"C-", "/"},
				termbox.KeySpace:         {"", "<space>"},
				termbox.KeyCtrl8:         {"C-", "8"},
			}
			if sk, ok := kmap[e.Key]; ok {
				pre = sk[0]
				k = sk[1]
			}
		}
	}

	ek = pre + mod + k
	return ek
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-xdgbasedir Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !windows

package home

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"
)

// Dir detects and returns the user home directory.
func Dir() string {
	// At first, Check the $HOME environment variable
	if usrHome := os.Getenv("HOME"); usrHome != "" {
		return usrHome
	}

	// Fallback if not set $HOME
	// gets the canonical username
	cmdWhoami := exec.Command("whoami")
	usrName, err := cmdWhoami.Output()
	if err != nil {
		return ""
	}

	// gets the home directory path use 'eval echo ~$USER' magic
	stdout := new(bytes.Buffer)
	cmd := exec.Command("sh", "-c", fmt.Sprintf("eval echo ~%s", usrName))
	fmt.Printf("cmd: %T = %v\n", cmd, cmd)
	cmd.Stdout = stdout
	if err := cmd.Run(); err != nil {
		return ""
	}

	return strings.TrimSpace(stdout.String())
}
<commit_msg>home: remove debug printf<commit_after>// Copyright 2017 The go-xdgbasedir Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !windows

package home

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"
)

// Dir detects and returns the user home directory.
func Dir() string {
	// At first, Check the $HOME environment variable
	if usrHome := os.Getenv("HOME"); usrHome != "" {
		return usrHome
	}

	// Fallback if not set $HOME
	// gets the canonical username
	cmdWhoami := exec.Command("whoami")
	usrName, err := cmdWhoami.Output()
	if err != nil {
		return ""
	}

	// gets the home directory path use 'eval echo ~$USER' magic
	stdout := new(bytes.Buffer)
	cmd := exec.Command("sh", "-c", fmt.Sprintf("eval echo ~%s", usrName))
	cmd.Stdout = stdout
	if err := cmd.Run(); err != nil {
		return ""
	}

	return strings.TrimSpace(stdout.String())
}
<|endoftext|>


<commit_before>// Package ping tries to ping a HTTP server through different ways
// Connection, Session (Head), Get and Post
package ping

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"time"

	"github.com/mehrdadrad/mylg/cli"
)

// Ping represents HTTP ping request
type Ping struct {
	url     string
	host    string
	timeout time.Duration
	count   int
	method  string
	buf     string
	rAddr   net.Addr
	nsTime  time.Duration
	conn    net.Conn
}

// Result holds Ping result
type Result struct {
	StatusCode int
	ConnTime   float64
	TotalTime  float64
	Size       int
	Proto      string
	Server     string
	Status     string
}

// NewPing validate and constructs request object
func NewPing(args string, cfg cli.Config) (*Ping, error) {
	URL, flag := cli.Flag(args)
	// help
	if _, ok := flag["help"]; ok || URL == "" {
		help(cfg)
		return nil, fmt.Errorf("")
	}
	URL = Normalize(URL)
	u, err := url.Parse(URL)
	if err != nil {
		return &Ping{}, fmt.Errorf("cannot parse url")
	}
	sTime := time.Now()
	ipAddr, err := net.ResolveIPAddr("ip", u.Host)
	if err != nil {
		return &Ping{}, fmt.Errorf("cannot resolve %s: Unknown host", u.Host)
	}

	p := &Ping{
		url:    URL,
		host:   u.Host,
		rAddr:  ipAddr,
		nsTime: time.Since(sTime),
	}

	// set count
	p.count = cli.SetFlag(flag, "c", cfg.Hping.Count).(int)
	// set timeout
	timeout := cli.SetFlag(flag, "t", cfg.Hping.Timeout).(string)
	p.timeout, err = time.ParseDuration(timeout)
	if err != nil {
		return p, err
	}
	// set method
	p.method = cli.SetFlag(flag, "m", cfg.Hping.Method).(string)
	p.method = strings.ToUpper(p.method)
	// set buff (post)
	buf := cli.SetFlag(flag, "d", "mylg").(string)
	p.buf = buf
	return p, nil
}

// Normalize fixes scheme
func Normalize(URL string) string {
	re := regexp.MustCompile(`(?i)https{0,1}://`)
	if !re.MatchString(URL) {
		URL = fmt.Sprintf("http://%s", URL)
	}
	return URL
}

// Run tries to ping w/ pretty print
func (p *Ping) Run() {
	var (
		sigCh = make(chan os.Signal, 1)
		c     = make(map[int]float64, 10)
		s     []float64
	)
	// capture interrupt w/ s channel
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	pStrPrefix := "HTTP Response seq=%d, "
	pStrSuffix := "proto=%s, status=%d, size=%d Bytes, time=%.3f ms\n"
	pStrSuffixHead := "proto=%s, status=%d, time=%.3f ms\n"
	fmt.Printf("HPING %s (%s), Method: %s, DNSLookup: %.4f ms\n", p.host, p.rAddr, p.method, p.nsTime.Seconds()*1000)

LOOP:
	for i := 0; i < p.count; i++ {
		if r, err := p.Ping(); err == nil {
			if p.method != "HEAD" {
				fmt.Printf(pStrPrefix+pStrSuffix, i, r.Proto, r.StatusCode, r.Size, r.TotalTime*1000)
			} else {
				fmt.Printf(pStrPrefix+pStrSuffixHead, i, r.Proto, r.StatusCode, r.TotalTime*1000)
			}
			c[r.StatusCode]++
			s = append(s, r.TotalTime*1000)
		} else {
			fmt.Printf(pStrPrefix+"timeout\n", i)
			c[-1]++
			if err.Error() == "wrong method" {
				println(err.Error())
				break LOOP
			}
		}
		select {
		case <-sigCh:
			break LOOP
		default:
		}
	}
	// print statistics
	printStats(c, s, p.host)
}

// printStats prints out the footer
func printStats(c map[int]float64, s []float64, host string) {
	var r = make(map[string]float64, 5)

	// total replied requests
	for k, v := range c {
		if k < 0 {
			continue
		}
		r["sum"] += v
	}

	for _, v := range s {
		// maximum
		if r["max"] < v {
			r["max"] = v
		}
		// minimum
		if r["min"] > v || r["min"] == 0 {
			r["min"] = v
		}
		// average
		if r["avg"] == 0 {
			r["avg"] = v
		} else {
			r["avg"] = (r["avg"] + v) / 2
		}
	}

	totalReq := r["sum"] + c[-1]
	timeoutPct := 100 - (100*r["sum"])/totalReq

	fmt.Printf("\n--- %s HTTP ping statistics --- \n", host)
	fmt.Printf("%.0f requests transmitted, %.0f replies received, %.0f%% timeout\n", totalReq, r["sum"], timeoutPct)
	fmt.Printf("HTTP Round-trip min/avg/max = %.2f/%.2f/%.2f ms\n", r["min"], r["avg"], r["max"])
	for k, v := range c {
		if k < 0 {
			continue
		}
		progress := fmt.Sprintf("%10s", strings.Repeat("\u2588", int(v*100/(r["sum"])/5)))
		fmt.Printf("HTTP Code [%d] responses : [%s] %.2f%% \n", k, progress, v*100/(r["sum"]))
	}
}

// Ping tries to ping a web server through http
func (p *Ping) Ping() (Result, error) {
	var (
		r     Result
		sTime time.Time
		resp  *http.Response
		err   error
	)

	client := &http.Client{Timeout: p.timeout * time.Second}
	sTime = time.Now()

	switch p.method {
	case "HEAD":
		resp, err = client.Head(p.url)
	case "GET":
		resp, err = client.Get(p.url)
	case "POST":
		r.Size = len(p.buf)
		reader := strings.NewReader(p.buf)
		resp, err = client.Post(p.url, "text/plain", reader)
	default:
		return r, fmt.Errorf("wrong method")
	}

	if err != nil {
		return r, err
	}
	r.TotalTime = time.Since(sTime).Seconds()

	if p.method == "GET" {
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return r, err
		}
		r.Size = len(body)
	}

	r.StatusCode = resp.StatusCode
	r.Proto = resp.Proto
	return r, nil
}

// help shows ping help
func help(cfg cli.Config) {
	fmt.Printf(`
    usage:
          hping url [options]

    options:
          -c count       Send 'count' requests (default: %d)
          -t timeout     Specifies a time limit for requests in ms/s (default is %s)
          -m method      HTTP methods: GET/POST/HEAD (default: %s)
          -d data        Sending the given data (text/json) (default: "%s")
	`,
		cfg.Hping.Count,
		cfg.Hping.Timeout,
		cfg.Hping.Method,
		cfg.Hping.Data)
}
<commit_msg>Makes hping print actul error from the request instead of 'timeout'. Changes print of timeout rate to failure rate instead. Makes hping stop directly if a unrecognized method is specified instead of showing it after the first ping<commit_after>// Package ping tries to ping a HTTP server through different ways
// Connection, Session (Head), Get and Post
package ping

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"time"

	"github.com/mehrdadrad/mylg/cli"
)

// Ping represents HTTP ping request
type Ping struct {
	url     string
	host    string
	timeout time.Duration
	count   int
	method  string
	buf     string
	rAddr   net.Addr
	nsTime  time.Duration
	conn    net.Conn
}

// Result holds Ping result
type Result struct {
	StatusCode int
	ConnTime   float64
	TotalTime  float64
	Size       int
	Proto      string
	Server     string
	Status     string
}

// NewPing validate and constructs request object
func NewPing(args string, cfg cli.Config) (*Ping, error) {
	URL, flag := cli.Flag(args)
	// help
	if _, ok := flag["help"]; ok || URL == "" {
		help(cfg)
		return nil, fmt.Errorf("")
	}
	URL = Normalize(URL)
	u, err := url.Parse(URL)
	if err != nil {
		return &Ping{}, fmt.Errorf("cannot parse url")
	}
	sTime := time.Now()
	ipAddr, err := net.ResolveIPAddr("ip", u.Host)
	if err != nil {
		return &Ping{}, fmt.Errorf("cannot resolve %s: Unknown host", u.Host)
	}

	p := &Ping{
		url:    URL,
		host:   u.Host,
		rAddr:  ipAddr,
		nsTime: time.Since(sTime),
	}

	// set count
	p.count = cli.SetFlag(flag, "c", cfg.Hping.Count).(int)
	// set timeout
	timeout := cli.SetFlag(flag, "t", cfg.Hping.Timeout).(string)
	p.timeout, err = time.ParseDuration(timeout)
	if err != nil {
		return p, err
	}
	// set method
	p.method = cli.SetFlag(flag, "m", cfg.Hping.Method).(string)
	p.method = strings.ToUpper(p.method)
	// set buff (post)
	buf := cli.SetFlag(flag, "d", "mylg").(string)
	p.buf = buf
	return p, nil
}

// Normalize fixes scheme
func Normalize(URL string) string {
	re := regexp.MustCompile(`(?i)https{0,1}://`)
	if !re.MatchString(URL) {
		URL = fmt.Sprintf("http://%s", URL)
	}
	return URL
}

// Run tries to ping w/ pretty print
func (p *Ping) Run() {
	if p.method != "GET" && p.method != "POST" && p.method != "HEAD" {
		fmt.Printf("Error: Method '%s' not recognized.\n", p.method)
		return
	}
	var (
		sigCh = make(chan os.Signal, 1)
		c     = make(map[int]float64, 10)
		s     []float64
	)
	// capture interrupt w/ s channel
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	pStrPrefix := "HTTP Response seq=%d, "
	pStrSuffix := "proto=%s, status=%d, size=%d Bytes, time=%.3f ms\n"
	pStrSuffixHead := "proto=%s, status=%d, time=%.3f ms\n"
	fmt.Printf("HPING %s (%s), Method: %s, DNSLookup: %.4f ms\n", p.host, p.rAddr, p.method, p.nsTime.Seconds()*1000)

LOOP:
	for i := 0; i < p.count; i++ {
		if r, err := p.Ping(); err == nil {
			if p.method != "HEAD" {
				fmt.Printf(pStrPrefix+pStrSuffix, i, r.Proto, r.StatusCode, r.Size, r.TotalTime*1000)
			} else {
				fmt.Printf(pStrPrefix+pStrSuffixHead, i, r.Proto, r.StatusCode, r.TotalTime*1000)
			}
			c[r.StatusCode]++
			s = append(s, r.TotalTime*1000)
		} else {
			c[-1]++
			errmsg := strings.Split(err.Error(), ": ")
			fmt.Printf(pStrPrefix+"%s\n", i, errmsg[len(errmsg)-1])
		}
		select {
		case <-sigCh:
			break LOOP
		default:
		}
	}
	// print statistics
	printStats(c, s, p.host)
}

// printStats prints out the footer
func printStats(c map[int]float64, s []float64, host string) {
	var r = make(map[string]float64, 5)

	// total replied requests
	for k, v := range c {
		if k < 0 {
			continue
		}
		r["sum"] += v
	}

	for _, v := range s {
		// maximum
		if r["max"] < v {
			r["max"] = v
		}
		// minimum
		if r["min"] > v || r["min"] == 0 {
			r["min"] = v
		}
		// average
		if r["avg"] == 0 {
			r["avg"] = v
		} else {
			r["avg"] = (r["avg"] + v) / 2
		}
	}

	totalReq := r["sum"] + c[-1]
	failPct := 100 - (100*r["sum"])/totalReq

	fmt.Printf("\n--- %s HTTP ping statistics --- \n", host)
	fmt.Printf("%.0f requests transmitted, %.0f replies received, %.0f%% requests failed\n", totalReq, r["sum"], failPct)
	fmt.Printf("HTTP Round-trip min/avg/max = %.2f/%.2f/%.2f ms\n", r["min"], r["avg"], r["max"])
	for k, v := range c {
		if k < 0 {
			continue
		}
		progress := fmt.Sprintf("%10s", strings.Repeat("\u2588", int(v*100/(r["sum"])/5)))
		fmt.Printf("HTTP Code [%d] responses : [%s] %.2f%% \n", k, progress, v*100/(r["sum"]))
	}
}

// Ping tries to ping a web server through http
func (p *Ping) Ping() (Result, error) {
	var (
		r     Result
		sTime time.Time
		resp  *http.Response
		err   error
	)

	client := &http.Client{Timeout: p.timeout * time.Second}
	sTime = time.Now()

	switch p.method {
	case "HEAD":
		resp, err = client.Head(p.url)
	case "GET":
		resp, err = client.Get(p.url)
	case "POST":
		r.Size = len(p.buf)
		reader := strings.NewReader(p.buf)
		resp, err = client.Post(p.url, "text/plain", reader)
	default:
		return r, fmt.Errorf("wrong method")
	}

	if err != nil {
		return r, err
	}
	r.TotalTime = time.Since(sTime).Seconds()

	if p.method == "GET" {
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return r, err
		}
		r.Size = len(body)
	}

	r.StatusCode = resp.StatusCode
	r.Proto = resp.Proto
	return r, nil
}

// help shows ping help
func help(cfg cli.Config) {
	fmt.Printf(`
    usage:
          hping url [options]

    options:
          -c count       Send 'count' requests (default: %d)
          -t timeout     Specifies a time limit for requests in ms/s (default is %s)
          -m method      HTTP methods: GET/POST/HEAD (default: %s)
          -d data        Sending the given data (text/json) (default: "%s")
	`,
		cfg.Hping.Count,
		cfg.Hping.Timeout,
		cfg.Hping.Method,
		cfg.Hping.Data)
}
<|endoftext|>


<commit_before>package httpcache

import (
	"github.com/lostisland/go-sawyer"
	"github.com/lostisland/go-sawyer/hypermedia"
	"net/http"
	"os"
	"path/filepath"
)

const (
	responseFilename = "response"
	bodyFilename     = "body"
)

type FileCache struct {
	path string
}

func NewFileCache(path string) *FileCache {
	return &FileCache{path}
}

func (c *FileCache) Get(req *http.Request, v interface{}) *sawyer.Response {
	path := c.requestPath(req)

	responseFile, err := os.Open(filepath.Join(path, responseFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer responseFile.Close()

	bodyFile, err := os.Open(filepath.Join(path, bodyFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer bodyFile.Close()

	return DecodeFrom(v, responseFile, bodyFile)
}

func (c *FileCache) Set(req *http.Request, res *sawyer.Response, v interface{}) error {
	path := c.requestPath(req)
	if err := os.MkdirAll(path, 0755); err != nil {
		return err
	}

	responseFile, err := os.Create(filepath.Join(path, responseFilename))
	if err != nil {
		return err
	}
	defer responseFile.Close()

	bodyFile, err := os.Create(filepath.Join(path, bodyFilename))
	if err != nil {
		return err
	}
	defer bodyFile.Close()

	return EncodeTo(v, res, responseFile, bodyFile)
}

func (c *FileCache) Rels(req *http.Request) hypermedia.Relations {
	path := c.requestPath(req)

	responseFile, err := os.Create(filepath.Join(path, responseFilename))
	if err != nil {
		return hypermedia.Relations{}
	}
	defer responseFile.Close()

	return Decode(responseFile).Rels
}

func (c *FileCache) requestPath(r *http.Request) string {
	return filepath.Join(c.path, RequestSha(r))
}
<commit_msg>use OpenFile to open with exclusive flag<commit_after>package httpcache

import (
	"github.com/lostisland/go-sawyer"
	"github.com/lostisland/go-sawyer/hypermedia"
	"net/http"
	"os"
	"path/filepath"
)

const (
	responseFilename = "response"
	bodyFilename     = "body"
	fileCreateFlag   = os.O_RDWR | os.O_CREATE | os.O_EXCL
)

type FileCache struct {
	path string
}

func NewFileCache(path string) *FileCache {
	return &FileCache{path}
}

func (c *FileCache) Get(req *http.Request, v interface{}) *sawyer.Response {
	path := c.requestPath(req)

	responseFile, err := os.Open(filepath.Join(path, responseFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer responseFile.Close()

	bodyFile, err := os.Open(filepath.Join(path, bodyFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer bodyFile.Close()

	return DecodeFrom(v, responseFile, bodyFile)
}

func (c *FileCache) Set(req *http.Request, res *sawyer.Response, v interface{}) error {
	path := c.requestPath(req)
	if err := os.MkdirAll(path, 0755); err != nil {
		return err
	}

	responseFile, err := os.OpenFile(filepath.Join(path, responseFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer responseFile.Close()

	bodyFile, err := os.OpenFile(filepath.Join(path, bodyFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer bodyFile.Close()

	return EncodeTo(v, res, responseFile, bodyFile)
}

func (c *FileCache) Rels(req *http.Request) hypermedia.Relations {
	path := c.requestPath(req)

	responseFile, err := os.Create(filepath.Join(path, responseFilename))
	if err != nil {
		return hypermedia.Relations{}
	}
	defer responseFile.Close()

	return Decode(responseFile).Rels
}

func (c *FileCache) requestPath(r *http.Request) string {
	return filepath.Join(c.path, RequestSha(r))
}
<|endoftext|>


<commit_before>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

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

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

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

*/
package render

import (
	"net/http"
	"text/template"

	"github.com/webx-top/echo"
)

var (
	DefaultOptions = &Options{
		Skipper:              echo.DefaultSkipper,
		ErrorPages:           make(map[int]string),
		DefaultHTTPErrorCode: http.StatusInternalServerError,
		FuncMap:              template.FuncMap{},
	}
)

type Options struct {
	Skipper              echo.Skipper
	ErrorPages           map[int]string
	DefaultHTTPErrorCode int
	FuncMap              template.FuncMap
}

// Middleware set renderer
func Middleware(d echo.Renderer) echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.SetRenderer(d)
			return h.Handle(c)
		})
	}
}

func Auto() echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.SetAuto(true)
			return h.Handle(c)
		})
	}
}

func HTTPErrorHandler(opt *Options) echo.HTTPErrorHandler {
	if opt == nil {
		opt = DefaultOptions
	}
	if opt.ErrorPages == nil {
		opt.ErrorPages = DefaultOptions.ErrorPages
	}
	if opt.DefaultHTTPErrorCode < 1 {
		opt.DefaultHTTPErrorCode = DefaultOptions.DefaultHTTPErrorCode
	}
	if opt.FuncMap == nil {
		opt.FuncMap = DefaultOptions.FuncMap
	}
	tmplNum := len(opt.ErrorPages)
	return func(err error, c echo.Context) {
		code := DefaultOptions.DefaultHTTPErrorCode
		var msg string
		var panicErr *echo.PanicError
		switch e := err.(type) {
		case *echo.HTTPError:
			if e.Code > 0 {
				code = e.Code
			}
			msg = e.Message
		case *echo.PanicError:
			panicErr = e

		}
		title := http.StatusText(code)
		if c.Echo().Debug() {
			msg = err.Error()
		} else if len(msg) == 0 {
			msg = title
		}
		if !c.Response().Committed() {
			switch {
			case c.Request().Method() == echo.HEAD:
				c.NoContent(code)
			case tmplNum > 0:
				t, y := opt.ErrorPages[code]
				if !y && code != 0 {
					t, y = opt.ErrorPages[0]
				}
				if y {
					data := c.Data().Reset().SetInfo(msg, 0)
					if c.Format() == `html` {
						c.SetCode(code)
						c.SetFunc(`Lang`, c.Lang)
						if len(opt.FuncMap) > 0 {
							for name, function := range opt.FuncMap {
								c.SetFunc(name, function)
							}
						}
						data.SetData(echo.H{
							"title":   title,
							"content": msg,
							"debug":   c.Echo().Debug(),
							"code":    code,
							"panic":   panicErr,
						}, 0)
					} else {
						c.SetCode(opt.DefaultHTTPErrorCode)
					}
					if err := c.SetAuto(true).Render(t, nil); err != nil {
						msg += "\n" + err.Error()
						y = false
						c.Logger().Error(err)
					}
				}
				if y {
					break
				}
				fallthrough
			default:
				c.String(msg, code)
			}
		}
		c.Logger().Debug(err)
	}
}
<commit_msg>update<commit_after>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

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

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

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

*/
package render

import (
	"net/http"
	"time"

	"github.com/webx-top/echo"
)

var (
	DefaultOptions = &Options{
		Skipper:              echo.DefaultSkipper,
		ErrorPages:           make(map[int]string),
		DefaultHTTPErrorCode: http.StatusInternalServerError,
		SetFuncMap: []echo.HandlerFunc{
			func(c echo.Context) error {
				c.SetFunc(`Lang`, c.Lang)
				c.SetFunc(`Now`, time.Now)
				c.SetFunc(`T`, c.T)
				return nil
			},
		},
	}
)

type Options struct {
	Skipper              echo.Skipper
	ErrorPages           map[int]string
	DefaultHTTPErrorCode int
	SetFuncMap           []echo.HandlerFunc
}

func (opt *Options) AddFuncSetter(set ...echo.HandlerFunc) *Options {
	if opt.SetFuncMap == nil {
		opt.SetFuncMap = make([]echo.HandlerFunc, len(DefaultOptions.SetFuncMap))
		for index, setter := range DefaultOptions.SetFuncMap {
			opt.SetFuncMap[index] = setter
		}
	}
	opt.SetFuncMap = append(opt.SetFuncMap, set...)
	return opt
}

func (opt *Options) SetFuncSetter(set ...echo.HandlerFunc) *Options {
	opt.SetFuncMap = set
	return opt
}

// Middleware set renderer
func Middleware(d echo.Renderer) echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.SetRenderer(d)
			return h.Handle(c)
		})
	}
}

func Auto() echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.SetAuto(true)
			return h.Handle(c)
		})
	}
}

func HTTPErrorHandler(opt *Options) echo.HTTPErrorHandler {
	if opt == nil {
		opt = DefaultOptions
	}
	if opt.ErrorPages == nil {
		opt.ErrorPages = DefaultOptions.ErrorPages
	}
	if opt.DefaultHTTPErrorCode < 1 {
		opt.DefaultHTTPErrorCode = DefaultOptions.DefaultHTTPErrorCode
	}
	if opt.SetFuncMap == nil {
		opt.SetFuncMap = DefaultOptions.SetFuncMap
	}
	tmplNum := len(opt.ErrorPages)
	return func(err error, c echo.Context) {
		code := DefaultOptions.DefaultHTTPErrorCode
		var msg string
		var panicErr *echo.PanicError
		switch e := err.(type) {
		case *echo.HTTPError:
			if e.Code > 0 {
				code = e.Code
			}
			msg = e.Message
		case *echo.PanicError:
			panicErr = e

		}
		title := http.StatusText(code)
		if c.Echo().Debug() {
			msg = err.Error()
		} else if len(msg) == 0 {
			msg = title
		}
		if !c.Response().Committed() {
			switch {
			case c.Request().Method() == echo.HEAD:
				c.NoContent(code)
			case tmplNum > 0:
				t, y := opt.ErrorPages[code]
				if !y && code != 0 {
					t, y = opt.ErrorPages[0]
				}
				if y {
					data := c.Data().Reset().SetInfo(msg, 0)
					if c.Format() == `html` {
						c.SetCode(code)
						c.SetFunc(`Lang`, c.Lang)
						if len(opt.SetFuncMap) > 0 {
							for _, setFunc := range opt.SetFuncMap {
								err = setFunc(c)
								if err != nil {
									c.String(err.Error())
									return
								}
							}
						}
						data.SetData(echo.H{
							"title":   title,
							"content": msg,
							"debug":   c.Echo().Debug(),
							"code":    code,
							"panic":   panicErr,
						}, 0)
					} else {
						c.SetCode(opt.DefaultHTTPErrorCode)
					}
					if err := c.SetAuto(true).Render(t, nil); err != nil {
						msg += "\n" + err.Error()
						y = false
						c.Logger().Error(err)
					}
				}
				if y {
					break
				}
				fallthrough
			default:
				c.String(msg, code)
			}
		}
		c.Logger().Debug(err)
	}
}
<|endoftext|>