<commit_before>package hal

import (
	"encoding/json"
	"fmt"
)

// User is a chat participant
type User struct {
	ID      string
	Name    string
	Roles   []string
	Options map[string]interface{}
}

func (u *User) Get(k string) interface{} {
	return nil
}

func NewUser() *User {
	return &User{
		Options: make(map[string]interface{}),
	}
}

// UserMap handles the known users
type UserMap struct {
	Map   map[string]User
	robot *Robot
}

// NewUserMap returns an initialized UserMap
func NewUserMap(robot *Robot) *UserMap {
	return &UserMap{
		Map:   make(map[string]User, 0),
		robot: robot,
	}
}

// All returns the underlying map of all users
func (um *UserMap) All() []User {
	users := make([]User, len(um.Map))
	for _, user := range um.Map {
		users = append(users, user)
	}

	return users
}

// Get looks up a user by id and returns a User object
func (um *UserMap) Get(id string) (User, error) {
	user, ok := um.Map[id]
	if !ok {
		return User{}, fmt.Errorf("could not find user with id %s", id)
	}
	return user, nil
}

// GetByName looks up a user by name and returns a User object
func (um *UserMap) GetByName(name string) (User, error) {
	for _, user := range um.Map {
		if user.Name == name {
			if user.Options == nil {
				user.Options = make(map[string]interface{})
			}
			return user, nil
		}
	}
	return User{Options: make(map[string]interface{})}, fmt.Errorf("could not find user with name %s", name)
}

// Set adds or updates a user in the UserMap and persists it to the store
func (um *UserMap) Set(id string, user User) error {
	// initialize user.Options if nothing's in there yet
	if user.Options == nil {
		user.Options = make(map[string]interface{})
	}
	um.Map[id] = user
	if err := um.Save(); err != nil {
		return err
	}
	return nil
}

// Encode marshals a UserMap to JSON
func (um *UserMap) Encode() ([]byte, error) {
	data, err := json.Marshal(um.Map)
	if err != nil {
		return []byte{}, err
	}
	return data, err
}

// Decode unmarshals a JSON object into a map of strings to Users
func (um *UserMap) Decode() (map[string]User, error) {
	data, err := um.robot.Store.Get("users")
	if err != nil {
		return nil, err
	}

	users := map[string]User{}
	if err := json.Unmarshal(data, &users); err != nil {
		return users, err
	}

	return users, nil
}

// Load retrieves known users from the store and populates the UserMap
func (um *UserMap) Load() error {
	data, err := um.Decode()
	if err != nil {
		return err
	}

	um.Map = data
	return nil
}

// Save persists known users to the store
func (um *UserMap) Save() error {
	data, err := um.Encode()
	if err != nil {
		return err
	}

	return um.robot.Store.Set("users", data)
}
<commit_msg>add mutexes around user/usermap functions<commit_after>package hal

import (
	"encoding/json"
	"fmt"
	"sync"
)

// User is a chat participant
type User struct {
	ID      string
	Name    string
	Roles   []string
	Options map[string]interface{}
}

func (u *User) Get(k string) (interface{}, error) {
	v, ok := u.Options[k]
	if !ok {
		return nil, fmt.Errorf("%s not found in user options", k)
	}
	return v, nil
}

func NewUser() *User {
	return &User{
		Options: make(map[string]interface{}),
	}
}

// UserMap handles the known users
type UserMap struct {
	Map   map[string]User
	robot *Robot
	sync.Mutex
}

// NewUserMap returns an initialized UserMap
func NewUserMap(robot *Robot) *UserMap {
	return &UserMap{
		Map:   make(map[string]User, 0),
		robot: robot,
	}
}

// All returns the underlying map of all users
func (um *UserMap) All() []User {
	um.Lock()

	users := make([]User, len(um.Map))
	for _, user := range um.Map {
		users = append(users, user)
	}

	um.Unlock()
	return users
}

// Get looks up a user by id and returns a User object
func (um *UserMap) Get(id string) (User, error) {
	um.Lock()
	defer um.Unlock()

	user, ok := um.Map[id]
	if !ok {
		return User{}, fmt.Errorf("could not find user with id %s", id)
	}
	return user, nil
}

// GetByName looks up a user by name and returns a User object
func (um *UserMap) GetByName(name string) (User, error) {
	um.Lock()
	defer um.Unlock()

	for _, user := range um.Map {
		if user.Name == name {
			if user.Options == nil {
				user.Options = make(map[string]interface{})
			}
			return user, nil
		}
	}
	return User{Options: make(map[string]interface{})}, fmt.Errorf("could not find user with name %s", name)
}

// Set adds or updates a user in the UserMap and persists it to the store
func (um *UserMap) Set(id string, user User) error {
	um.Lock()

	// initialize user.Options if nothing's in there yet
	if user.Options == nil {
		user.Options = make(map[string]interface{})
	}
	um.Map[id] = user
	if err := um.Save(); err != nil {
		um.Unlock()
		return err
	}

	um.Unlock()
	return nil
}

// Encode marshals a UserMap to JSON
func (um *UserMap) Encode() ([]byte, error) {
	data, err := json.Marshal(um.Map)
	if err != nil {
		return []byte{}, err
	}
	return data, err
}

// Decode unmarshals a JSON object into a map of strings to Users
func (um *UserMap) Decode() (map[string]User, error) {
	data, err := um.robot.Store.Get("users")
	if err != nil {
		return nil, err
	}

	users := map[string]User{}
	if err := json.Unmarshal(data, &users); err != nil {
		return users, err
	}

	return users, nil
}

// Load retrieves known users from the store and populates the UserMap
func (um *UserMap) Load() error {
	um.Lock()

	data, err := um.Decode()
	if err != nil {
		um.Unlock()
		return err
	}

	um.Map = data

	um.Unlock()
	return nil
}

// Save persists known users to the store
func (um *UserMap) Save() error {
	data, err := um.Encode()
	if err != nil {
		return err
	}

	return um.robot.Store.Set("users", data)
}
<|endoftext|>


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

package textprot

import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/netflix/rend/common"
	"github.com/netflix/rend/metrics"
)

type TextParser struct {
	reader *bufio.Reader
}

func NewTextParser(reader *bufio.Reader) TextParser {
	return TextParser{
		reader: reader,
	}
}

func (t TextParser) Parse() (common.Request, common.RequestType, error) {
	data, err := t.reader.ReadString('\n')
	metrics.IncCounterBy(common.MetricBytesReadRemote, uint64(len(data)))

	if err != nil {
		if err == io.EOF {
			fmt.Println("End of file: Connection closed?")
		}

		fmt.Println(err.Error())
		return nil, common.RequestUnknown, err
	}

	clParts := strings.Split(strings.TrimSpace(data), " ")

	switch clParts[0] {
	case "set":
		return setRequest(t.reader, clParts, common.RequestSet)

	case "add":
		return setRequest(t.reader, clParts, common.RequestAdd)

	case "replace":
		return setRequest(t.reader, clParts, common.RequestReplace)

	case "get":
		if len(clParts) < 2 {
			return nil, common.RequestGet, common.ErrBadRequest
		}

		keys := make([][]byte, 0)
		for _, key := range clParts[1:] {
			keys = append(keys, []byte(key))
		}

		opaques := make([]uint32, len(keys))
		quiet := make([]bool, len(keys))

		return common.GetRequest{
			Keys:    keys,
			Opaques: opaques,
			Quiet:   quiet,
			NoopEnd: false,
		}, common.RequestGet, nil

	case "delete":
		if len(clParts) != 2 {
			return nil, common.RequestDelete, common.ErrBadRequest
		}

		return common.DeleteRequest{
			Key:    []byte(clParts[1]),
			Opaque: uint32(0),
		}, common.RequestDelete, nil

	// TODO: Error handling for invalid cmd line
	case "touch":
		if len(clParts) != 3 {
			return nil, common.RequestTouch, common.ErrBadRequest
		}

		key := []byte(clParts[1])

		exptime, err := strconv.ParseUint(strings.TrimSpace(clParts[2]), 10, 32)

		if err != nil {
			fmt.Println(err.Error())
			return nil, common.RequestSet, common.ErrBadRequest
		}

		return common.TouchRequest{
			Key:     key,
			Exptime: uint32(exptime),
			Opaque:  uint32(0),
		}, common.RequestTouch, nil

	case "noop":
		if len(clParts) != 1 {
			return nil, common.RequestNoop, common.ErrBadRequest
		}
		return common.NoopRequest{
			Opaque: 0,
		}, common.RequestNoop, nil

	case "quit":
		if len(clParts) != 1 {
			return nil, common.RequestQuit, common.ErrBadRequest
		}
		return common.QuitRequest{
			Opaque: 0,
			Quiet:  false,
		}, common.RequestQuit, nil

	case "version":
		if len(clParts) != 1 {
			return nil, common.RequestQuit, common.ErrBadRequest
		}
		return common.VersionRequest{
			Opaque: 0,
		}, common.RequestVersion, nil

	default:
		return nil, common.RequestUnknown, nil
	}
}

func setRequest(r *bufio.Reader, clParts []string, reqType common.RequestType) (common.SetRequest, common.RequestType, error) {
	// sanity check
	if len(clParts) != 5 {
		return common.SetRequest{}, reqType, common.ErrBadRequest
	}

	key := []byte(clParts[1])

	flags, err := strconv.ParseUint(strings.TrimSpace(clParts[2]), 10, 32)
	if err != nil {
		fmt.Println(err.Error())
		return common.SetRequest{}, reqType, common.ErrBadFlags
	}

	exptime, err := strconv.ParseUint(strings.TrimSpace(clParts[3]), 10, 32)
	if err != nil {
		fmt.Println(err.Error())
		return common.SetRequest{}, reqType, common.ErrBadExptime
	}

	length, err := strconv.ParseUint(strings.TrimSpace(clParts[4]), 10, 32)
	if err != nil {
		fmt.Println(err.Error())
		return common.SetRequest{}, reqType, common.ErrBadLength
	}

	// Read in data
	dataBuf := make([]byte, length)
	n, err := io.ReadAtLeast(r, dataBuf, int(length))
	metrics.IncCounterBy(common.MetricBytesReadRemote, uint64(n))
	if err != nil {
		return common.SetRequest{}, reqType, common.ErrInternal
	}

	// Consume the last two bytes "\r\n"
	r.Discard(2)
	metrics.IncCounterBy(common.MetricBytesReadRemote, 2)

	return common.SetRequest{
		Key:     key,
		Flags:   uint32(flags),
		Exptime: uint32(exptime),
		Opaque:  uint32(0),
		Data:    dataBuf,
	}, reqType, nil
}
<commit_msg>Changing text protocol set parsing to be more compatible with command line clients.<commit_after>// Copyright 2015 Netflix, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package textprot

import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/netflix/rend/common"
	"github.com/netflix/rend/metrics"
)

type TextParser struct {
	reader *bufio.Reader
}

func NewTextParser(reader *bufio.Reader) TextParser {
	return TextParser{
		reader: reader,
	}
}

func (t TextParser) Parse() (common.Request, common.RequestType, error) {
	data, err := t.reader.ReadString('\n')
	metrics.IncCounterBy(common.MetricBytesReadRemote, uint64(len(data)))

	if err != nil {
		if err == io.EOF {
			fmt.Println("End of file: Connection closed?")
		}

		fmt.Println(err.Error())
		return nil, common.RequestUnknown, err
	}

	clParts := strings.Split(strings.TrimSpace(data), " ")

	switch clParts[0] {
	case "set":
		return setRequest(t.reader, clParts, common.RequestSet)

	case "add":
		return setRequest(t.reader, clParts, common.RequestAdd)

	case "replace":
		return setRequest(t.reader, clParts, common.RequestReplace)

	case "get":
		if len(clParts) < 2 {
			return nil, common.RequestGet, common.ErrBadRequest
		}

		keys := make([][]byte, 0)
		for _, key := range clParts[1:] {
			keys = append(keys, []byte(key))
		}

		opaques := make([]uint32, len(keys))
		quiet := make([]bool, len(keys))

		return common.GetRequest{
			Keys:    keys,
			Opaques: opaques,
			Quiet:   quiet,
			NoopEnd: false,
		}, common.RequestGet, nil

	case "delete":
		if len(clParts) != 2 {
			return nil, common.RequestDelete, common.ErrBadRequest
		}

		return common.DeleteRequest{
			Key:    []byte(clParts[1]),
			Opaque: uint32(0),
		}, common.RequestDelete, nil

	// TODO: Error handling for invalid cmd line
	case "touch":
		if len(clParts) != 3 {
			return nil, common.RequestTouch, common.ErrBadRequest
		}

		key := []byte(clParts[1])

		exptime, err := strconv.ParseUint(strings.TrimSpace(clParts[2]), 10, 32)

		if err != nil {
			fmt.Println(err.Error())
			return nil, common.RequestSet, common.ErrBadRequest
		}

		return common.TouchRequest{
			Key:     key,
			Exptime: uint32(exptime),
			Opaque:  uint32(0),
		}, common.RequestTouch, nil

	case "noop":
		if len(clParts) != 1 {
			return nil, common.RequestNoop, common.ErrBadRequest
		}
		return common.NoopRequest{
			Opaque: 0,
		}, common.RequestNoop, nil

	case "quit":
		if len(clParts) != 1 {
			return nil, common.RequestQuit, common.ErrBadRequest
		}
		return common.QuitRequest{
			Opaque: 0,
			Quiet:  false,
		}, common.RequestQuit, nil

	case "version":
		if len(clParts) != 1 {
			return nil, common.RequestQuit, common.ErrBadRequest
		}
		return common.VersionRequest{
			Opaque: 0,
		}, common.RequestVersion, nil

	default:
		return nil, common.RequestUnknown, nil
	}
}

func setRequest(r *bufio.Reader, clParts []string, reqType common.RequestType) (common.SetRequest, common.RequestType, error) {
	// sanity check
	if len(clParts) != 5 {
		return common.SetRequest{}, reqType, common.ErrBadRequest
	}

	key := []byte(clParts[1])

	flags, err := strconv.ParseUint(strings.TrimSpace(clParts[2]), 10, 32)
	if err != nil {
		fmt.Println(err.Error())
		return common.SetRequest{}, reqType, common.ErrBadFlags
	}

	exptime, err := strconv.ParseUint(strings.TrimSpace(clParts[3]), 10, 32)
	if err != nil {
		fmt.Println(err.Error())
		return common.SetRequest{}, reqType, common.ErrBadExptime
	}

	length, err := strconv.ParseUint(strings.TrimSpace(clParts[4]), 10, 32)
	if err != nil {
		fmt.Println(err.Error())
		return common.SetRequest{}, reqType, common.ErrBadLength
	}

	// Read in data
	dataBuf := make([]byte, length)
	n, err := io.ReadAtLeast(r, dataBuf, int(length))
	metrics.IncCounterBy(common.MetricBytesReadRemote, uint64(n))
	if err != nil {
		return common.SetRequest{}, reqType, common.ErrInternal
	}

	// Consume the last two bytes "\r\n"
	r.ReadString(byte('\n'))
	metrics.IncCounterBy(common.MetricBytesReadRemote, 2)

	return common.SetRequest{
		Key:     key,
		Flags:   uint32(flags),
		Exptime: uint32(exptime),
		Opaque:  uint32(0),
		Data:    dataBuf,
	}, reqType, nil
}
<|endoftext|>


<commit_before>package s3gof3r

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
)

// convenience multipliers
const (
	_        = iota
	kb int64 = 1 << (10 * iota)
	mb
	gb
	tb
	pb
	eb
)

// Min and Max functions
func min64(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func max64(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}

// Error type and functions for http requests/responses
type respError struct {
	r *http.Response
	b bytes.Buffer
}

func newRespError(r *http.Response) *respError {
	e := new(respError)
	e.r = r
	io.Copy(&e.b, r.Body)
	r.Body.Close()
	return e
}

func (e *respError) Error() string {
	return fmt.Sprintf(
		"http status error:  %d: %q",
		e.r.StatusCode,
		e.b.String(),
	)
}

func md5Check(r io.ReadSeeker, given string) (err error) {
	h := md5.New()
	if _, err = io.Copy(h, r); err != nil {
		return
	}
	if _, err = r.Seek(0, 0); err != nil {
		return
	}
	calculated := fmt.Sprintf("%x", h.Sum(nil))
	if calculated != given {
		log.Println(base64.StdEncoding.EncodeToString(h.Sum(nil)))
		return fmt.Errorf("md5 mismatch. given:%s calculated:%s", given, calculated)
	}
	return nil
}

func bucketFromUrl(subdomain string) string {
	s := strings.Split(subdomain, ".")
	return strings.Join(s[:len(s)-1], ".")
}

func checkClose(c io.Closer, err *error) {
	cerr := c.Close()
	if *err == nil {
		*err = cerr
	}
}
<commit_msg>Parse errors from response body.<commit_after>package s3gof3r

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// convenience multipliers
const (
	_        = iota
	kb int64 = 1 << (10 * iota)
	mb
	gb
	tb
	pb
	eb
)

// Min and Max functions
func min64(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func max64(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}

// Error type and functions for http response
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
type respError struct {
	r         *http.Response
	Code      string
	Message   string
	Resource  string
	RequestId string
}

func newRespError(r *http.Response) *respError {
	e := new(respError)
	e.r = r
	b, _ := ioutil.ReadAll(r.Body)
	xml.NewDecoder(bytes.NewReader(b)).Decode(e) // parse error from response
	r.Body.Close()
	return e
}

func (e *respError) Error() string {
	return fmt.Sprintf(
		"Error:  %d: %q",
		e.r.StatusCode,
		e.Message,
	)
}

func md5Check(r io.ReadSeeker, given string) (err error) {
	h := md5.New()
	if _, err = io.Copy(h, r); err != nil {
		return
	}
	if _, err = r.Seek(0, 0); err != nil {
		return
	}
	calculated := fmt.Sprintf("%x", h.Sum(nil))
	if calculated != given {
		log.Println(base64.StdEncoding.EncodeToString(h.Sum(nil)))
		return fmt.Errorf("md5 mismatch. given:%s calculated:%s", given, calculated)
	}
	return nil
}

func bucketFromUrl(subdomain string) string {
	s := strings.Split(subdomain, ".")
	return strings.Join(s[:len(s)-1], ".")
}

func checkClose(c io.Closer, err *error) {
	cerr := c.Close()
	if *err == nil {
		*err = cerr
	}
}
<|endoftext|>


<commit_before>package manta

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"runtime"
	"strconv"
	"strings"

	"github.com/davecgh/go-spew/spew"
	"github.com/golang/protobuf/proto"
)

var debugMode, traceMode bool

func init() {
	if os.Getenv("DEBUG") != "" {
		debugMode = true
	}
}

var (
	_sprintf = fmt.Sprintf
	_sdump   = spew.Sdump
)

// Convert a string to an int32
func atoi32(s string) (int32, error) {
	n, err := strconv.ParseInt(s, 0, 32)
	if err != nil {
		return 0, err
	}
	return int32(n), nil
}

// printf only if debugging
func _debugf(format string, args ...interface{}) {
	if debugMode {
		args = append([]interface{}{_caller(2)}, args...)
		fmt.Printf("%s: "+format+"\n", args...)
	}
}

// printf only if tracing
func _tracef(format string, args ...interface{}) {
	if traceMode {
		args = append([]interface{}{_caller(2)}, args...)
		fmt.Printf("%s: "+format+"\n", args...)
	}
}

// error with printf syntax
func _errorf(format string, args ...interface{}) error {
	return fmt.Errorf(format, args...)
}

// panic with printf syntax
func _panicf(format string, args ...interface{}) {
	panic(fmt.Errorf(format, args...))
}

// dump named object only if debugging
func _dump(label string, args ...interface{}) {
	if debugMode {
		fmt.Printf("%s: %s", _caller(2), label)
		spew.Dump(args...)
	}
}

// dumps a given byte buffer to the given fixture filename
func _dump_fixture(filename string, buf []byte) {
	fmt.Printf("writing fixture %s...\n", filename)
	if err := ioutil.WriteFile("./fixtures/"+filename, buf, 0644); err != nil {
		panic(err)
	}
}

// reads a byte buffer from the given fixture filename
func _read_fixture(filename string) []byte {
	buf, err := ioutil.ReadFile("./fixtures/" + filename)
	if err != nil {
		panic(err)
	}
	return buf
}

// marshal a proto.Message to bytes
func _proto_marshal(obj proto.Message) []byte {
	buf, err := proto.Marshal(obj)
	if err != nil {
		panic(err)
	}
	return buf
}

// marshal an interface{} to JSON bytes
func _json_marshal(obj interface{}) []byte {
	buf, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		panic(err)
	}
	return buf
}

// Returns the name of the calling function
func _caller(n int) string {
	if pc, _, _, ok := runtime.Caller(n); ok {
		fns := strings.Split(runtime.FuncForPC(pc).Name(), "/")
		return fns[len(fns)-1]
	}

	return "unknown"
}

func log2(n int) int {
	return int(math.Log(float64(n))/math.Log(2)) + 1
}
<commit_msg>Added util.hasPrefix<commit_after>package manta

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"runtime"
	"strconv"
	"strings"

	"github.com/davecgh/go-spew/spew"
	"github.com/golang/protobuf/proto"
)

var debugMode, traceMode bool

func init() {
	if os.Getenv("DEBUG") != "" {
		debugMode = true
	}
}

var (
	_sprintf = fmt.Sprintf
	_sdump   = spew.Sdump
)

// Convert a string to an int32
func atoi32(s string) (int32, error) {
	n, err := strconv.ParseInt(s, 0, 32)
	if err != nil {
		return 0, err
	}
	return int32(n), nil
}

// printf only if debugging
func _debugf(format string, args ...interface{}) {
	if debugMode {
		args = append([]interface{}{_caller(2)}, args...)
		fmt.Printf("%s: "+format+"\n", args...)
	}
}

// printf only if tracing
func _tracef(format string, args ...interface{}) {
	if traceMode {
		args = append([]interface{}{_caller(2)}, args...)
		fmt.Printf("%s: "+format+"\n", args...)
	}
}

// error with printf syntax
func _errorf(format string, args ...interface{}) error {
	return fmt.Errorf(format, args...)
}

// panic with printf syntax
func _panicf(format string, args ...interface{}) {
	panic(fmt.Errorf(format, args...))
}

// dump named object only if debugging
func _dump(label string, args ...interface{}) {
	if debugMode {
		fmt.Printf("%s: %s", _caller(2), label)
		spew.Dump(args...)
	}
}

// dumps a given byte buffer to the given fixture filename
func _dump_fixture(filename string, buf []byte) {
	fmt.Printf("writing fixture %s...\n", filename)
	if err := ioutil.WriteFile("./fixtures/"+filename, buf, 0644); err != nil {
		panic(err)
	}
}

// reads a byte buffer from the given fixture filename
func _read_fixture(filename string) []byte {
	buf, err := ioutil.ReadFile("./fixtures/" + filename)
	if err != nil {
		panic(err)
	}
	return buf
}

// marshal a proto.Message to bytes
func _proto_marshal(obj proto.Message) []byte {
	buf, err := proto.Marshal(obj)
	if err != nil {
		panic(err)
	}
	return buf
}

// marshal an interface{} to JSON bytes
func _json_marshal(obj interface{}) []byte {
	buf, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		panic(err)
	}
	return buf
}

// Returns the name of the calling function
func _caller(n int) string {
	if pc, _, _, ok := runtime.Caller(n); ok {
		fns := strings.Split(runtime.FuncForPC(pc).Name(), "/")
		return fns[len(fns)-1]
	}

	return "unknown"
}

// Compares string with prefix
func hasPrefix(s, prefix string) bool {
	return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}

func log2(n int) int {
	return int(math.Log(float64(n))/math.Log(2)) + 1
}
<|endoftext|>


<commit_before>// Package snmputil provides helper routines for gosnmp
//
// Copyright 2016 Paul Stuart. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package snmputil

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	radix "github.com/hashicorp/go-immutable-radix"
	"github.com/pkg/errors"
	"github.com/soniah/gosnmp"
)

var (
	// Debug will log snmp debugging output if set
	Debug *log.Logger

	// lookupOID is a lookup table to find the dotted form of a symbolic name
	lookupOID = make(map[string]string)

	// done will terminate all polling processes if closed
	done = make(chan struct{})
	// how to break up column indexes with multiple elements
	multiName = strings.Fields("Grouping Member Element Item")
	rtree     = radix.New()
)

const (
	ifName  = ".1.3.6.1.2.1.31.1.1.1.1"
	ifAlias = ".1.3.6.1.2.1.31.1.1.1.18"
)

// Sender will send the interpreted PDU value to be saved or whathaveyou
type Sender func(string, map[string]string, interface{}, time.Time) error

// Criteria specifies what is to query and what to keep
type Criteria struct {
	OID     string            // OID can be dotted string or symbolic name
	Tags    map[string]string // any additional tags to associate
	Regexps []string          // filter resulting entries
	Keep    bool              // keep if resulting name matches, otherwise omit
}

// ErrFunc processes error and may be nil if desired
type ErrFunc func(error)

// numerical returns the parsed data type in its numeric form
func numerical(s string) (interface{}, error) {
	if f, err := strconv.ParseFloat(s, 64); err == nil {
		return f, nil
	}
	if i, err := strconv.ParseInt(s, 0, 64); err == nil {
		return i, nil
	}
	return s, fmt.Errorf("not a number")
}

// LoadOIDs reads a file of OIDs and their symbolic names
func LoadOIDs(in io.Reader) error {
	scanner := bufio.NewScanner(in)
	for scanner.Scan() {
		f := strings.Fields(scanner.Text())
		if len(f) < 2 {
			continue
		}
		// snmptranslate isn't providing leading dot
		if f[1][:1] != "." {
			f[1] = "." + f[1]
		}
		lookupOID[f[0]] = f[1]
		rtree, _, _ = rtree.Insert([]byte(f[1]), f[0])
	}
	return scanner.Err()
}

// LoadOIDFile is a helper routine to load OID descriptions from a file
func LoadOIDFile(filename string) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return LoadOIDs(f)
}

// makeString converts ascii octets into a string
func makeString(bits []string) string {
	chars := make([]byte, len(bits))
	for i, bit := range bits {
		n, _ := strconv.Atoi(bit)
		chars[i] = byte(n)
	}
	return string(chars)
}

// oidStrings converts ascii octets into an array of words
func oidStrings(in string) []string {
	words := []string{}
	bits := strings.Split(in, ".")
	for i := 0; i < len(bits); i++ {
		cnt, _ := strconv.Atoi(bits[i])
		end := i + cnt + 1
		if i > len(bits) || i >= end {
			break
		}
		if end > len(bits) {
			end = len(bits)
		}
		word := makeString(bits[i+1 : end])
		words = append(words, word)
		i += cnt
	}
	return words
}

// BulkColumns returns a gosnmp.WalkFunc that will process results from a bulkwalk
func BulkColumns(client *gosnmp.GoSNMP, crit Criteria, sender Sender, logger *log.Logger) (gosnmp.WalkFunc, error) {
	if logger == nil {
		logger = log.New(ioutil.Discard, "", 0)
	}
	// set up regexp filters
	filterNames := []*regexp.Regexp{}
	for _, n := range crit.Regexps {
		re, err := regexp.Compile(n)
		if err != nil {
			return nil, err
		}
		filterNames = append(filterNames, re)
	}

	// get interface column names and aliases
	columns := make(map[string]string)
	aliases := make(map[string]string)
	suffixValue := func(oid string, lookup map[string]string) error {
		fn := func(pdu gosnmp.SnmpPDU) error {
			switch pdu.Type {
			case gosnmp.OctetString:
				lookup[pdu.Name[len(oid)+2:]] = string(pdu.Value.([]byte))
			case gosnmp.IPAddress:
				fmt.Printf("IP ADDR TYPE: %x VALUE: %v\n", pdu.Type, pdu.Value)
				lookup[pdu.Name[len(oid)+2:]] = pdu.Value.(string)
			default:
				logger.Printf("UNKNOWN TYPE: %x VALUE: %v\n", pdu.Type, pdu.Value)
			}
			return nil
		}
		return BulkWalkAll(client, oid, fn)
	}
	if err := suffixValue(ifName, columns); err != nil {
		return nil, err
	}
	if err := suffixValue(ifAlias, aliases); err != nil {
		return nil, err
	}

	// our handler that will process each returned SNMP packet
	return func(pdu gosnmp.SnmpPDU) error {
		subOID, v, ok := rtree.Root().LongestPrefix([]byte(pdu.Name))
		if !ok {
			return errors.Errorf("cannot find name for OID: %s", pdu.Name)
		}
		name := v.(string)

		filtered := crit.Keep
		for _, r := range filterNames {
			if r.MatchString(name) {
				if crit.Keep {
					filtered = false
					break
				}
				logger.Printf("Omitting name: %s (%s)\n", name, subOID)
				return nil
			}
		}
		if filtered {
			logger.Printf("Not keeping name: %s (%s)\n", name, subOID)
			return nil
		}

		var column, alias string
		suffix := pdu.Name[len(subOID)+1:]
		group := oidStrings(suffix)

		// interface names/aliases only apply to OIDs starting with 'if'
		if strings.HasPrefix(name, "if") {
			column = columns[suffix]
			alias = aliases[suffix]
		}
		if len(group) == 0 && len(column) == 0 && suffix != "0" {
			column = makeString(strings.Split(suffix, "."))
		}

		t := map[string]string{}
		if len(column) > 0 {
			t["column"] = column
		}
		if len(alias) > 0 {
			t["alias"] = alias
		}
		if len(group) > 0 && len(group[0]) > 0 {
			t["grouping"] = group[0]
		}
		if len(group) > 1 && len(group[1]) > 0 {
			t["member"] = group[1]
		}
		if len(group) > 3 && len(group[1]) > 0 {
			t["element"] = group[2]
		}

		for k, v := range crit.Tags {
			t[k] = v
		}

		switch pdu.Type {
		case gosnmp.Integer, gosnmp.Counter32, gosnmp.Gauge32, gosnmp.TimeTicks, gosnmp.Counter64, gosnmp.Uinteger32:
		case gosnmp.IPAddress:
		case gosnmp.OctetString:
			s := string(pdu.Value.([]uint8))
			if n, err := numerical(s); err != nil {
				logger.Printf("%s (%x) - non numerical: %s\n", name, pdu.Type, s)
				pdu.Value = n
			}
		default:
			logger.Printf("%s - unsupported type: %x value: %v\n", name, pdu.Type, pdu.Value)
			return nil
		}
		return sender(name, t, pdu.Value, time.Now())
	}, nil
}

// GetOID will return the OID representing name
func GetOID(oid string) (string, error) {
	if strings.HasPrefix(oid, ".") {
		oid = oid[1:]
	}
	if strings.HasPrefix(oid, "1.") {
		return oid, nil
	}
	fixed, ok := lookupOID[oid]
	if !ok {
		return oid, fmt.Errorf("no OID found for %s", oid)
	}
	return fixed, nil
}

// BulkWalkAll applies bulk walk results to fn once all values returned (synchronously)
func BulkWalkAll(client *gosnmp.GoSNMP, oid string, fn gosnmp.WalkFunc) error {
	pdus, err := client.BulkWalkAll(oid)
	if err != nil {
		return err
	}
	for _, pdu := range pdus {
		if err := fn(pdu); err != nil {
			return err
		}
	}
	return nil
}

// Sampler will do a bulkwalk on the device specified using the given Profile
func Sampler(p Profile, crit Criteria, sender Sender) error {
	client, err := NewClient(p)
	if err != nil {
		return err
	}
	crit.OID, err = GetOID(crit.OID)
	if err != nil {
		return err
	}
	if sender == nil {
		sender = func(name string, tags map[string]string, value interface{}, when time.Time) error {
			if tags != nil && len(tags) > 0 {
				t := make([]string, 0, len(tags))
				for k, v := range tags {
					t = append(t, fmt.Sprintf("%s=%v", k, v))
				}
				fmt.Printf("Host:%s Name:%s Value:%v Tags:%s\n", client.Target, name, value, strings.Join(t, ","))
			} else {
				fmt.Printf("Host:%s Name:%s Value:%v\n", client.Target, name, value)
			}
			return nil
		}
	}
	walker, err := BulkColumns(client, crit, sender, nil)
	if err != nil {
		return err
	}
	return BulkWalkAll(client, crit.OID, walker)
}

// Bulkwalker will do a bulkwalk on the device specified in the Profile
func Bulkwalker(p Profile, crit Criteria, freq int, sender Sender, errFn ErrFunc, logger *log.Logger) error {
	client, err := NewClient(p)
	if err != nil {
		return err
	}
	crit.OID, err = GetOID(crit.OID)
	if err != nil {
		return err
	}
	if crit.Tags == nil {
		crit.Tags = make(map[string]string)
	}
	crit.Tags["host"] = client.Target
	if Debug != nil {
		client.Logger = Debug
	}
	walker, err := BulkColumns(client, crit, sender, logger)
	if err != nil {
		return err
	}
	go Poller(client, crit.OID, freq, walker, errFn)
	return nil
}

// Poller will make snmp requests indefinitely
func Poller(client *gosnmp.GoSNMP, oid string, freq int, walker gosnmp.WalkFunc, errFn ErrFunc) {

	c := time.Tick(time.Duration(freq) * time.Second)

	for {
		err := client.BulkWalk(oid, walker)
		if errFn != nil {
			errFn(err)
		}
		select {
		case _ = <-c:
			continue
		case _ = <-done:
			client.Conn.Close()
			return
		}
	}
}

// Quit will exit all active Pollers
func Quit() {
	close(done)
}
<commit_msg>fix column lookup<commit_after>// Package snmputil provides helper routines for gosnmp
//
// Copyright 2016 Paul Stuart. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package snmputil

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	radix "github.com/hashicorp/go-immutable-radix"
	"github.com/pkg/errors"
	"github.com/soniah/gosnmp"
)

var (
	// Debug will log snmp debugging output if set
	Debug *log.Logger

	// lookupOID is a lookup table to find the dotted form of a symbolic name
	lookupOID = make(map[string]string)

	// done will terminate all polling processes if closed
	done = make(chan struct{})
	// how to break up column indexes with multiple elements
	multiName = strings.Fields("Grouping Member Element Item")
	rtree     = radix.New()
)

const (
	ifName  = ".1.3.6.1.2.1.31.1.1.1.1"
	ifAlias = ".1.3.6.1.2.1.31.1.1.1.18"
)

// Sender will send the interpreted PDU value to be saved or whathaveyou
type Sender func(string, map[string]string, interface{}, time.Time) error

// Criteria specifies what is to query and what to keep
type Criteria struct {
	OID     string            // OID can be dotted string or symbolic name
	Tags    map[string]string // any additional tags to associate
	Regexps []string          // filter resulting entries
	Keep    bool              // keep if resulting name matches, otherwise omit
}

// ErrFunc processes error and may be nil if desired
type ErrFunc func(error)

// numerical returns the parsed data type in its numeric form
func numerical(s string) (interface{}, error) {
	if f, err := strconv.ParseFloat(s, 64); err == nil {
		return f, nil
	}
	if i, err := strconv.ParseInt(s, 0, 64); err == nil {
		return i, nil
	}
	return s, fmt.Errorf("not a number")
}

// LoadOIDs reads a file of OIDs and their symbolic names
func LoadOIDs(in io.Reader) error {
	scanner := bufio.NewScanner(in)
	for scanner.Scan() {
		f := strings.Fields(scanner.Text())
		if len(f) < 2 {
			continue
		}
		// snmptranslate isn't providing leading dot
		if f[1][:1] != "." {
			f[1] = "." + f[1]
		}
		lookupOID[f[0]] = f[1]
		rtree, _, _ = rtree.Insert([]byte(f[1]), f[0])
	}
	return scanner.Err()
}

// LoadOIDFile is a helper routine to load OID descriptions from a file
func LoadOIDFile(filename string) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return LoadOIDs(f)
}

// makeString converts ascii octets into a string
func makeString(bits []string) string {
	chars := make([]byte, len(bits))
	for i, bit := range bits {
		n, _ := strconv.Atoi(bit)
		chars[i] = byte(n)
	}
	return string(chars)
}

// oidStrings converts ascii octets into an array of words
func oidStrings(in string) []string {
	words := []string{}
	bits := strings.Split(in, ".")
	for i := 0; i < len(bits); i++ {
		cnt, _ := strconv.Atoi(bits[i])
		end := i + cnt + 1
		if i > len(bits) || i >= end {
			break
		}
		if end > len(bits) {
			end = len(bits)
		}
		word := makeString(bits[i+1 : end])
		words = append(words, word)
		i += cnt
	}
	return words
}

// BulkColumns returns a gosnmp.WalkFunc that will process results from a bulkwalk
func BulkColumns(client *gosnmp.GoSNMP, crit Criteria, sender Sender, logger *log.Logger) (gosnmp.WalkFunc, error) {
	if logger == nil {
		logger = log.New(ioutil.Discard, "", 0)
	}
	// set up regexp filters
	filterNames := []*regexp.Regexp{}
	for _, n := range crit.Regexps {
		re, err := regexp.Compile(n)
		if err != nil {
			return nil, err
		}
		filterNames = append(filterNames, re)
	}

	// get interface column names and aliases
	columns := make(map[string]string)
	aliases := make(map[string]string)
	suffixValue := func(oid string, lookup map[string]string) error {
		fn := func(pdu gosnmp.SnmpPDU) error {
			switch pdu.Type {
			case gosnmp.OctetString:
				lookup[pdu.Name[len(oid)+1:]] = string(pdu.Value.([]byte))
			default:
				logger.Printf("unknown type: %x value: %v\n", pdu.Type, pdu.Value)
			}
			return nil
		}
		return BulkWalkAll(client, oid, fn)
	}
	if err := suffixValue(ifName, columns); err != nil {
		return nil, err
	}
	if err := suffixValue(ifAlias, aliases); err != nil {
		return nil, err
	}

	// our handler that will process each returned SNMP packet
	return func(pdu gosnmp.SnmpPDU) error {
		subOID, v, ok := rtree.Root().LongestPrefix([]byte(pdu.Name))
		if !ok {
			return errors.Errorf("cannot find name for OID: %s", pdu.Name)
		}
		name := v.(string)

		filtered := crit.Keep
		for _, r := range filterNames {
			if r.MatchString(name) {
				if crit.Keep {
					filtered = false
					break
				}
				logger.Printf("omitting name: %s (%s)\n", name, subOID)
				return nil
			}
		}
		if filtered {
			logger.Printf("not keeping name: %s (%s)\n", name, subOID)
			return nil
		}

		var column, alias string
		suffix := pdu.Name[len(subOID)+1:]
		group := oidStrings(suffix)

		// interface names/aliases only apply to OIDs starting with 'if'
		if strings.HasPrefix(name, "if") {
			column = columns[suffix]
			alias = aliases[suffix]
		}
		if len(group) == 0 && len(column) == 0 && suffix != "0" {
			column = makeString(strings.Split(suffix, "."))
		}

		t := map[string]string{}
		if len(column) > 0 {
			t["column"] = column
		}
		if len(alias) > 0 {
			t["alias"] = alias
		}
		if len(group) > 0 && len(group[0]) > 0 {
			t["grouping"] = group[0]
		}
		if len(group) > 1 && len(group[1]) > 0 {
			t["member"] = group[1]
		}
		if len(group) > 3 && len(group[1]) > 0 {
			t["element"] = group[2]
		}

		for k, v := range crit.Tags {
			t[k] = v
		}

		switch pdu.Type {
		case gosnmp.Integer, gosnmp.Counter32, gosnmp.Gauge32, gosnmp.TimeTicks, gosnmp.Counter64, gosnmp.Uinteger32:
		case gosnmp.IPAddress:
		case gosnmp.OctetString:
			s := string(pdu.Value.([]uint8))
			if n, err := numerical(s); err != nil {
				logger.Printf("%s (%x) - non numerical: %s\n", name, pdu.Type, s)
				pdu.Value = n
			}
		default:
			logger.Printf("%s - unsupported type: %x value: %v\n", name, pdu.Type, pdu.Value)
			return nil
		}
		return sender(name, t, pdu.Value, time.Now())
	}, nil
}

// GetOID will return the OID representing name
func GetOID(oid string) (string, error) {
	if strings.HasPrefix(oid, ".") {
		oid = oid[1:]
	}
	if strings.HasPrefix(oid, "1.") {
		return oid, nil
	}
	fixed, ok := lookupOID[oid]
	if !ok {
		return oid, fmt.Errorf("no OID found for %s", oid)
	}
	return fixed, nil
}

// BulkWalkAll applies bulk walk results to fn once all values returned (synchronously)
func BulkWalkAll(client *gosnmp.GoSNMP, oid string, fn gosnmp.WalkFunc) error {
	pdus, err := client.BulkWalkAll(oid)
	if err != nil {
		return err
	}
	for _, pdu := range pdus {
		if err := fn(pdu); err != nil {
			return err
		}
	}
	return nil
}

// Sampler will do a bulkwalk on the device specified using the given Profile
func Sampler(p Profile, crit Criteria, sender Sender) error {
	client, err := NewClient(p)
	if err != nil {
		return err
	}
	crit.OID, err = GetOID(crit.OID)
	if err != nil {
		return err
	}
	if sender == nil {
		sender = func(name string, tags map[string]string, value interface{}, when time.Time) error {
			if tags != nil && len(tags) > 0 {
				t := make([]string, 0, len(tags))
				for k, v := range tags {
					t = append(t, fmt.Sprintf("%s=%v", k, v))
				}
				fmt.Printf("Host:%s Name:%s Value:%v Tags:%s\n", client.Target, name, value, strings.Join(t, ","))
			} else {
				fmt.Printf("Host:%s Name:%s Value:%v\n", client.Target, name, value)
			}
			return nil
		}
	}
	walker, err := BulkColumns(client, crit, sender, nil)
	if err != nil {
		return err
	}
	return BulkWalkAll(client, crit.OID, walker)
}

// Bulkwalker will do a bulkwalk on the device specified in the Profile
func Bulkwalker(p Profile, crit Criteria, freq int, sender Sender, errFn ErrFunc, logger *log.Logger) error {
	client, err := NewClient(p)
	if err != nil {
		return err
	}
	crit.OID, err = GetOID(crit.OID)
	if err != nil {
		return err
	}
	if crit.Tags == nil {
		crit.Tags = make(map[string]string)
	}
	crit.Tags["host"] = client.Target
	if Debug != nil {
		client.Logger = Debug
	}
	walker, err := BulkColumns(client, crit, sender, logger)
	if err != nil {
		return err
	}
	go Poller(client, crit.OID, freq, walker, errFn)
	return nil
}

// Poller will make snmp requests indefinitely
func Poller(client *gosnmp.GoSNMP, oid string, freq int, walker gosnmp.WalkFunc, errFn ErrFunc) {

	c := time.Tick(time.Duration(freq) * time.Second)

	for {
		err := client.BulkWalk(oid, walker)
		if errFn != nil {
			errFn(err)
		}
		select {
		case _ = <-c:
			continue
		case _ = <-done:
			client.Conn.Close()
			return
		}
	}
}

// Quit will exit all active Pollers
func Quit() {
	close(done)
}
<|endoftext|>


<commit_before>package s3gof3r

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// convenience multipliers
const (
	_        = iota
	kb int64 = 1 << (10 * iota)
	mb
	gb
	tb
	pb
	eb
)

// Min and Max functions
func min64(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func max64(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}

// Error type and functions for http response
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
type respError struct {
	r         *http.Response
	Code      string
	Message   string
	Resource  string
	RequestId string
}

func newRespError(r *http.Response) *respError {
	e := new(respError)
	e.r = r
	b, _ := ioutil.ReadAll(r.Body)
	xml.NewDecoder(bytes.NewReader(b)).Decode(e) // parse error from response
	r.Body.Close()
	return e
}

func (e *respError) Error() string {
	return fmt.Sprintf(
		"%d: %q",
		e.r.StatusCode,
		e.Message,
	)
}

func md5Check(r io.ReadSeeker, given string) (err error) {
	h := md5.New()
	if _, err = io.Copy(h, r); err != nil {
		return
	}
	if _, err = r.Seek(0, 0); err != nil {
		return
	}
	calculated := fmt.Sprintf("%x", h.Sum(nil))
	if calculated != given {
		log.Println(base64.StdEncoding.EncodeToString(h.Sum(nil)))
		return fmt.Errorf("md5 mismatch. given:%s calculated:%s", given, calculated)
	}
	return nil
}

func bucketFromUrl(subdomain string) string {
	s := strings.Split(subdomain, ".")
	return strings.Join(s[:len(s)-1], ".")
}

func checkClose(c io.Closer, err *error) {
	cerr := c.Close()
	if *err == nil {
		*err = cerr
	}
}
<commit_msg>Set StatusCode in newRespError. Remove response.<commit_after>package s3gof3r

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// convenience multipliers
const (
	_        = iota
	kb int64 = 1 << (10 * iota)
	mb
	gb
	tb
	pb
	eb
)

// Min and Max functions
func min64(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func max64(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}

// Error type and functions for http response
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
type respError struct {
	Code       string
	Message    string
	Resource   string
	RequestId  string
	StatusCode int
}

func newRespError(r *http.Response) *respError {
	e := new(respError)
	e.StatusCode = r.StatusCode
	b, _ := ioutil.ReadAll(r.Body)
	xml.NewDecoder(bytes.NewReader(b)).Decode(e) // parse error from response
	r.Body.Close()
	return e
}

func (e *respError) Error() string {
	return fmt.Sprintf(
		"%d: %q",
		e.StatusCode,
		e.Message,
	)
}

func md5Check(r io.ReadSeeker, given string) (err error) {
	h := md5.New()
	if _, err = io.Copy(h, r); err != nil {
		return
	}
	if _, err = r.Seek(0, 0); err != nil {
		return
	}
	calculated := fmt.Sprintf("%x", h.Sum(nil))
	if calculated != given {
		log.Println(base64.StdEncoding.EncodeToString(h.Sum(nil)))
		return fmt.Errorf("md5 mismatch. given:%s calculated:%s", given, calculated)
	}
	return nil
}

func bucketFromUrl(subdomain string) string {
	s := strings.Split(subdomain, ".")
	return strings.Join(s[:len(s)-1], ".")
}

func checkClose(c io.Closer, err *error) {
	cerr := c.Close()
	if *err == nil {
		*err = cerr
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"strconv"
	"unsafe"
)

func isSpace(c byte) bool {
	return c == ' ' || c == '\t' || c == '\r' || c == '\n'
}

// parseKey does key sanitization (see Key Format in the readme) and stops on ':', which indicates the end of
// the key. key is the sanitized key part (before the ':'), ok indicates whether this function successfully
// found a ':' to split on, forward indicates whether this key is to be forwarded (forwarded keys start with
// forwardKeyPrefix and that prefix is stripped from key), and rest is the remainder of the input after the
// ':'.
func parseKey(b []byte, forwardingEnabled bool) (key string, ok bool, forward bool, rest []byte) {
	var buf bytes.Buffer
	forward = forwardingEnabled
	for i, c := range b {
		if forward && i < len(forwardKeyPrefix) {
			forward = (c == forwardKeyPrefix[i])
			if forward && i == len(forwardKeyPrefix)-1 {
				// We're forwarding this key; strip the prefix
				buf.Reset()
				continue
			}
		}
		if c < ' ' || c > '~' { // Remove any byte that isn't a printable ascii char
			continue
		}
		switch c {
		case ' ': // Replace space with _
			c = '_'
		case '/': // Replace / with -
			c = '-'
		case '<', '>', '*', '[', ']', '{', '}': // Remove <, >, *, [, ], {, and }
			continue
		case ':': // End of key
			return buf.String(), true, forward, b[i+1:]
		}
		buf.WriteByte(c)
	}
	return "", false, false, nil
}

// TODO XXX HACK FIXME
// parseFloat reads a float64 from b. This uses unsafe hackery courtesy of
// https://code.google.com/p/go/issues/detail?id=2632#c16 -- if that issue gets fixed, we should switch to
// doing that instead of using an unsafe []byte -> string conversion.
func parseFloat(b []byte) (float64, error) {
	s := *(*string)(unsafe.Pointer(&b))
	return strconv.ParseFloat(s, 64)
}

// parseValue reads a float64 value off of b, expecting it to be followed by a | character.
func parseValue(b []byte) (f float64, ok bool, rest []byte) {
	endingPipe := false
	var i int
	var c byte
	for i, c = range b {
		if c == '|' {
			endingPipe = true
			break
		}
	}
	if !endingPipe {
		return 0, false, nil
	}
	f, err := parseFloat(b[:i])
	if err != nil {
		return 0, false, nil
	}
	return f, true, b[i+1:]
}

func parseMetricType(b []byte) (typ StatType, ok bool, rest []byte) {
	tag := b
	rest = nil
	for i, c := range b {
		if c == '|' {
			tag = b[:i]
			rest = b[i+1:]
			break
		}
	}

	typ, ok = tagToStatType(tag)
	if !ok {
		return 0, false, nil
	}
	return typ, true, rest
}

func parseRate(b []byte) (float64, bool) {
	if len(b) < 2 {
		return 0, false
	}
	if b[0] != '@' {
		return 0, false
	}
	f, err := parseFloat(b[1:])
	if err != nil {
		return 0, false
	}
	return f, true
}

func parseStatsdMessage(msg []byte, forwardingEnabled bool) (stat *Stat, ok bool) {
	stat = &Stat{}
	name, ok, forward, rest := parseKey(msg, forwardingEnabled)
	if !ok || name == "" { // empty name is invalid
		return nil, false
	}
	stat.Forward = forward
	stat.Name = name

	// NOTE: It looks like statsd will accept multiple values for a key at once (e.g., foo.bar:1|c:2.5|g), but
	// this isn't actually documented and I'm not going to support it for now.
	stat.Value, ok, rest = parseValue(rest)
	if !ok {
		return nil, false
	}
	stat.Type, ok, rest = parseMetricType(rest)
	if !ok {
		return nil, false
	}

	switch stat.Type {
	case StatSet, StatGauge:
		if len(rest) > 0 {
			return nil, false
		}
		return stat, true
	}

	rate := 1.0
	if len(rest) > 0 {
		rate, ok = parseRate(rest)
		if !ok {
			return nil, false
		}
		// Statsd ignores sample rates > 0, but I'm going to be more strict.
		if rate > 1.0 || rate <= 0 {
			return nil, false
		}
	}
	stat.SampleRate = rate
	return stat, true
}
<commit_msg>Use a slightly safer way of doing unsafe float conversions<commit_after>package main

import (
	"bytes"
	"reflect"
	"strconv"
	"unsafe"
)

func isSpace(c byte) bool {
	return c == ' ' || c == '\t' || c == '\r' || c == '\n'
}

// parseKey does key sanitization (see Key Format in the readme) and stops on ':', which indicates the end of
// the key. key is the sanitized key part (before the ':'), ok indicates whether this function successfully
// found a ':' to split on, forward indicates whether this key is to be forwarded (forwarded keys start with
// forwardKeyPrefix and that prefix is stripped from key), and rest is the remainder of the input after the
// ':'.
func parseKey(b []byte, forwardingEnabled bool) (key string, ok bool, forward bool, rest []byte) {
	var buf bytes.Buffer
	forward = forwardingEnabled
	for i, c := range b {
		if forward && i < len(forwardKeyPrefix) {
			forward = (c == forwardKeyPrefix[i])
			if forward && i == len(forwardKeyPrefix)-1 {
				// We're forwarding this key; strip the prefix
				buf.Reset()
				continue
			}
		}
		if c < ' ' || c > '~' { // Remove any byte that isn't a printable ascii char
			continue
		}
		switch c {
		case ' ': // Replace space with _
			c = '_'
		case '/': // Replace / with -
			c = '-'
		case '<', '>', '*', '[', ']', '{', '}': // Remove <, >, *, [, ], {, and }
			continue
		case ':': // End of key
			return buf.String(), true, forward, b[i+1:]
		}
		buf.WriteByte(c)
	}
	return "", false, false, nil
}

// TODO XXX HACK FIXME
// parseFloat reads a float64 from b. This uses unsafe hackery courtesy of
// https://code.google.com/p/go/issues/detail?id=2632#c16 -- if that issue gets fixed, we should switch to
// doing that instead of using an unsafe []byte -> string conversion.
func parseFloat(b []byte) (float64, error) {
	var s string
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	sh.Data = uintptr(unsafe.Pointer(&b[0]))
	sh.Len = len(b)
	return strconv.ParseFloat(s, 64)
}

// parseValue reads a float64 value off of b, expecting it to be followed by a | character.
func parseValue(b []byte) (f float64, ok bool, rest []byte) {
	endingPipe := false
	var i int
	var c byte
	for i, c = range b {
		if c == '|' {
			endingPipe = true
			break
		}
	}
	if !endingPipe {
		return 0, false, nil
	}
	f, err := parseFloat(b[:i])
	if err != nil {
		return 0, false, nil
	}
	return f, true, b[i+1:]
}

func parseMetricType(b []byte) (typ StatType, ok bool, rest []byte) {
	tag := b
	rest = nil
	for i, c := range b {
		if c == '|' {
			tag = b[:i]
			rest = b[i+1:]
			break
		}
	}

	typ, ok = tagToStatType(tag)
	if !ok {
		return 0, false, nil
	}
	return typ, true, rest
}

func parseRate(b []byte) (float64, bool) {
	if len(b) < 2 {
		return 0, false
	}
	if b[0] != '@' {
		return 0, false
	}
	f, err := parseFloat(b[1:])
	if err != nil {
		return 0, false
	}
	return f, true
}

func parseStatsdMessage(msg []byte, forwardingEnabled bool) (stat *Stat, ok bool) {
	stat = &Stat{}
	name, ok, forward, rest := parseKey(msg, forwardingEnabled)
	if !ok || name == "" { // empty name is invalid
		return nil, false
	}
	stat.Forward = forward
	stat.Name = name

	// NOTE: It looks like statsd will accept multiple values for a key at once (e.g., foo.bar:1|c:2.5|g), but
	// this isn't actually documented and I'm not going to support it for now.
	stat.Value, ok, rest = parseValue(rest)
	if !ok {
		return nil, false
	}
	stat.Type, ok, rest = parseMetricType(rest)
	if !ok {
		return nil, false
	}

	switch stat.Type {
	case StatSet, StatGauge:
		if len(rest) > 0 {
			return nil, false
		}
		return stat, true
	}

	rate := 1.0
	if len(rest) > 0 {
		rate, ok = parseRate(rest)
		if !ok {
			return nil, false
		}
		// Statsd ignores sample rates > 0, but I'm going to be more strict.
		if rate > 1.0 || rate <= 0 {
			return nil, false
		}
	}
	stat.SampleRate = rate
	return stat, true
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2016, Jörg Pernfuß <code.jpe@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/asaskevich/govalidator"
	"github.com/satori/go.uuid"
)

func CalculateLookupId(id uint64, metric string) string {
	asset := strconv.FormatUint(id, 10)
	hash := sha256.New()
	hash.Write([]byte(asset))
	hash.Write([]byte(metric))

	return hex.EncodeToString(hash.Sum(nil))
}

func Itemize(details *proto.Deployment) (string, *ConfigurationItem, error) {
	var (
		fqdn, dns_zone string
		err            error
	)
	lookupID := CalculateLookupId(details.Node.AssetId, details.Metric.Path)

	item := &ConfigurationItem{
		Metric:   details.Metric.Path,
		Interval: details.CheckConfig.Interval,
		HostId:   strconv.FormatUint(details.Node.AssetId, 10),
		Metadata: ConfigurationMetaData{
			Monitoring: details.Monitoring.Name,
			Team:       details.Team.Name,
		},
		Thresholds: []ConfigurationThreshold{},
	}
	if item.ConfigurationItemId, err = uuid.FromString(details.CheckInstance.InstanceId); err != nil {
		return "", nil, err
	}

	// set oncall duty if available
	if details.Oncall != nil && details.Oncall.Id != "" {
		item.Oncall = fmt.Sprintf("%s (%s)", details.Oncall.Name, details.Oncall.Number)
	}

	// construct item.Metadata.Targethost with help of system properties
	if details.Properties != nil {
		for _, prop := range *details.Properties {
			switch prop.Name {
			case "fqdn":
				fqdn = prop.Value
			case "dns_zone":
				dns_zone = prop.Value
			}
		}
	}
	switch {
	case len(fqdn) > 0:
		item.Metadata.Targethost = fqdn
	case len(dns_zone) > 0:
		item.Metadata.Targethost = fmt.Sprintf("%s.%s", details.Node.Name, dns_zone)
	default:
		item.Metadata.Targethost = details.Node.Name
	}

	// construct item.Metadata.Source
	if details.Service != nil {
		item.Metadata.Source = fmt.Sprintf("%s, %s", details.Service.Name, details.CheckConfig.Name)
	} else {
		item.Metadata.Source = fmt.Sprintf("System (%s), %s", details.Node.Name, details.CheckConfig.Name)
	}

	// slurp all thresholds
	for _, thr := range details.CheckConfig.Thresholds {
		t := ConfigurationThreshold{
			Predicate: thr.Predicate.Symbol,
			Level:     thr.Level.Numeric,
			Value:     thr.Value,
		}
		item.Thresholds = append(item.Thresholds, t)
	}

	govalidator.SetFieldsRequiredByDefault(true)
	if ok, err := govalidator.ValidateStruct(item); !ok {
		log.Println(err)
		return "", nil, err
	}
	return lookupID, item, nil
}

func GetServiceAttributeValue(details *proto.Deployment, attribute string) string {
	if details.Service == nil {
		return ``
	}
	if len(details.Service.Attributes) == 0 {
		return ``
	}
	for _, attr := range details.Service.Attributes {
		if attr.Name == attribute {
			return attr.Value
		}
	}
	return ``
}

func abortOnError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

// 200
func dispatchJsonOK(w *http.ResponseWriter, jsonb *[]byte) {
	(*w).Header().Set("Content-Type", "application/json")
	(*w).WriteHeader(http.StatusOK)
	(*w).Write(*jsonb)
}

// 204
func dispatchNoContent(w *http.ResponseWriter) {
	(*w).WriteHeader(http.StatusNoContent)
	(*w).Write(nil)
}

// 400
func dispatchBadRequest(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusBadRequest)
	log.Println(err)
}

// 404
func dispatchNotFound(w *http.ResponseWriter) {
	http.Error(*w, "No items found", http.StatusNotFound)
	log.Println("No items found")
}

// 410
func dispatchGone(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusGone)
	log.Println(err)
}

// 412
func dispatchPrecondition(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusPreconditionFailed)
	log.Println(err)
}

// 422
func dispatchUnprocessable(w *http.ResponseWriter, err string) {
	http.Error(*w, err, 422)
	log.Println(err)
}

// 500
func dispatchInternalServerError(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusInternalServerError)
	if Eye.Volatile {
		log.Fatal(err)
	}
	log.Println(err)
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Add support for disk metrics<commit_after>/*
Copyright (c) 2016, Jörg Pernfuß <code.jpe@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/asaskevich/govalidator"
	"github.com/satori/go.uuid"
)

func CalculateLookupId(id uint64, metric string) string {
	asset := strconv.FormatUint(id, 10)
	hash := sha256.New()
	hash.Write([]byte(asset))
	hash.Write([]byte(metric))

	return hex.EncodeToString(hash.Sum(nil))
}

func Itemize(details *proto.Deployment) (string, *ConfigurationItem, error) {
	var (
		fqdn, dns_zone string
		err            error
	)
	lookupID := CalculateLookupId(details.Node.AssetId, details.Metric.Path)

	item := &ConfigurationItem{
		Metric:   details.Metric.Path,
		Interval: details.CheckConfig.Interval,
		HostId:   strconv.FormatUint(details.Node.AssetId, 10),
		Metadata: ConfigurationMetaData{
			Monitoring: details.Monitoring.Name,
			Team:       details.Team.Name,
		},
		Thresholds: []ConfigurationThreshold{},
	}
	if item.ConfigurationItemId, err = uuid.FromString(details.CheckInstance.InstanceId); err != nil {
		return "", nil, err
	}

	switch item.Metric {
	case `disk.write.per.second`:
		fallthrough
	case `disk.read.per.second`:
		fallthrough
	case `disk.free`:
		fallthrough
	case `disk.usage.percent`:
		mpt := GetServiceAttributeValue(details, `filesystem`)
		if mpt == `` {
			return ``, nil, fmt.Errorf(`Disk metric is missing filesystem service attribute`)
		}
		item.Metric = fmt.Sprintf("%s:%s", item.Metric, mpt)
		// recalculate lookupID
		lookupID = CalculateLookupId(details.Node.AssetId, item.Metric)
	}

	// set oncall duty if available
	if details.Oncall != nil && details.Oncall.Id != "" {
		item.Oncall = fmt.Sprintf("%s (%s)", details.Oncall.Name, details.Oncall.Number)
	}

	// construct item.Metadata.Targethost with help of system properties
	if details.Properties != nil {
		for _, prop := range *details.Properties {
			switch prop.Name {
			case "fqdn":
				fqdn = prop.Value
			case "dns_zone":
				dns_zone = prop.Value
			}
		}
	}
	switch {
	case len(fqdn) > 0:
		item.Metadata.Targethost = fqdn
	case len(dns_zone) > 0:
		item.Metadata.Targethost = fmt.Sprintf("%s.%s", details.Node.Name, dns_zone)
	default:
		item.Metadata.Targethost = details.Node.Name
	}

	// construct item.Metadata.Source
	if details.Service != nil {
		item.Metadata.Source = fmt.Sprintf("%s, %s", details.Service.Name, details.CheckConfig.Name)
	} else {
		item.Metadata.Source = fmt.Sprintf("System (%s), %s", details.Node.Name, details.CheckConfig.Name)
	}

	// slurp all thresholds
	for _, thr := range details.CheckConfig.Thresholds {
		t := ConfigurationThreshold{
			Predicate: thr.Predicate.Symbol,
			Level:     thr.Level.Numeric,
			Value:     thr.Value,
		}
		item.Thresholds = append(item.Thresholds, t)
	}

	govalidator.SetFieldsRequiredByDefault(true)
	if ok, err := govalidator.ValidateStruct(item); !ok {
		log.Println(err)
		return "", nil, err
	}
	return lookupID, item, nil
}

func GetServiceAttributeValue(details *proto.Deployment, attribute string) string {
	if details.Service == nil {
		return ``
	}
	if len(details.Service.Attributes) == 0 {
		return ``
	}
	for _, attr := range details.Service.Attributes {
		if attr.Name == attribute {
			return attr.Value
		}
	}
	return ``
}

func abortOnError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

// 200
func dispatchJsonOK(w *http.ResponseWriter, jsonb *[]byte) {
	(*w).Header().Set("Content-Type", "application/json")
	(*w).WriteHeader(http.StatusOK)
	(*w).Write(*jsonb)
}

// 204
func dispatchNoContent(w *http.ResponseWriter) {
	(*w).WriteHeader(http.StatusNoContent)
	(*w).Write(nil)
}

// 400
func dispatchBadRequest(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusBadRequest)
	log.Println(err)
}

// 404
func dispatchNotFound(w *http.ResponseWriter) {
	http.Error(*w, "No items found", http.StatusNotFound)
	log.Println("No items found")
}

// 410
func dispatchGone(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusGone)
	log.Println(err)
}

// 412
func dispatchPrecondition(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusPreconditionFailed)
	log.Println(err)
}

// 422
func dispatchUnprocessable(w *http.ResponseWriter, err string) {
	http.Error(*w, err, 422)
	log.Println(err)
}

// 500
func dispatchInternalServerError(w *http.ResponseWriter, err string) {
	http.Error(*w, err, http.StatusInternalServerError)
	if Eye.Volatile {
		log.Fatal(err)
	}
	log.Println(err)
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"runtime/debug"
	"strings"

)

func PanicCatcher(w http.ResponseWriter) {
	if r := recover(); r != nil {
		log.Printf("%s\n", debug.Stack())
		msg := fmt.Sprintf("PANIC! %s", r)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
}

func DecodeJsonBody(r *http.Request, s interface{}) error {
	decoder := json.NewDecoder(r.Body)
	var err error

	switch s.(type) {
	case *proto.Request:
		c := s.(*proto.Request)
		err = decoder.Decode(c)
	case *auth.Kex:
		c := s.(*auth.Kex)
		err = decoder.Decode(c)
	default:
		rt := reflect.TypeOf(s)
		//return fmt.Errorf("DecodeJsonBody: Unhandled request type: %s", rt)
		// XXX Dev Setting
		errMsg := fmt.Sprintf("DecodeJsonBody: Unhandled request type: %s", rt)
		log.Fatal(errMsg)
	}
	if err != nil {
		return err
	}
	return nil
}

func ResultLength(r *somaResult, t ErrorMarker) int {
	switch t.(type) {
	case *proto.Result:
		switch {
		case r.Datacenters != nil:
			return len(r.Datacenters)
		case r.Levels != nil:
			return len(r.Levels)
		case r.Predicates != nil:
			return len(r.Predicates)
		case r.Status != nil:
			return len(r.Status)
		case r.Oncall != nil:
			return len(r.Oncall)
		case r.Teams != nil:
			return len(r.Teams)
		case r.Nodes != nil:
			return len(r.Nodes)
		case r.Views != nil:
			return len(r.Views)
		case r.Servers != nil:
			return len(r.Servers)
		case r.Units != nil:
			return len(r.Units)
		case r.Providers != nil:
			return len(r.Providers)
		case r.Metrics != nil:
			return len(r.Metrics)
		case r.Modes != nil:
			return len(r.Modes)
		case r.Users != nil:
			return len(r.Users)
		case r.Systems != nil:
			return len(r.Systems)
		case r.Capabilities != nil:
			return len(r.Capabilities)
		case r.Properties != nil:
			return len(r.Properties)
		case r.Attributes != nil:
			return len(r.Attributes)
		case r.Repositories != nil:
			return len(r.Repositories)
		case r.Buckets != nil:
			return len(r.Buckets)
		case r.Groups != nil:
			return len(r.Groups)
		case r.Clusters != nil:
			return len(r.Clusters)
		case r.CheckConfigs != nil:
			return len(r.CheckConfigs)
		case r.Validity != nil:
			return len(r.Validity)
		case r.HostDeployments != nil:
			if len(r.Deployments) > len(r.HostDeployments) {
				return len(r.Deployments)
			}
			return len(r.HostDeployments)
		case r.Deployments != nil:
			return len(r.Deployments)
		}
	default:
		return 0
	}
	return 0
}

func DispatchBadRequest(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusBadRequest)
		return
	}
	http.Error(*w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}

func DispatchUnauthorized(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusUnauthorized)
		return
	}
	http.Error(*w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}

func DispatchForbidden(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusForbidden)
		return
	}
	http.Error(*w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

func DispatchNotFound(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusNotFound)
		return
	}
	http.Error(*w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}

func DispatchConflict(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusConflict)
		return
	}
	http.Error(*w, http.StatusText(http.StatusConflict), http.StatusConflict)
}

func DispatchInternalError(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Error(*w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

func DispatchJsonReply(w *http.ResponseWriter, b *[]byte) {
	(*w).Header().Set("Content-Type", "application/json")
	(*w).WriteHeader(http.StatusOK)
	(*w).Write(*b)
}

func DispatchOctetReply(w *http.ResponseWriter, b *[]byte) {
	(*w).Header().Set("Content-Type", `application/octet-stream`)
	(*w).WriteHeader(http.StatusOK)
	(*w).Write(*b)
}

func GetPropertyTypeFromUrl(u *url.URL) (string, error) {
	// strip surrounding / and skip first path element `property|filter`
	el := strings.Split(strings.Trim(u.Path, "/"), "/")[1:]
	if el[0] == "property" {
		// looks like the path was /filter/property/...
		el = el[1:]
	}
	switch el[0] {
	case "service":
		switch el[1] {
		case "team":
			return "service", nil
		case "global":
			return "template", nil
		default:
			return "", errors.New("Unknown service property type")
		}
	default:
		return el[0], nil
	}
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Import extractAddress function<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"runtime/debug"
	"strings"

)

func PanicCatcher(w http.ResponseWriter) {
	if r := recover(); r != nil {
		log.Printf("%s\n", debug.Stack())
		msg := fmt.Sprintf("PANIC! %s", r)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
}

func DecodeJsonBody(r *http.Request, s interface{}) error {
	decoder := json.NewDecoder(r.Body)
	var err error

	switch s.(type) {
	case *proto.Request:
		c := s.(*proto.Request)
		err = decoder.Decode(c)
	case *auth.Kex:
		c := s.(*auth.Kex)
		err = decoder.Decode(c)
	default:
		rt := reflect.TypeOf(s)
		//return fmt.Errorf("DecodeJsonBody: Unhandled request type: %s", rt)
		// XXX Dev Setting
		errMsg := fmt.Sprintf("DecodeJsonBody: Unhandled request type: %s", rt)
		log.Fatal(errMsg)
	}
	if err != nil {
		return err
	}
	return nil
}

func ResultLength(r *somaResult, t ErrorMarker) int {
	switch t.(type) {
	case *proto.Result:
		switch {
		case r.Datacenters != nil:
			return len(r.Datacenters)
		case r.Levels != nil:
			return len(r.Levels)
		case r.Predicates != nil:
			return len(r.Predicates)
		case r.Status != nil:
			return len(r.Status)
		case r.Oncall != nil:
			return len(r.Oncall)
		case r.Teams != nil:
			return len(r.Teams)
		case r.Nodes != nil:
			return len(r.Nodes)
		case r.Views != nil:
			return len(r.Views)
		case r.Servers != nil:
			return len(r.Servers)
		case r.Units != nil:
			return len(r.Units)
		case r.Providers != nil:
			return len(r.Providers)
		case r.Metrics != nil:
			return len(r.Metrics)
		case r.Modes != nil:
			return len(r.Modes)
		case r.Users != nil:
			return len(r.Users)
		case r.Systems != nil:
			return len(r.Systems)
		case r.Capabilities != nil:
			return len(r.Capabilities)
		case r.Properties != nil:
			return len(r.Properties)
		case r.Attributes != nil:
			return len(r.Attributes)
		case r.Repositories != nil:
			return len(r.Repositories)
		case r.Buckets != nil:
			return len(r.Buckets)
		case r.Groups != nil:
			return len(r.Groups)
		case r.Clusters != nil:
			return len(r.Clusters)
		case r.CheckConfigs != nil:
			return len(r.CheckConfigs)
		case r.Validity != nil:
			return len(r.Validity)
		case r.HostDeployments != nil:
			if len(r.Deployments) > len(r.HostDeployments) {
				return len(r.Deployments)
			}
			return len(r.HostDeployments)
		case r.Deployments != nil:
			return len(r.Deployments)
		}
	default:
		return 0
	}
	return 0
}

func DispatchBadRequest(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusBadRequest)
		return
	}
	http.Error(*w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}

func DispatchUnauthorized(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusUnauthorized)
		return
	}
	http.Error(*w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}

func DispatchForbidden(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusForbidden)
		return
	}
	http.Error(*w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

func DispatchNotFound(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusNotFound)
		return
	}
	http.Error(*w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}

func DispatchConflict(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusConflict)
		return
	}
	http.Error(*w, http.StatusText(http.StatusConflict), http.StatusConflict)
}

func DispatchInternalError(w *http.ResponseWriter, err error) {
	if err != nil {
		http.Error(*w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Error(*w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

func DispatchJsonReply(w *http.ResponseWriter, b *[]byte) {
	(*w).Header().Set("Content-Type", "application/json")
	(*w).WriteHeader(http.StatusOK)
	(*w).Write(*b)
}

func DispatchOctetReply(w *http.ResponseWriter, b *[]byte) {
	(*w).Header().Set("Content-Type", `application/octet-stream`)
	(*w).WriteHeader(http.StatusOK)
	(*w).Write(*b)
}

func GetPropertyTypeFromUrl(u *url.URL) (string, error) {
	// strip surrounding / and skip first path element `property|filter`
	el := strings.Split(strings.Trim(u.Path, "/"), "/")[1:]
	if el[0] == "property" {
		// looks like the path was /filter/property/...
		el = el[1:]
	}
	switch el[0] {
	case "service":
		switch el[1] {
		case "team":
			return "service", nil
		case "global":
			return "template", nil
		default:
			return "", errors.New("Unknown service property type")
		}
	default:
		return el[0], nil
	}
}

// extractAddress extracts the IP address part of the IP:port string
// set as net/http.Request.RemoteAddr. It handles IPv4 cases like
// 192.0.2.1:48467 and IPv6 cases like [2001:db8::1%lo0]:48467
func extractAddress(str string) string {
	var addr string

	switch {
	case strings.Contains(str, `]`):
		// IPv6 address [2001:db8::1%lo0]:48467
		addr = strings.Split(str, `]`)[0]
		addr = strings.Split(addr, `%`)[0]
		addr = strings.TrimLeft(addr, `[`)
	default:
		// IPv4 address 192.0.2.1:48467
		addr = strings.Split(str, `:`)[0]
	}
	return addr
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


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

package gocui

import (
	"bytes"
	"errors"
	"io"
	"strings"

	"github.com/nsf/termbox-go"
)

// A View is a window. It maintains its own internal buffer and cursor
// position.
type View struct {
	name           string
	x0, y0, x1, y1 int
	ox, oy         int
	cx, cy         int
	lines          [][]rune
	overwrite      bool // overwrite in edit mode
	readOffset     int
	readCache      string

	// BgColor and FgColor allow to configure the background and foreground
	// colors of the View.
	BgColor, FgColor Attribute

	// SelBgColor and SelFgColor are used to configure the background and
	// foreground colors of the selected line, when it is highlighted.
	SelBgColor, SelFgColor Attribute

	// If Editable is true, keystrokes will be added to the view's internal
	// buffer at the cursor position.
	Editable bool

	// If Highlight is true, Sel{Bg,Fg}Colors will be used
	// for the line under the cursor position.
	Highlight bool

	// If Frame is true, a border will be drawn around the view.
	Frame bool

	// If Wrap is true, the content that is written to this View is
	// automatically wrapped when it is longer than its width.
	Wrap bool

	// If Wrap is true, each wrapping line is prefixed with this prefix.
	WrapPrefix string
}

// newView returns a new View object.
func newView(name string, x0, y0, x1, y1 int) *View {
	v := &View{
		name:  name,
		x0:    x0,
		y0:    y0,
		x1:    x1,
		y1:    y1,
		Frame: true,
	}
	return v
}

// Size returns the number of visible columns and rows in the View.
func (v *View) Size() (x, y int) {
	return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
}

// Name returns the name of the view.
func (v *View) Name() string {
	return v.name
}

// setRune writes a rune at the given point, relative to the view. It
// checks if the position is valid and applies the view's colors, taking
// into account if the cell must be highlighted.
func (v *View) setRune(x, y int, ch rune) error {
	maxX, maxY := v.Size()
	if x < 0 || x >= maxX || y < 0 || y >= maxY {
		return errors.New("invalid point")
	}

	var fgColor, bgColor Attribute
	if v.Highlight && y == v.cy {
		fgColor = v.SelFgColor
		bgColor = v.SelBgColor
	} else {
		fgColor = v.FgColor
		bgColor = v.BgColor
	}
	termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
		termbox.Attribute(fgColor), termbox.Attribute(bgColor))
	return nil
}

// SetCursor sets the cursor position of the view at the given point,
// relative to the view. It checks if the position is valid.
func (v *View) SetCursor(x, y int) error {
	maxX, maxY := v.Size()
	if x < 0 || x >= maxX || y < 0 || y >= maxY {
		return errors.New("invalid point")
	}
	v.cx = x
	v.cy = y
	return nil
}

// Cursor returns the cursor position of the view.
func (v *View) Cursor() (x, y int) {
	return v.cx, v.cy
}

// SetOrigin sets the origin position of the view's internal buffer,
// so the buffer starts to be printed from this point, which means that
// it is linked with the origin point of view. It can be used to
// implement Horizontal and Vertical scrolling with just incrementing
// or decrementing ox and oy.
func (v *View) SetOrigin(x, y int) error {
	if x < 0 || y < 0 {
		return errors.New("invalid point")
	}
	v.ox = x
	v.oy = y
	return nil
}

// Origin returns the origin position of the view.
func (v *View) Origin() (x, y int) {
	return v.ox, v.oy
}

// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (v *View) Write(p []byte) (n int, err error) {
	for _, ch := range bytes.Runes(p) {
		switch ch {
		case '\n':
			v.lines = append(v.lines, nil)
		case '\r':
			nl := len(v.lines)
			if nl > 0 {
				v.lines[nl-1] = nil
			} else {
				v.lines = make([][]rune, 1)
			}
		default:
			nl := len(v.lines)
			if nl > 0 {
				v.lines[nl-1] = append(v.lines[nl-1], ch)
			} else {
				v.lines = make([][]rune, 1)
				v.lines[0] = append(v.lines[0], ch)
			}
		}
	}
	return len(p), nil
}

// Read reads data into p. It returns the number of bytes read into p.
// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
// cache to be refreshed with the contents of the view.
func (v *View) Read(p []byte) (n int, err error) {
	if v.readOffset == 0 {
		v.readCache = v.Buffer()
	}
	if v.readOffset < len(v.readCache) {
		n = copy(p, v.readCache[v.readOffset:])
		v.readOffset += n
	} else {
		err = io.EOF
	}
	return
}

// Rewind sets the offset for the next Read to 0, which also refresh the
// read cache.
func (v *View) Rewind() {
	v.readOffset = 0
}

// draw re-draws the view's contents.
func (v *View) draw() error {
	maxX, maxY := v.Size()
	y := 0
	for i, line := range v.lines {
		if i < v.oy {
			continue
		}
		x := 0
		for j, ch := range line {
			if j < v.ox {
				continue
			}
			if x == maxX && v.Wrap {
				x = 0
				y++
				for _, p := range v.WrapPrefix + string(ch) {
					if x >= maxX || y >= maxY {
						break
					}
					if err := v.setRune(x, y, p); err != nil {
						return err
					}
					x++
				}
			} else if x < maxX && y < maxY {
				if err := v.setRune(x, y, ch); err != nil {
					return err
				}
				x++
			}
		}
		y++
	}
	return nil
}

// Clear empties the view's internal buffer.
func (v *View) Clear() {
	v.lines = nil
	v.clearRunes()
}

// clearRunes erases all the cells in the view.
func (v *View) clearRunes() {
	maxX, maxY := v.Size()
	for x := 0; x < maxX; x++ {
		for y := 0; y < maxY; y++ {
			termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
				termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
		}
	}
}

// writeRune writes a rune into the view's internal buffer, at the
// position corresponding to the point (x, y). The length of the internal
// buffer is increased if the point is out of bounds. Overwrite mode is
// governed by the value of View.overwrite.
func (v *View) writeRune(x, y int, ch rune) error {
	x = v.ox + x
	y = v.oy + y

	if x < 0 || y < 0 {
		return errors.New("invalid point")
	}

	if y >= len(v.lines) {
		if y >= cap(v.lines) {
			s := make([][]rune, y+1, (y+1)*2)
			copy(s, v.lines)
			v.lines = s
		} else {
			v.lines = v.lines[:y+1]
		}
	}
	if v.lines[y] == nil {
		v.lines[y] = make([]rune, x+1, (x+1)*2)
	} else if x >= len(v.lines[y]) {
		if x >= cap(v.lines[y]) {
			s := make([]rune, x+1, (x+1)*2)
			copy(s, v.lines[y])
			v.lines[y] = s
		} else {
			v.lines[y] = v.lines[y][:x+1]
		}
	}
	if !v.overwrite {
		v.lines[y] = append(v.lines[y], ' ')
		copy(v.lines[y][x+1:], v.lines[y][x:])
	}
	v.lines[y][x] = ch
	return nil
}

// deleteRune removes a rune from the view's internal buffer, at the
// position corresponding to the point (x, y).
func (v *View) deleteRune(x, y int) error {
	x = v.ox + x
	y = v.oy + y

	if x < 0 || y < 0 || y >= len(v.lines) || v.lines[y] == nil || x >= len(v.lines[y]) {
		return errors.New("invalid point")
	}
	copy(v.lines[y][x:], v.lines[y][x+1:])
	v.lines[y][len(v.lines[y])-1] = ' '
	return nil
}

// addLine adds a line into the view's internal buffer at the position
// corresponding to the point (x, y).
func (v *View) addLine(y int) error {
	y = v.oy + y

	if y < 0 || y >= len(v.lines) {
		return errors.New("invalid point")
	}
	v.lines = append(v.lines, nil)
	copy(v.lines[y+1:], v.lines[y:])
	v.lines[y] = nil
	return nil
}

// Buffer returns a string with the contents of the view's internal
// buffer
func (v *View) Buffer() string {
	str := ""
	for _, l := range v.lines {
		str += string(l) + "\n"
	}
	return strings.Replace(str, "\x00", " ", -1)
}

// Line returns a string with the line of the view's internal buffer
// at the position corresponding to the point (x, y).
func (v *View) Line(y int) (string, error) {
	y = v.oy + y

	if y < 0 || y >= len(v.lines) {
		return "", errors.New("invalid point")
	}
	return string(v.lines[y]), nil
}

// Word returns a string with the word of the view's internal buffer
// at the position corresponding to the point (x, y).
func (v *View) Word(x, y int) (string, error) {
	x = v.ox + x
	y = v.oy + y

	if y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
		return "", errors.New("invalid point")
	}
	l := string(v.lines[y])
	nl := strings.LastIndexFunc(l[:x], indexFunc)
	if nl == -1 {
		nl = 0
	} else {
		nl = nl + 1
	}
	nr := strings.IndexFunc(l[x:], indexFunc)
	if nr == -1 {
		nr = len(l)
	} else {
		nr = nr + x
	}
	return string(l[nl:nr]), nil
}

// indexFunc allows to split lines by words taking into account spaces
// and 0
func indexFunc(r rune) bool {
	return r == ' ' || r == 0
}
<commit_msg>Implement autoscroll. Fix scroll when View.Wrap is enabled<commit_after>// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gocui

import (
	"bytes"
	"errors"
	"io"
	"strings"

	"github.com/nsf/termbox-go"
)

// A View is a window. It maintains its own internal buffer and cursor
// position.
type View struct {
	name           string
	x0, y0, x1, y1 int
	ox, oy         int
	cx, cy         int
	lines          [][]rune
	overwrite      bool // overwrite in edit mode
	readOffset     int
	readCache      string

	// BgColor and FgColor allow to configure the background and foreground
	// colors of the View.
	BgColor, FgColor Attribute

	// SelBgColor and SelFgColor are used to configure the background and
	// foreground colors of the selected line, when it is highlighted.
	SelBgColor, SelFgColor Attribute

	// If Editable is true, keystrokes will be added to the view's internal
	// buffer at the cursor position.
	Editable bool

	// If Highlight is true, Sel{Bg,Fg}Colors will be used
	// for the line under the cursor position.
	Highlight bool

	// If Frame is true, a border will be drawn around the view.
	Frame bool

	// If Wrap is true, the content that is written to this View is
	// automatically wrapped when it is longer than its width. If true the
	// view's x-origin will be ignored.
	Wrap bool

	// If Wrap is true, each wrapping line is prefixed with this prefix.
	WrapPrefix string

	// If Autoscroll is true, the View will automatically scroll down when the
	// text overflows. If true the view's y-origin will be ignored.
	Autoscroll bool
}

// newView returns a new View object.
func newView(name string, x0, y0, x1, y1 int) *View {
	v := &View{
		name:  name,
		x0:    x0,
		y0:    y0,
		x1:    x1,
		y1:    y1,
		Frame: true,
	}
	return v
}

// Size returns the number of visible columns and rows in the View.
func (v *View) Size() (x, y int) {
	return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
}

// Name returns the name of the view.
func (v *View) Name() string {
	return v.name
}

// setRune writes a rune at the given point, relative to the view. It
// checks if the position is valid and applies the view's colors, taking
// into account if the cell must be highlighted.
func (v *View) setRune(x, y int, ch rune) error {
	maxX, maxY := v.Size()
	if x < 0 || x >= maxX || y < 0 || y >= maxY {
		return errors.New("invalid point")
	}

	var fgColor, bgColor Attribute
	if v.Highlight && y == v.cy {
		fgColor = v.SelFgColor
		bgColor = v.SelBgColor
	} else {
		fgColor = v.FgColor
		bgColor = v.BgColor
	}
	termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
		termbox.Attribute(fgColor), termbox.Attribute(bgColor))
	return nil
}

// SetCursor sets the cursor position of the view at the given point,
// relative to the view. It checks if the position is valid.
func (v *View) SetCursor(x, y int) error {
	maxX, maxY := v.Size()
	if x < 0 || x >= maxX || y < 0 || y >= maxY {
		return errors.New("invalid point")
	}
	v.cx = x
	v.cy = y
	return nil
}

// Cursor returns the cursor position of the view.
func (v *View) Cursor() (x, y int) {
	return v.cx, v.cy
}

// SetOrigin sets the origin position of the view's internal buffer,
// so the buffer starts to be printed from this point, which means that
// it is linked with the origin point of view. It can be used to
// implement Horizontal and Vertical scrolling with just incrementing
// or decrementing ox and oy.
func (v *View) SetOrigin(x, y int) error {
	if x < 0 || y < 0 {
		return errors.New("invalid point")
	}
	v.ox = x
	v.oy = y
	return nil
}

// Origin returns the origin position of the view.
func (v *View) Origin() (x, y int) {
	return v.ox, v.oy
}

// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (v *View) Write(p []byte) (n int, err error) {
	for _, ch := range bytes.Runes(p) {
		switch ch {
		case '\n':
			v.lines = append(v.lines, nil)
		case '\r':
			nl := len(v.lines)
			if nl > 0 {
				v.lines[nl-1] = nil
			} else {
				v.lines = make([][]rune, 1)
			}
		default:
			nl := len(v.lines)
			if nl > 0 {
				v.lines[nl-1] = append(v.lines[nl-1], ch)
			} else {
				v.lines = make([][]rune, 1)
				v.lines[0] = append(v.lines[0], ch)
			}
		}
	}
	return len(p), nil
}

// Read reads data into p. It returns the number of bytes read into p.
// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
// cache to be refreshed with the contents of the view.
func (v *View) Read(p []byte) (n int, err error) {
	if v.readOffset == 0 {
		v.readCache = v.Buffer()
	}
	if v.readOffset < len(v.readCache) {
		n = copy(p, v.readCache[v.readOffset:])
		v.readOffset += n
	} else {
		err = io.EOF
	}
	return
}

// Rewind sets the offset for the next Read to 0, which also refresh the
// read cache.
func (v *View) Rewind() {
	v.readOffset = 0
}

// draw re-draws the view's contents.
func (v *View) draw() error {
	maxX, maxY := v.Size()

	// This buffering takes care of v.ox
	if v.Wrap {
		if len(v.WrapPrefix) >= maxX {
			return errors.New("WrapPrefix bigger or equal to X size")
		}
		v.ox = 0
	}
	buf := make([][]rune, 0)
	for _, line := range v.lines {
		if v.Wrap {
			// Copy first line
			bufLine := make([]rune, maxX)
			// if v.ox >= len(line), then the line will be empty
			if v.ox < len(line) {
				copy(bufLine, line[v.ox:])
			}
			buf = append(buf, bufLine)
			// Append wrapped lines with WrapPrefix
			for n := maxX; n < len(line); n += maxX - len(v.WrapPrefix) {
				prefixLine := make([]rune, maxX)
				if v.ox < len(line) {
					copy(prefixLine, []rune(v.WrapPrefix))
					copy(prefixLine[len([]rune(v.WrapPrefix)):], line[v.ox+n:])
				}
				buf = append(buf, prefixLine)
			}
		} else {
			bufLine := make([]rune, maxX)
			if v.ox < len(line) {
				copy(bufLine, line[v.ox:])
			}
			buf = append(buf, bufLine)
		}
	}

	// The actual drawing takes into account v.oy
	if v.Autoscroll && len(buf) > maxY {
		v.oy = len(buf) - maxY
	}
	y := 0
	for i, line := range buf {
		if i < v.oy {
			continue
		}
		if y >= maxY {
			break
		}
		for x, ch := range line {
			if x >= maxX {
				break
			}
			if err := v.setRune(x, y, ch); err != nil {
				return err
			}
		}
		y++
	}
	return nil
}

// Clear empties the view's internal buffer.
func (v *View) Clear() {
	v.lines = nil
	v.clearRunes()
}

// clearRunes erases all the cells in the view.
func (v *View) clearRunes() {
	maxX, maxY := v.Size()
	for x := 0; x < maxX; x++ {
		for y := 0; y < maxY; y++ {
			termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
				termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
		}
	}
}

// writeRune writes a rune into the view's internal buffer, at the
// position corresponding to the point (x, y). The length of the internal
// buffer is increased if the point is out of bounds. Overwrite mode is
// governed by the value of View.overwrite.
func (v *View) writeRune(x, y int, ch rune) error {
	x = v.ox + x
	y = v.oy + y

	if x < 0 || y < 0 {
		return errors.New("invalid point")
	}

	if y >= len(v.lines) {
		if y >= cap(v.lines) {
			s := make([][]rune, y+1, (y+1)*2)
			copy(s, v.lines)
			v.lines = s
		} else {
			v.lines = v.lines[:y+1]
		}
	}
	if v.lines[y] == nil {
		v.lines[y] = make([]rune, x+1, (x+1)*2)
	} else if x >= len(v.lines[y]) {
		if x >= cap(v.lines[y]) {
			s := make([]rune, x+1, (x+1)*2)
			copy(s, v.lines[y])
			v.lines[y] = s
		} else {
			v.lines[y] = v.lines[y][:x+1]
		}
	}
	if !v.overwrite {
		v.lines[y] = append(v.lines[y], ' ')
		copy(v.lines[y][x+1:], v.lines[y][x:])
	}
	v.lines[y][x] = ch
	return nil
}

// deleteRune removes a rune from the view's internal buffer, at the
// position corresponding to the point (x, y).
func (v *View) deleteRune(x, y int) error {
	x = v.ox + x
	y = v.oy + y

	if x < 0 || y < 0 || y >= len(v.lines) || v.lines[y] == nil || x >= len(v.lines[y]) {
		return errors.New("invalid point")
	}
	copy(v.lines[y][x:], v.lines[y][x+1:])
	v.lines[y][len(v.lines[y])-1] = ' '
	return nil
}

// addLine adds a line into the view's internal buffer at the position
// corresponding to the point (x, y).
func (v *View) addLine(y int) error {
	y = v.oy + y

	if y < 0 || y >= len(v.lines) {
		return errors.New("invalid point")
	}
	v.lines = append(v.lines, nil)
	copy(v.lines[y+1:], v.lines[y:])
	v.lines[y] = nil
	return nil
}

// Buffer returns a string with the contents of the view's internal
// buffer
func (v *View) Buffer() string {
	str := ""
	for _, l := range v.lines {
		str += string(l) + "\n"
	}
	return strings.Replace(str, "\x00", " ", -1)
}

// Line returns a string with the line of the view's internal buffer
// at the position corresponding to the point (x, y).
func (v *View) Line(y int) (string, error) {
	y = v.oy + y

	if y < 0 || y >= len(v.lines) {
		return "", errors.New("invalid point")
	}
	return string(v.lines[y]), nil
}

// Word returns a string with the word of the view's internal buffer
// at the position corresponding to the point (x, y).
func (v *View) Word(x, y int) (string, error) {
	x = v.ox + x
	y = v.oy + y

	if y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
		return "", errors.New("invalid point")
	}
	l := string(v.lines[y])
	nl := strings.LastIndexFunc(l[:x], indexFunc)
	if nl == -1 {
		nl = 0
	} else {
		nl = nl + 1
	}
	nr := strings.IndexFunc(l[x:], indexFunc)
	if nr == -1 {
		nr = len(l)
	} else {
		nr = nr + x
	}
	return string(l[nl:nr]), nil
}

// indexFunc allows to split lines by words taking into account spaces
// and 0
func indexFunc(r rune) bool {
	return r == ' ' || r == 0
}
<|endoftext|>