<commit_before>package main

import (
	"io"
	"log"
	"net"
	"os"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gholt/ring"
)

type msgMap struct {
	lock    sync.RWMutex
	mapping map[ring.MsgType]ring.MsgUnmarshaller
}

func newMsgMap() *msgMap {
	return &msgMap{mapping: make(map[ring.MsgType]ring.MsgUnmarshaller)}
}

func (mm *msgMap) set(t ring.MsgType, f ring.MsgUnmarshaller) ring.MsgUnmarshaller {
	mm.lock.Lock()
	p := mm.mapping[t]
	mm.mapping[t] = f
	mm.lock.Unlock()
	return p
}

func (mm *msgMap) get(t ring.MsgType) ring.MsgUnmarshaller {
	mm.lock.RLock()
	f := mm.mapping[t]
	mm.lock.RUnlock()
	return f
}

type ringPipe struct {
	localNodeID     uint64
	conn            net.Conn
	lock            sync.RWMutex
	msgMap          *msgMap
	logError        *log.Logger
	logWarning      *log.Logger
	typeBytes       int
	lengthBytes     int
	writeChan       chan ring.Msg
	writingDoneChan chan struct{}
	sendDrops       uint32
}

func NewRingPipe(localNodeID uint64, c net.Conn) *ringPipe {
	rp := &ringPipe{
		localNodeID:     localNodeID,
		conn:            c,
		msgMap:          newMsgMap(),
		logError:        log.New(os.Stderr, "", log.LstdFlags),
		logWarning:      log.New(os.Stderr, "", log.LstdFlags),
		typeBytes:       1,
		lengthBytes:     3,
		writeChan:       make(chan ring.Msg, 40),
		writingDoneChan: make(chan struct{}, 1),
	}
	return rp
}

func (rp *ringPipe) Version() int64 {
	return 1
}

func (rp *ringPipe) ReplicaCount() int {
	return 2
}

func (rp *ringPipe) PartitionBitCount() uint16 {
	return 8
}

func (rp *ringPipe) LocalNodeID() uint64 {
	return rp.localNodeID
}

func (rp *ringPipe) Responsible(partition uint32) bool {
	// TODO: Testing push replication, so node 2 is responsible for everything
	// but we're putting everything into node 1.
	return rp.localNodeID == 2
}

func (rp *ringPipe) ResponsibleIDs(partition uint32) []uint64 {
	return []uint64{2, 2}
}

func (rp *ringPipe) Start() {
	go rp.reading()
	go rp.writing()
}

const _GLH_SEND_MSG_TIMEOUT = 1

func (rp *ringPipe) MaxMsgLength() uint64 {
	return 16 * 1024 * 1024
}

func (rp *ringPipe) SetMsgHandler(t ring.MsgType, h ring.MsgUnmarshaller) {
	rp.msgMap.set(t, h)
}

func (rp *ringPipe) MsgToNode(localNodeID uint64, m ring.Msg) bool {
	select {
	case rp.writeChan <- m:
		return true
	case <-time.After(_GLH_SEND_MSG_TIMEOUT * time.Second):
		atomic.AddUint32(&rp.sendDrops, 1)
		return false
	}
}

func (rp *ringPipe) MsgToOtherReplicas(ringVersion int64, partition uint32, m ring.Msg) bool {
	// TODO: If ringVersion has changed, partition invalid, etc. return false
	select {
	case rp.writeChan <- m:
		return true
	case <-time.After(_GLH_SEND_MSG_TIMEOUT * time.Second):
		atomic.AddUint32(&rp.sendDrops, 1)
		return false
	}
}

func (rp *ringPipe) reading() {
	b := make([]byte, rp.typeBytes+rp.lengthBytes)
	d := make([]byte, 65536)
	for {
		var n int
		var sn int
		var err error
		for n != len(b) {
			if err != nil {
				if n != 0 || err != io.EOF {
					rp.logError.Print("error reading msg", err)
				}
				return
			}
			sn, err = rp.conn.Read(b[n:])
			n += sn
		}
		if err != nil {
			rp.logError.Print("error reading msg start", err)
			return
		}
		var t uint64
		for i := 0; i < rp.typeBytes; i++ {
			t = (t << 8) | uint64(b[i])
		}
		var l uint64
		for i := 0; i < rp.lengthBytes; i++ {
			l = (l << 8) | uint64(b[rp.typeBytes+i])
		}
		f := rp.msgMap.get(ring.MsgType(t))
		if f != nil {
			_, err = f(rp.conn, l)
			if err != nil {
				rp.logError.Print("error reading msg content", err)
				return
			}
		} else {
			rp.logWarning.Printf("unknown msg type %d", t)
			for l > 0 {
				if err != nil {
					rp.logError.Print("err reading unknown msg content", err)
					return
				}
				if l >= uint64(len(d)) {
					sn, err = rp.conn.Read(d)
				} else {
					sn, err = rp.conn.Read(d[:l])
				}
				l -= uint64(sn)
			}
		}
	}
}

func (rp *ringPipe) writing() {
	b := make([]byte, rp.typeBytes+rp.lengthBytes)
	for {
		m := <-rp.writeChan
		if m == nil {
			break
		}
		t := m.MsgType()
		for i := rp.typeBytes - 1; i >= 0; i-- {
			b[i] = byte(t)
			t >>= 8
		}
		l := m.MsgLength()
		for i := rp.lengthBytes - 1; i >= 0; i-- {
			b[rp.typeBytes+i] = byte(l)
			l >>= 8
		}
		_, err := rp.conn.Write(b)
		if err != nil {
			rp.logError.Print("err writing msg", err)
			break
		}
		_, err = m.WriteContent(rp.conn)
		if err != nil {
			rp.logError.Print("err writing msg content", err)
			break
		}
		m.Done()
	}
	rp.writingDoneChan <- struct{}{}
}
<commit_msg>Better define MsgTo and Done<commit_after>package main

import (
	"io"
	"log"
	"net"
	"os"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gholt/ring"
)

type msgMap struct {
	lock    sync.RWMutex
	mapping map[ring.MsgType]ring.MsgUnmarshaller
}

func newMsgMap() *msgMap {
	return &msgMap{mapping: make(map[ring.MsgType]ring.MsgUnmarshaller)}
}

func (mm *msgMap) set(t ring.MsgType, f ring.MsgUnmarshaller) ring.MsgUnmarshaller {
	mm.lock.Lock()
	p := mm.mapping[t]
	mm.mapping[t] = f
	mm.lock.Unlock()
	return p
}

func (mm *msgMap) get(t ring.MsgType) ring.MsgUnmarshaller {
	mm.lock.RLock()
	f := mm.mapping[t]
	mm.lock.RUnlock()
	return f
}

type ringPipe struct {
	localNodeID     uint64
	conn            net.Conn
	lock            sync.RWMutex
	msgMap          *msgMap
	logError        *log.Logger
	logWarning      *log.Logger
	typeBytes       int
	lengthBytes     int
	writeChan       chan ring.Msg
	writingDoneChan chan struct{}
	sendDrops       uint32
}

func NewRingPipe(localNodeID uint64, c net.Conn) *ringPipe {
	rp := &ringPipe{
		localNodeID:     localNodeID,
		conn:            c,
		msgMap:          newMsgMap(),
		logError:        log.New(os.Stderr, "", log.LstdFlags),
		logWarning:      log.New(os.Stderr, "", log.LstdFlags),
		typeBytes:       1,
		lengthBytes:     3,
		writeChan:       make(chan ring.Msg, 40),
		writingDoneChan: make(chan struct{}, 1),
	}
	return rp
}

func (rp *ringPipe) Version() int64 {
	return 1
}

func (rp *ringPipe) ReplicaCount() int {
	return 2
}

func (rp *ringPipe) PartitionBitCount() uint16 {
	return 8
}

func (rp *ringPipe) LocalNodeID() uint64 {
	return rp.localNodeID
}

func (rp *ringPipe) Responsible(partition uint32) bool {
	// TODO: Testing push replication, so node 2 is responsible for everything
	// but we're putting everything into node 1.
	return rp.localNodeID == 2
}

func (rp *ringPipe) ResponsibleIDs(partition uint32) []uint64 {
	return []uint64{2, 2}
}

func (rp *ringPipe) Start() {
	go rp.reading()
	go rp.writing()
}

const _GLH_SEND_MSG_TIMEOUT = 1

func (rp *ringPipe) MaxMsgLength() uint64 {
	return 16 * 1024 * 1024
}

func (rp *ringPipe) SetMsgHandler(t ring.MsgType, h ring.MsgUnmarshaller) {
	rp.msgMap.set(t, h)
}

func (rp *ringPipe) MsgToNode(localNodeID uint64, m ring.Msg) {
	select {
	case rp.writeChan <- m:
	case <-time.After(_GLH_SEND_MSG_TIMEOUT * time.Second):
		atomic.AddUint32(&rp.sendDrops, 1)
	}
	m.Done()
}

func (rp *ringPipe) MsgToOtherReplicas(ringVersion int64, partition uint32, m ring.Msg) {
	// TODO: If ringVersion has changed, partition invalid, etc. return false
	select {
	case rp.writeChan <- m:
	case <-time.After(_GLH_SEND_MSG_TIMEOUT * time.Second):
		atomic.AddUint32(&rp.sendDrops, 1)
	}
	m.Done()
}

func (rp *ringPipe) reading() {
	b := make([]byte, rp.typeBytes+rp.lengthBytes)
	d := make([]byte, 65536)
	for {
		var n int
		var sn int
		var err error
		for n != len(b) {
			if err != nil {
				if n != 0 || err != io.EOF {
					rp.logError.Print("error reading msg", err)
				}
				return
			}
			sn, err = rp.conn.Read(b[n:])
			n += sn
		}
		if err != nil {
			rp.logError.Print("error reading msg start", err)
			return
		}
		var t uint64
		for i := 0; i < rp.typeBytes; i++ {
			t = (t << 8) | uint64(b[i])
		}
		var l uint64
		for i := 0; i < rp.lengthBytes; i++ {
			l = (l << 8) | uint64(b[rp.typeBytes+i])
		}
		f := rp.msgMap.get(ring.MsgType(t))
		if f != nil {
			_, err = f(rp.conn, l)
			if err != nil {
				rp.logError.Print("error reading msg content", err)
				return
			}
		} else {
			rp.logWarning.Printf("unknown msg type %d", t)
			for l > 0 {
				if err != nil {
					rp.logError.Print("err reading unknown msg content", err)
					return
				}
				if l >= uint64(len(d)) {
					sn, err = rp.conn.Read(d)
				} else {
					sn, err = rp.conn.Read(d[:l])
				}
				l -= uint64(sn)
			}
		}
	}
}

func (rp *ringPipe) writing() {
	b := make([]byte, rp.typeBytes+rp.lengthBytes)
	for {
		m := <-rp.writeChan
		if m == nil {
			break
		}
		t := m.MsgType()
		for i := rp.typeBytes - 1; i >= 0; i-- {
			b[i] = byte(t)
			t >>= 8
		}
		l := m.MsgLength()
		for i := rp.lengthBytes - 1; i >= 0; i-- {
			b[rp.typeBytes+i] = byte(l)
			l >>= 8
		}
		_, err := rp.conn.Write(b)
		if err != nil {
			rp.logError.Print("err writing msg", err)
			break
		}
		_, err = m.WriteContent(rp.conn)
		if err != nil {
			rp.logError.Print("err writing msg content", err)
			break
		}
	}
	rp.writingDoneChan <- struct{}{}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"code.google.com/p/go-uuid/uuid"
	"github.com/codegangsta/negroni"
	"github.com/julienschmidt/httprouter"
	"github.com/unrolled/render"

	"github.com/wantedly/risu/registry"
	"github.com/wantedly/risu/schema"
)

var ren = render.New()

func create(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	defer r.Body.Close()
	var opts schema.BuildCreateOpts
	err := json.NewDecoder(r.Body).Decode(&opts)
	if err != nil {
		log.Fatal(err)
	}

	if opts.Dockerfile == "" {
		opts.Dockerfile = "Dockerfile"
	}

	build := schema.Build{
		ID:             uuid.NewUUID(),
		SourceRepo:     opts.SourceRepo,
		SourceRevision: opts.SourceRevision,
		Name:           opts.Name,
		Dockerfile:     opts.Dockerfile,
		Status:         "building",
		CreatedAt:      time.Now(),
		UpdatedAt:      time.Now(),
	}

	reg := registry.NewRegistry("localfs", "")
	reg.Set(build)

	// debug code
	builddata, err := reg.Get(build.ID)
	fmt.Fprintln(w, builddata)
}

func root(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	ren.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	reg := registry.NewRegistry("localfs", "")
	builds, err := reg.List()
	if err != nil {
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
	}

	ren.JSON(w, http.StatusOK, builds)
}

func show(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	uuid := uuid.Parse(id)
	reg := registry.NewRegistry("localfs", "")
	build, err := reg.Get(uuid)
	if err != nil {
		ren.JSON(w, http.StatusNotFound, map[string]string{"status": "not found"})
	}
	ren.JSON(w, http.StatusOK, build)
}

func main() {
	router := httprouter.New()
	router.GET("/", root)
	router.GET("/builds", index)
	router.GET("/builds/:id", show)
	router.POST("/builds", create)

	n := negroni.Classic()
	n.UseHandler(router)
	n.Run(":8080")
}
<commit_msg>Enable to switch registry backend<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"code.google.com/p/go-uuid/uuid"
	"github.com/codegangsta/negroni"
	"github.com/julienschmidt/httprouter"
	"github.com/unrolled/render"

	"github.com/wantedly/risu/registry"
	"github.com/wantedly/risu/schema"
)

var ren = render.New()
var reg = registry.NewRegistry(os.Getenv("REGISTRY_BACKEND"), os.Getenv("REGISTRY_ENDPOINT"))

func create(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	defer r.Body.Close()
	var opts schema.BuildCreateOpts
	err := json.NewDecoder(r.Body).Decode(&opts)
	if err != nil {
		log.Fatal(err)
	}

	if opts.Dockerfile == "" {
		opts.Dockerfile = "Dockerfile"
	}

	build := schema.Build{
		ID:             uuid.NewUUID(),
		SourceRepo:     opts.SourceRepo,
		SourceRevision: opts.SourceRevision,
		Name:           opts.Name,
		Dockerfile:     opts.Dockerfile,
		Status:         "building",
		CreatedAt:      time.Now(),
		UpdatedAt:      time.Now(),
	}

	reg.Set(build)

	// debug code
	builddata, err := reg.Get(build.ID)
	fmt.Fprintln(w, builddata)
}

func root(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	ren.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	builds, err := reg.List()
	if err != nil {
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
	}

	ren.JSON(w, http.StatusOK, builds)
}

func show(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	uuid := uuid.Parse(id)
	build, err := reg.Get(uuid)
	if err != nil {
		ren.JSON(w, http.StatusNotFound, map[string]string{"status": "not found"})
	}
	ren.JSON(w, http.StatusOK, build)
}

func main() {
	router := httprouter.New()
	router.GET("/", root)
	router.GET("/builds", index)
	router.GET("/builds/:id", show)
	router.POST("/builds", create)

	n := negroni.Classic()
	n.UseHandler(router)
	n.Run(":8080")
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Pani Networks
// 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 rlog

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"runtime"
	"strconv"
	"strings"
)

// The known log levels
const (
	levelTrace = iota
	levelDebug
	levelInfo
	levelWarn
	levelErr
	levelCrit
	levelNone
)

// Translation map from level to string representation
var levelStrings = map[int]string{
	levelTrace: "TRACE",
	levelDebug: "DEBUG",
	levelInfo:  "INFO",
	levelWarn:  "WARN",
	levelErr:   "ERROR",
	levelCrit:  "CRITICAL",
	levelNone:  "NONE",
}

// Translation from level string to number.
var levelNumbers = map[string]int{
	"TRACE":    levelTrace,
	"DEBUG":    levelDebug,
	"INFO":     levelInfo,
	"WARN":     levelWarn,
	"ERROR":    levelErr,
	"CRITICAL": levelCrit,
	"NONE":     levelNone,
}

// Rlog is controlled via environment variables. Those things won't change on
// us. Therefore, we can look them up once and store them in module level
// global variables.
var settingTraceLevel int = -1        // -1 indicates 'not set' -> no tracing
var settingLogLevel int = levelInfo   // by default we log INFO or higher
var settingGetCallerInfo bool = false // whether we log info about calling function
var settingLogTime bool = true        // whether date/time should be logged
var settingDateTimeFlags int          // flags for date/time output
var settingLogFile string = ""        // logfile name
var logWriter *log.Logger

// init extracts settings for our logger from environment variables when the
// module is imported.
func init() {
	var err error

	logLevelEnv := strings.ToUpper(os.Getenv("RLOG_LOG_LEVEL"))
	callerInfoEnv := os.Getenv("RLOG_CALLER_INFO")
	traceLevelEnv := os.Getenv("RLOG_TRACE_LEVEL")
	dontLogTimeEnv := os.Getenv("RLOG_LOG_NOTIME")
	logFileEnv := os.Getenv("RLOG_LOG_FILE")

	// Evaluating the desired log level
	levelVal, ok := levelNumbers[logLevelEnv]
	if ok {
		if levelVal != levelTrace {
			// User can't set things to 'Trace', so we would leave it at
			// the default, which is 'Info'. But other than that the user
			// has specified a valid level value, so we can set this now.
			settingLogLevel = levelVal
		}
	}

	// Evaluating the caller info variable.
	settingGetCallerInfo = isTrueBoolString(callerInfoEnv)

	// Evaluating whether date/time should be logged with each message
	settingLogTime = !isTrueBoolString(dontLogTimeEnv)

	// Evaluating the trace level variable
	if traceLevelEnv != "" {
		var traceLevel int
		if traceLevel, err = strconv.Atoi(traceLevelEnv); err == nil {
			if traceLevel >= -1 {
				settingTraceLevel = traceLevel
			}
		}
	}

	// By default we log to stderr...
	logWriter := os.Stderr

	// ... but if requested we'll create and/or append to a logfile instead
	if logFileEnv != "" {
		newLogFile, err := os.OpenFile(logFileEnv, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err == nil {
			logWriter = newLogFile
		}
	}

	// Initialize the logger we will use throughout
	settingDateTimeFlags = 0
	if settingLogTime {
		// Store the flags that enable date/time logging, since that's
		// controlled by an environment variable. Any new loggers created at
		// runtime should inherit the same setting (the same flags).
		settingDateTimeFlags = log.Ldate | log.Ltime
	}
	SetNewLogWriter(logWriter)
}

// SetNewLogWriter re-wires the log output to a new io.Writer. By default rlog
// logs to os.Stderr, but this function can be used to direct the output
// somewhere else.
func SetNewLogWriter(writer io.Writer) {
	// Use the stored date/time flag settings
	logWriter = log.New(writer, "", settingDateTimeFlags)
}

// isTrueBoolString tests a string to see if it represents a 'true' value.
// The ParseBool function unfortunately doesn't recognize 'y' or 'yes', which
// is why we added that test here as well.
func isTrueBoolString(str string) bool {
	str = strings.ToUpper(str)
	if str == "Y" || str == "YES" {
		return true
	}
	if isTrue, err := strconv.ParseBool(str); err == nil && isTrue {
		return true
	}
	return false
}

// basicLog is called by all the 'level 'functions.
// It checks what is configured to be included in the log message,
// decorates it accordingly and assembles the entire line. It then
// uses the standard log package to finally output the message.
func basicLog(logLevel int, format string, prefixAddition string, a ...interface{}) {
	// Should we even be logging this?
	// Note that Trace is a special case. We will never get a message with
	// logTrace unless tracing was specifically enabled.
	if logLevel < settingLogLevel && logLevel != levelTrace {
		return
	}

	// Extract information about the caller of the log function, if requested.
	callerInfo := ""
	if settingGetCallerInfo {
		if pc, fullFilePath, line, ok := runtime.Caller(2); ok {
			callingFuncName := runtime.FuncForPC(pc).Name()
			// We only want to print file and package name, so use the last two
			// elements of the full path. The path package deals with different
			// path formats on different systems, so we use that instead of
			// just string-split.
			dirPath, fileName := path.Split(fullFilePath)
			var moduleName string
			if dirPath != "" {
				dirPath = dirPath[:len(dirPath)-1]
				dirPath, moduleName = path.Split(dirPath)
			} else {
				moduleName = ""
			}

			callerInfo = fmt.Sprintf("[%s/%s:%d (%s)] ", moduleName, fileName,
				line, callingFuncName)
		}
	}

	// Assemble the actual log line
	var msg string
	if format != "" {
		msg = fmt.Sprintf(format, a...)
	} else {
		msg = fmt.Sprintln(a...)
	}
	levelDecoration := levelStrings[logLevel] + prefixAddition
	logWriter.Printf("%-9s: %s%s", levelDecoration, callerInfo, msg)
}

// Trace is for low level tracing of activities. It takes an additional 'level'
// parameter. The RLOG_TRACE_LEVEL variable is used to determine which levels
// of trace message are output: Every message with a level lower or equal to
// what is specified in RLOG_TRACE_LEVEL. If RLOG_TRACE_LEVEL is not defined at
// all then no trace messages are printed.
func Trace(traceLevel int, a ...interface{}) {
	if traceLevel <= settingTraceLevel && traceLevel >= 0 {
		prefixAddition := fmt.Sprintf("(%d)", traceLevel)
		basicLog(levelTrace, "", prefixAddition, a...)
	}
}

// Tracef prints trace messages, with formatting.
func Tracef(traceLevel int, format string, a ...interface{}) {
	if traceLevel <= settingTraceLevel && traceLevel >= 0 {
		prefixAddition := fmt.Sprintf("(%d)", traceLevel)
		basicLog(levelTrace, format, prefixAddition, a...)
	}
}

// Debug prints a message if RLOG_LEVEL is set to DEBUG.
func Debug(a ...interface{}) {
	basicLog(levelDebug, "", "", a...)
}

// Debugf prints a message if RLOG_LEVEL is set to DEBUG, with formatting.
func Debugf(format string, a ...interface{}) {
	basicLog(levelDebug, format, "", a...)
}

// Info prints a message if RLOG_LEVEL is set to INFO or lower.
func Info(a ...interface{}) {
	basicLog(levelInfo, "", "", a...)
}

// Infof prints a message if RLOG_LEVEL is set to INFO or lower, with
// formatting.
func Infof(format string, a ...interface{}) {
	basicLog(levelInfo, format, "", a...)
}

// Warn prints a message if RLOG_LEVEL is set to WARN or lower.
func Warn(a ...interface{}) {
	basicLog(levelWarn, "", "", a...)
}

// Warnf prints a message if RLOG_LEVEL is set to WARN or lower, with
// formatting.
func Warnf(format string, a ...interface{}) {
	basicLog(levelWarn, format, "", a...)
}

// Error prints a message if RLOG_LEVEL is set to ERROR or lower.
func Error(a ...interface{}) {
	basicLog(levelErr, "", "", a...)
}

// Errorf prints a message if RLOG_LEVEL is set to ERROR or lower, with
// formatting.
func Errorf(format string, a ...interface{}) {
	basicLog(levelErr, format, "", a...)
}

// Critical prints a message if RLOG_LEVEL is set to CRITICAL or lower.
func Critical(a ...interface{}) {
	basicLog(levelCrit, "", "", a...)
}

// Criticalf prints a message if RLOG_LEVEL is set to CRITICAL or lower, with
// formatting.
func Criticalf(format string, a ...interface{}) {
	basicLog(levelCrit, format, "", a...)
}
<commit_msg>Implement RLOG_CALLER_FILTER feature to allow fine grained tuning of trace levels<commit_after>// Copyright (c) 2016 Pani Networks
// 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 rlog

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

// The known log levels
const (
	levelTrace = iota
	levelDebug
	levelInfo
	levelWarn
	levelErr
	levelCrit
	levelNone
)

// Translation map from level to string representation
var levelStrings = map[int]string{
	levelTrace: "TRACE",
	levelDebug: "DEBUG",
	levelInfo:  "INFO",
	levelWarn:  "WARN",
	levelErr:   "ERROR",
	levelCrit:  "CRITICAL",
	levelNone:  "NONE",
}

// Translation from level string to number.
var levelNumbers = map[string]int{
	"TRACE":    levelTrace,
	"DEBUG":    levelDebug,
	"INFO":     levelInfo,
	"WARN":     levelWarn,
	"ERROR":    levelErr,
	"CRITICAL": levelCrit,
	"NONE":     levelNone,
}

// FilterSpec holds a list of filters.
type FilterSpec struct {
	Filters []Filter
}

// FromString initializes FilterSpec from string.
// format "<filter>,<filter>,[<filter>]..."
//     filter:
//       <pattern=Level>
//     pattern:
//       shell glob to match caller filename
//     Level:
//       trace level of the logs to enable in mathed files
//
//     example:
//        "client.go=1,ip*=5"
func (spec *FilterSpec) FromString(s string) {
	fields := strings.Split(s, ",")

	for _, f := range fields {
		// tokens should contain two elements: The filename and the trace level.
		tokens := strings.Split(f, "=")
		if len(tokens) != 2 {
			return
		}

		if filterLevel, err := strconv.Atoi(tokens[1]); err == nil {
			spec.Filters = append(spec.Filters, Filter{tokens[0], filterLevel})
		} else {
			return
		}
	}

	return
}

// matchFilters checks if given filename and trace level are accepted
// by any of the filters
func (spec *FilterSpec) matchFilters(filename string, level int) bool {

	// If no filters don't match anything.
	if len(spec.Filters) == 0 {
		return false
	}

	// If at least one filter matches.
	for _, filter := range spec.Filters {
		if filter.match(filename, level) {
			return true
		}
	}

	return false
}

// Filter holds filename and trace level to match logs.
type Filter struct {
	Pattern string
	Level   int
}

// match checks if given filename and trace level are matched by
// this filter.
func (f Filter) match(filename string, traceLevel int) bool {
	match, _ := filepath.Match(f.Pattern, filepath.Base(filename))
	if match && f.Level >= traceLevel {
		return true
	}

	return false
}

// Rlog is controlled via environment variables. Those things won't change on
// us. Therefore, we can look them up once and store them in module level
// global variables.
var settingTraceLevel int = -1        // -1 indicates 'not set' -> no tracing
var settingLogLevel int = levelInfo   // by default we log INFO or higher
var settingGetCallerInfo bool = false // whether we log info about calling function
var settingLogTime bool = true        // whether date/time should be logged
var settingDateTimeFlags int          // flags for date/time output
var settingLogFile string = ""        // logfile name
var logWriter *log.Logger
var settingCallerFilter string = ""
var filterSpec = new(FilterSpec)

// init extracts settings for our logger from environment variables when the
// module is imported.
func init() {
	var err error

	logLevelEnv := strings.ToUpper(os.Getenv("RLOG_LOG_LEVEL"))
	callerInfoEnv := os.Getenv("RLOG_CALLER_INFO")
	traceLevelEnv := os.Getenv("RLOG_TRACE_LEVEL")
	dontLogTimeEnv := os.Getenv("RLOG_LOG_NOTIME")
	logFileEnv := os.Getenv("RLOG_LOG_FILE")
	settingCallerFilter := os.Getenv("RLOG_CALLER_FILTER")

	// Evaluating the desired log level
	levelVal, ok := levelNumbers[logLevelEnv]
	if ok {
		if levelVal != levelTrace {
			// User can't set things to 'Trace', so we would leave it at
			// the default, which is 'Info'. But other than that the user
			// has specified a valid level value, so we can set this now.
			settingLogLevel = levelVal
		}
	}

	// Evaluating the caller info variable.
	settingGetCallerInfo = isTrueBoolString(callerInfoEnv)

	// Evaluating whether date/time should be logged with each message
	settingLogTime = !isTrueBoolString(dontLogTimeEnv)

	// Initialize filters.
	filterSpec.FromString(settingCallerFilter)

	// Evaluating the trace level variable
	if traceLevelEnv != "" {
		var traceLevel int
		if traceLevel, err = strconv.Atoi(traceLevelEnv); err == nil {
			if traceLevel >= -1 {
				settingTraceLevel = traceLevel
			}
		}
	}

	// By default we log to stderr...
	logWriter := os.Stderr

	// ... but if requested we'll create and/or append to a logfile instead
	if logFileEnv != "" {
		newLogFile, err := os.OpenFile(logFileEnv, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err == nil {
			logWriter = newLogFile
		}
	}

	// Initialize the logger we will use throughout
	settingDateTimeFlags = 0
	if settingLogTime {
		// Store the flags that enable date/time logging, since that's
		// controlled by an environment variable. Any new loggers created at
		// runtime should inherit the same setting (the same flags).
		settingDateTimeFlags = log.Ldate | log.Ltime
	}
	SetNewLogWriter(logWriter)
}

// SetNewLogWriter re-wires the log output to a new io.Writer. By default rlog
// logs to os.Stderr, but this function can be used to direct the output
// somewhere else.
func SetNewLogWriter(writer io.Writer) {
	// Use the stored date/time flag settings
	logWriter = log.New(writer, "", settingDateTimeFlags)
}

// isTrueBoolString tests a string to see if it represents a 'true' value.
// The ParseBool function unfortunately doesn't recognize 'y' or 'yes', which
// is why we added that test here as well.
func isTrueBoolString(str string) bool {
	str = strings.ToUpper(str)
	if str == "Y" || str == "YES" {
		return true
	}
	if isTrue, err := strconv.ParseBool(str); err == nil && isTrue {
		return true
	}
	return false
}

// basicLog is called by all the 'level 'functions.
// It checks what is configured to be included in the log message,
// decorates it accordingly and assembles the entire line. It then
// uses the standard log package to finally output the message.
func basicLog(logLevel int, logTraceLevel int, format string, prefixAddition string, a ...interface{}) {

	// Extract information about the caller of the log function, if requested.
	var callingFuncName string
	/*
		pc, filename, line, callerInfoAvailable := runtime.Caller(2)
		if callerInfoAvailable {
			callingFuncName = runtime.FuncForPC(pc).Name()
		}
	*/

	// Perform tests to see if we should log this message.
	var allowLog bool
	if logLevel != levelTrace {
		// If log is not a trace log then check log level.
		if logLevel >= settingLogLevel {
			allowLog = true
		}
	} else {
		// Trace logs are allowed if either global tracel level matches
		// or one of filters matches.
		if logTraceLevel <= settingTraceLevel && logTraceLevel >= 0 {
			allowLog = true
		}

		if filterSpec.matchFilters(filename, logTraceLevel) {
			allowLog = true
		}
	}
	if !allowLog {
		return
	}

	callerInfo := ""
	if settingGetCallerInfo {
		if pc, fullFilePath, line, ok := runtime.Caller(2); ok {
			callingFuncName := runtime.FuncForPC(pc).Name()
			// We only want to print file and package name, so use the last two
			// elements of the full path. The path package deals with different
			// path formats on different systems, so we use that instead of
			// just string-split.
			dirPath, fileName := path.Split(fullFilePath)
			var moduleName string
			if dirPath != "" {
				dirPath = dirPath[:len(dirPath)-1]
				dirPath, moduleName = path.Split(dirPath)
			} else {
				moduleName = ""
			}

			callerInfo = fmt.Sprintf("[%s/%s:%d (%s)] ", moduleName, fileName,
				line, callingFuncName)
		}
		/*
			if settingGetCallerInfo && callerInfoAvailable {
				callerInfo = fmt.Sprintf("[%s:%d (%s)] ", filename, line, callingFuncName)
		*/
	}

	// Assemble the actual log line
	var msg string
	if format != "" {
		msg = fmt.Sprintf(format, a...)
	} else {
		msg = fmt.Sprintln(a...)
	}
	levelDecoration := levelStrings[logLevel] + prefixAddition
	logWriter.Printf("%-9s: %s%s", levelDecoration, callerInfo, msg)
}

// Trace is for low level tracing of activities. It takes an additional 'level'
// parameter. The RLOG_TRACE_LEVEL variable is used to determine which levels
// of trace message are output: Every message with a level lower or equal to
// what is specified in RLOG_TRACE_LEVEL. If RLOG_TRACE_LEVEL is not defined at
// all then no trace messages are printed.
func Trace(traceLevel int, a ...interface{}) {
	prefixAddition := fmt.Sprintf("(%d)", traceLevel)
	basicLog(levelTrace, traceLevel, "", prefixAddition, a...)
}

// Tracef prints trace messages, with formatting.
func Tracef(traceLevel int, format string, a ...interface{}) {
	prefixAddition := fmt.Sprintf("(%d)", traceLevel)
	basicLog(levelTrace, traceLevel, format, prefixAddition, a...)
}

const NotATrace = -1

// Debug prints a message if RLOG_LEVEL is set to DEBUG.
func Debug(a ...interface{}) {
	basicLog(levelDebug, NotATrace, "", "", a...)
}

// Debugf prints a message if RLOG_LEVEL is set to DEBUG, with formatting.
func Debugf(format string, a ...interface{}) {
	basicLog(levelDebug, NotATrace, format, "", a...)
}

// Info prints a message if RLOG_LEVEL is set to INFO or lower.
func Info(a ...interface{}) {
	basicLog(levelInfo, NotATrace, "", "", a...)
}

// Infof prints a message if RLOG_LEVEL is set to INFO or lower, with
// formatting.
func Infof(format string, a ...interface{}) {
	basicLog(levelInfo, NotATrace, format, "", a...)
}

// Warn prints a message if RLOG_LEVEL is set to WARN or lower.
func Warn(a ...interface{}) {
	basicLog(levelWarn, NotATrace, "", "", a...)
}

// Warnf prints a message if RLOG_LEVEL is set to WARN or lower, with
// formatting.
func Warnf(format string, a ...interface{}) {
	basicLog(levelWarn, NotATrace, format, "", a...)
}

// Error prints a message if RLOG_LEVEL is set to ERROR or lower.
func Error(a ...interface{}) {
	basicLog(levelErr, NotATrace, "", "", a...)
}

// Errorf prints a message if RLOG_LEVEL is set to ERROR or lower, with
// formatting.
func Errorf(format string, a ...interface{}) {
	basicLog(levelErr, NotATrace, format, "", a...)
}

// Critical prints a message if RLOG_LEVEL is set to CRITICAL or lower.
func Critical(a ...interface{}) {
	basicLog(levelCrit, NotATrace, "", "", a...)
}

// Criticalf prints a message if RLOG_LEVEL is set to CRITICAL or lower, with
// formatting.
func Criticalf(format string, a ...interface{}) {
	basicLog(levelCrit, NotATrace, format, "", a...)
}
<|endoftext|>


<commit_before>// +build performance

/*
Copyright 2019 The Knative 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

    https://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 performance

import (
	"fmt"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/knative/test-infra/shared/junit"
	perf "github.com/knative/test-infra/shared/performance"
	"github.com/knative/test-infra/shared/testgrid"
	pkgTest "knative.dev/pkg/test"
	"knative.dev/pkg/test/ingress"
	"knative.dev/serving/test"
	v1a1test "knative.dev/serving/test/v1alpha1"

	vegeta "github.com/tsenart/vegeta/lib"
)

const (
	reqTimeout = 30 * time.Second
	app        = "helloworld"
)

var loads = [...]int{100, 1000}

func filename(name string) string {
	// Replace characters in `name` with characters for a file name.
	return strings.ReplaceAll(name, "/", "-")
}

func runTest(t *testing.T, pacer vegeta.Pacer, saveMetrics bool) {
	t.Helper()
	tName := t.Name()
	perfClients, err := Setup(t)
	if err != nil {
		t.Fatalf("Cannot initialize performance client: %v", err)
	}

	clients := perfClients.E2EClients
	names := test.ResourceNames{
		Service: test.ObjectNameForTest(t),
		Image:   app,
	}

	defer TearDown(perfClients, names, t.Logf)
	test.CleanupOnInterrupt(func() { TearDown(perfClients, names, t.Logf) })

	t.Log("Creating a new Service")
	objs, err := v1a1test.CreateRunLatestServiceReady(t, clients, &names)
	if err != nil {
		t.Fatalf("Failed to create Service: %v", err)
	}

	domain := objs.Route.Status.URL.Host
	endpoint, err := ingress.GetIngressEndpoint(clients.KubeClient.Kube)
	if err != nil {
		t.Fatalf("Cannot get service endpoint: %v", err)
	}

	if _, err := pkgTest.WaitForEndpointState(
		clients.KubeClient,
		t.Logf,
		domain,
		v1a1test.RetryingRouteInconsistency(pkgTest.IsStatusOK),
		"WaitForSuccessfulResponse",
		test.ServingFlags.ResolvableDomain); err != nil {
		t.Fatalf("Error probing domain %s: %v", domain, err)
	}

	targeter := vegeta.NewStaticTargeter(vegeta.Target{
		Method: http.MethodGet,
		URL:    fmt.Sprintf("http://%s/", *endpoint),
	})
	attacker := vegeta.NewAttacker()

	var metrics vegeta.Metrics
	for res := range attacker.Attack(targeter, pacer, duration, tName) {
		metrics.Add(res)
	}
	metrics.Close()

	// Return directly if we do not want to save metrics for this test run.
	if !saveMetrics {
		return
	}

	var tc []junit.TestCase
	// Add latency metrics
	tc = append(tc, perf.CreatePerfTestCase(float32(metrics.Latencies.P50.Seconds()*1000), "p50(ms)", tName))
	tc = append(tc, perf.CreatePerfTestCase(float32(metrics.Latencies.Quantile(0.90).Seconds()*1000), "p90(ms)", tName))
	tc = append(tc, perf.CreatePerfTestCase(float32(metrics.Latencies.P99.Seconds()*1000), "p99(ms)", tName))

	// Add errorsPercentage metrics
	tc = append(tc, perf.CreatePerfTestCase(float32(1-metrics.Success), "errorsPercentage", tName))

	if err = testgrid.CreateXMLOutput(tc, filename(tName)); err != nil {
		t.Fatalf("Cannot create output xml: %v", err)
	}
}

// TestBenchmarkSteadyTraffic generates steady traffic at different volumes.
func TestBenchmarkSteadyTraffic(t *testing.T) {
	for _, load := range loads {
		t.Run(fmt.Sprintf("N%d", load), func(t *testing.T) {
			zeroToNSteadyPacer := NewSteadyUpPacer(
				vegeta.Rate{Freq: 1, Per: time.Second},
				vegeta.Rate{Freq: load, Per: time.Second},
				duration/2,
			)
			runTest(t, zeroToNSteadyPacer, true)
		})
	}
}

// TestBenchmarkBurstZeroToN generates a burst from 0 to N concurrent requests, for different values of N.
func TestBenchmarkBurstZeroToN(t *testing.T) {
	for _, load := range loads {
		t.Run(fmt.Sprintf("N%d", load), func(t *testing.T) {
			zeroToNBurstPacer := vegeta.ConstantPacer{Freq: load, Per: time.Second}
			runTest(t, zeroToNBurstPacer, true)
		})
	}
}

// TestBenchmarkBurstNto2N generates a burst from N to 2N concurrent requests, for different values of N.
func TestBenchmarkBurstNto2N(t *testing.T) {
	for _, load := range loads {
		t.Run(fmt.Sprintf("N%d", load), func(t *testing.T) {
			// Steady ramp up from 0 to N, then burst to 2N.
			zeroToNSteadyPacer := NewSteadyUpPacer(
				vegeta.Rate{Freq: 1, Per: time.Second},
				vegeta.Rate{Freq: load, Per: time.Second},
				duration,
			)
			// Scale up to the desired load and discard the metrics.
			runTest(t, zeroToNSteadyPacer, false)

			nTo2NBurstPacer := vegeta.ConstantPacer{Freq: 2 * load, Per: time.Second}
			runTest(t, nTo2NBurstPacer, true)
		})
	}
}
<commit_msg>forgot to add host header (#4818)<commit_after>// +build performance

/*
Copyright 2019 The Knative 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

    https://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 performance

import (
	"fmt"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/knative/test-infra/shared/junit"
	perf "github.com/knative/test-infra/shared/performance"
	"github.com/knative/test-infra/shared/testgrid"
	pkgTest "knative.dev/pkg/test"
	"knative.dev/pkg/test/ingress"
	"knative.dev/serving/test"
	v1a1test "knative.dev/serving/test/v1alpha1"

	vegeta "github.com/tsenart/vegeta/lib"
)

const (
	reqTimeout = 30 * time.Second
	app        = "helloworld"
)

var loads = [...]int{100, 1000}

func filename(name string) string {
	// Replace characters in `name` with characters for a file name.
	return strings.ReplaceAll(name, "/", "-")
}

func runTest(t *testing.T, pacer vegeta.Pacer, saveMetrics bool) {
	t.Helper()
	tName := t.Name()
	perfClients, err := Setup(t)
	if err != nil {
		t.Fatalf("Cannot initialize performance client: %v", err)
	}

	clients := perfClients.E2EClients
	names := test.ResourceNames{
		Service: test.ObjectNameForTest(t),
		Image:   app,
	}

	defer TearDown(perfClients, names, t.Logf)
	test.CleanupOnInterrupt(func() { TearDown(perfClients, names, t.Logf) })

	t.Log("Creating a new Service")
	objs, err := v1a1test.CreateRunLatestServiceReady(t, clients, &names)
	if err != nil {
		t.Fatalf("Failed to create Service: %v", err)
	}

	domain := objs.Route.Status.URL.Host
	endpoint, err := ingress.GetIngressEndpoint(clients.KubeClient.Kube)
	if err != nil {
		t.Fatalf("Cannot get service endpoint: %v", err)
	}

	if _, err := pkgTest.WaitForEndpointState(
		clients.KubeClient,
		t.Logf,
		domain,
		v1a1test.RetryingRouteInconsistency(pkgTest.IsStatusOK),
		"WaitForSuccessfulResponse",
		test.ServingFlags.ResolvableDomain); err != nil {
		t.Fatalf("Error probing domain %s: %v", domain, err)
	}

	targeter := vegeta.NewStaticTargeter(vegeta.Target{
		Method: http.MethodGet,
		Header: map[string][]string{"Host": []string{domain}},
		URL:    fmt.Sprintf("http://%s", *endpoint),
	})
	attacker := vegeta.NewAttacker()

	var metrics vegeta.Metrics
	for res := range attacker.Attack(targeter, pacer, duration, tName) {
		metrics.Add(res)
	}
	metrics.Close()

	// Return directly if we do not want to save metrics for this test run.
	if !saveMetrics {
		return
	}

	var tc []junit.TestCase
	// Add latency metrics
	tc = append(tc, perf.CreatePerfTestCase(float32(metrics.Latencies.P50.Seconds()*1000), "p50(ms)", tName))
	tc = append(tc, perf.CreatePerfTestCase(float32(metrics.Latencies.Quantile(0.90).Seconds()*1000), "p90(ms)", tName))
	tc = append(tc, perf.CreatePerfTestCase(float32(metrics.Latencies.P99.Seconds()*1000), "p99(ms)", tName))

	// Add errorsPercentage metrics
	tc = append(tc, perf.CreatePerfTestCase(float32(1-metrics.Success), "errorsPercentage", tName))

	if err = testgrid.CreateXMLOutput(tc, filename(tName)); err != nil {
		t.Fatalf("Cannot create output xml: %v", err)
	}
}

// TestBenchmarkSteadyTraffic generates steady traffic at different volumes.
func TestBenchmarkSteadyTraffic(t *testing.T) {
	for _, load := range loads {
		t.Run(fmt.Sprintf("N%d", load), func(t *testing.T) {
			zeroToNSteadyPacer := NewSteadyUpPacer(
				vegeta.Rate{Freq: 1, Per: time.Second},
				vegeta.Rate{Freq: load, Per: time.Second},
				duration/2,
			)
			runTest(t, zeroToNSteadyPacer, true)
		})
	}
}

// TestBenchmarkBurstZeroToN generates a burst from 0 to N concurrent requests, for different values of N.
func TestBenchmarkBurstZeroToN(t *testing.T) {
	for _, load := range loads {
		t.Run(fmt.Sprintf("N%d", load), func(t *testing.T) {
			zeroToNBurstPacer := vegeta.ConstantPacer{Freq: load, Per: time.Second}
			runTest(t, zeroToNBurstPacer, true)
		})
	}
}

// TestBenchmarkBurstNto2N generates a burst from N to 2N concurrent requests, for different values of N.
func TestBenchmarkBurstNto2N(t *testing.T) {
	for _, load := range loads {
		t.Run(fmt.Sprintf("N%d", load), func(t *testing.T) {
			// Steady ramp up from 0 to N, then burst to 2N.
			zeroToNSteadyPacer := NewSteadyUpPacer(
				vegeta.Rate{Freq: 1, Per: time.Second},
				vegeta.Rate{Freq: load, Per: time.Second},
				duration,
			)
			// Scale up to the desired load and discard the metrics.
			runTest(t, zeroToNSteadyPacer, false)

			nTo2NBurstPacer := vegeta.ConstantPacer{Freq: 2 * load, Per: time.Second}
			runTest(t, nTo2NBurstPacer, true)
		})
	}
}
<|endoftext|>


<commit_before>package dnsr

var root = `
;       This file holds the information on root name servers needed to
;       initialize cache of Internet domain name servers
;       (e.g. reference this file in the "cache  .  <file>"
;       configuration file of BIND domain name servers).
;
;       This file is made available by InterNIC
;       under anonymous FTP as
;           file                /domain/named.cache
;           on server           FTP.INTERNIC.NET
;       -OR-                    RS.INTERNIC.NET
;
;       last update:     May 12, 2022
;       related version of root zone:     2022051201
;
; FORMERLY NS.INTERNIC.NET
;
.                        3600000      NS    A.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET.      3600000      A     198.41.0.4
A.ROOT-SERVERS.NET.      3600000      AAAA  2001:503:ba3e::2:30
;
; FORMERLY NS1.ISI.EDU
;
.                        3600000      NS    B.ROOT-SERVERS.NET.
B.ROOT-SERVERS.NET.      3600000      A     199.9.14.201
B.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:200::b
;
; FORMERLY C.PSI.NET
;
.                        3600000      NS    C.ROOT-SERVERS.NET.
C.ROOT-SERVERS.NET.      3600000      A     192.33.4.12
C.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:2::c
;
; FORMERLY TERP.UMD.EDU
;
.                        3600000      NS    D.ROOT-SERVERS.NET.
D.ROOT-SERVERS.NET.      3600000      A     199.7.91.13
D.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:2d::d
;
; FORMERLY NS.NASA.GOV
;
.                        3600000      NS    E.ROOT-SERVERS.NET.
E.ROOT-SERVERS.NET.      3600000      A     192.203.230.10
E.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:a8::e
;
; FORMERLY NS.ISC.ORG
;
.                        3600000      NS    F.ROOT-SERVERS.NET.
F.ROOT-SERVERS.NET.      3600000      A     192.5.5.241
F.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:2f::f
;
; FORMERLY NS.NIC.DDN.MIL
;
.                        3600000      NS    G.ROOT-SERVERS.NET.
G.ROOT-SERVERS.NET.      3600000      A     192.112.36.4
G.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:12::d0d
;
; FORMERLY AOS.ARL.ARMY.MIL
;
.                        3600000      NS    H.ROOT-SERVERS.NET.
H.ROOT-SERVERS.NET.      3600000      A     198.97.190.53
H.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:1::53
;
; FORMERLY NIC.NORDU.NET
;
.                        3600000      NS    I.ROOT-SERVERS.NET.
I.ROOT-SERVERS.NET.      3600000      A     192.36.148.17
I.ROOT-SERVERS.NET.      3600000      AAAA  2001:7fe::53
;
; OPERATED BY VERISIGN, INC.
;
.                        3600000      NS    J.ROOT-SERVERS.NET.
J.ROOT-SERVERS.NET.      3600000      A     192.58.128.30
J.ROOT-SERVERS.NET.      3600000      AAAA  2001:503:c27::2:30
;
; OPERATED BY RIPE NCC
;
.                        3600000      NS    K.ROOT-SERVERS.NET.
K.ROOT-SERVERS.NET.      3600000      A     193.0.14.129
K.ROOT-SERVERS.NET.      3600000      AAAA  2001:7fd::1
;
; OPERATED BY ICANN
;
.                        3600000      NS    L.ROOT-SERVERS.NET.
L.ROOT-SERVERS.NET.      3600000      A     199.7.83.42
L.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:9f::42
;
; OPERATED BY WIDE
;
.                        3600000      NS    M.ROOT-SERVERS.NET.
M.ROOT-SERVERS.NET.      3600000      A     202.12.27.33
M.ROOT-SERVERS.NET.      3600000      AAAA  2001:dc3::35
; End of file`
<commit_msg>Automatic update for Thu Jun  2 08:20:51 UTC 2022<commit_after>package dnsr

var root = `
;       This file holds the information on root name servers needed to
;       initialize cache of Internet domain name servers
;       (e.g. reference this file in the "cache  .  <file>"
;       configuration file of BIND domain name servers).
;
;       This file is made available by InterNIC
;       under anonymous FTP as
;           file                /domain/named.cache
;           on server           FTP.INTERNIC.NET
;       -OR-                    RS.INTERNIC.NET
;
;       last update:     June 01, 2022
;       related version of root zone:     2022060101
;
; FORMERLY NS.INTERNIC.NET
;
.                        3600000      NS    A.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET.      3600000      A     198.41.0.4
A.ROOT-SERVERS.NET.      3600000      AAAA  2001:503:ba3e::2:30
;
; FORMERLY NS1.ISI.EDU
;
.                        3600000      NS    B.ROOT-SERVERS.NET.
B.ROOT-SERVERS.NET.      3600000      A     199.9.14.201
B.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:200::b
;
; FORMERLY C.PSI.NET
;
.                        3600000      NS    C.ROOT-SERVERS.NET.
C.ROOT-SERVERS.NET.      3600000      A     192.33.4.12
C.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:2::c
;
; FORMERLY TERP.UMD.EDU
;
.                        3600000      NS    D.ROOT-SERVERS.NET.
D.ROOT-SERVERS.NET.      3600000      A     199.7.91.13
D.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:2d::d
;
; FORMERLY NS.NASA.GOV
;
.                        3600000      NS    E.ROOT-SERVERS.NET.
E.ROOT-SERVERS.NET.      3600000      A     192.203.230.10
E.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:a8::e
;
; FORMERLY NS.ISC.ORG
;
.                        3600000      NS    F.ROOT-SERVERS.NET.
F.ROOT-SERVERS.NET.      3600000      A     192.5.5.241
F.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:2f::f
;
; FORMERLY NS.NIC.DDN.MIL
;
.                        3600000      NS    G.ROOT-SERVERS.NET.
G.ROOT-SERVERS.NET.      3600000      A     192.112.36.4
G.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:12::d0d
;
; FORMERLY AOS.ARL.ARMY.MIL
;
.                        3600000      NS    H.ROOT-SERVERS.NET.
H.ROOT-SERVERS.NET.      3600000      A     198.97.190.53
H.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:1::53
;
; FORMERLY NIC.NORDU.NET
;
.                        3600000      NS    I.ROOT-SERVERS.NET.
I.ROOT-SERVERS.NET.      3600000      A     192.36.148.17
I.ROOT-SERVERS.NET.      3600000      AAAA  2001:7fe::53
;
; OPERATED BY VERISIGN, INC.
;
.                        3600000      NS    J.ROOT-SERVERS.NET.
J.ROOT-SERVERS.NET.      3600000      A     192.58.128.30
J.ROOT-SERVERS.NET.      3600000      AAAA  2001:503:c27::2:30
;
; OPERATED BY RIPE NCC
;
.                        3600000      NS    K.ROOT-SERVERS.NET.
K.ROOT-SERVERS.NET.      3600000      A     193.0.14.129
K.ROOT-SERVERS.NET.      3600000      AAAA  2001:7fd::1
;
; OPERATED BY ICANN
;
.                        3600000      NS    L.ROOT-SERVERS.NET.
L.ROOT-SERVERS.NET.      3600000      A     199.7.83.42
L.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:9f::42
;
; OPERATED BY WIDE
;
.                        3600000      NS    M.ROOT-SERVERS.NET.
M.ROOT-SERVERS.NET.      3600000      A     202.12.27.33
M.ROOT-SERVERS.NET.      3600000      AAAA  2001:dc3::35
; End of file`
<|endoftext|>


<commit_before>//
// Copyright (c) 2017
// Mainflux
// Cavium
//
// SPDX-License-Identifier: Apache-2.0
//

package client

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/drasko/edgex-export"
	"github.com/drasko/edgex-export/mongo"
	"github.com/go-zoo/bone"
	"go.uber.org/zap"
	"gopkg.in/mgo.v2/bson"
)

const (
	// TODO this consts need to be configurable somehow
	distroHost     = "127.0.0.1"
	distroPort int = 48070
)

func getRegByID(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	id := bone.GetValue(r, "id")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	reg := export.Registration{}
	if err := c.Find(bson.M{"id": id}).One(&reg); err != nil {
		logger.Error("Failed to query by id", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	res, err := json.Marshal(reg)
	if err != nil {
		logger.Error("Failed to query by id", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, string(res))
}

func getRegList(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	t := bone.GetValue(r, "type")

	var l string

	switch t {
	case "algorithms":
		l = `["None","Aes"]`
	case "compressions":
		l = `["None","Gzip","Zip"]`
	case "formats":
		l = `["JSON","XML","Serialized","IotCoreJSON","AzureJSON","CSV"]`
	case "destinations":
		l = `["DestMQTT", "TeDestZMQller", "DestIotCoreMQTT,
			"DestAzureMQTT", "DestRest"]`
	default:
		logger.Error("Unknown type: " + t)
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, "Unknown type: "+t)
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, l)
}

func getAllReg(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	reg := []export.Registration{}
	if err := c.Find(nil).All(&reg); err != nil {
		logger.Error("Failed to query all registrations", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	res, err := json.Marshal(reg)
	if err != nil {
		logger.Error("Failed to query all registrations", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, string(res))
}

func getRegByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	name := bone.GetValue(r, "name")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	reg := export.Registration{}
	if err := c.Find(bson.M{"name": name}).One(&reg); err != nil {
		logger.Error("Failed to query by name", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	res, err := json.Marshal(reg)
	if err != nil {
		logger.Error("Failed to query by name", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, string(res))
}

func addReg(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, err.Error())
		return
	}

	reg := export.Registration{}
	if err := json.Unmarshal(data, &reg); err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, err.Error())
		return
	}

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	count, err := c.Find(bson.M{"name": reg.Name}).Count()
	if err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}
	if count != 0 {
		logger.Error("Username already taken: " + reg.Name)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if err := c.Insert(reg); err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusCreated)
	notifyUpdatedRegistrations()
}

func updateReg(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		logger.Error("Failed to query update registration", zap.Error(err))
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, err.Error())
		return
	}

	var body map[string]interface{}
	if err := json.Unmarshal(data, &body); err != nil {
		logger.Error("Failed to query update registration", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
	}

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	name := body["name"]
	query := bson.M{"name": name}
	update := bson.M{"$set": body}

	if err := c.Update(query, update); err != nil {
		logger.Error("Failed to query update registration", zap.Error(err))
		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	notifyUpdatedRegistrations()
}

func delRegByID(w http.ResponseWriter, r *http.Request) {
	id := bone.GetValue(r, "id")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	if err := c.Remove(bson.M{"id": id}); err != nil {
		logger.Error("Failed to query by id", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	notifyUpdatedRegistrations()
}

func delRegByName(w http.ResponseWriter, r *http.Request) {
	name := bone.GetValue(r, "name")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	if err := c.Remove(bson.M{"name": name}); err != nil {
		logger.Error("Failed to query by name", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	notifyUpdatedRegistrations()
}

func notifyUpdatedRegistrations() {
	go func() {
		// TODO make configurable distro host/port
		client := &http.Client{}
		url := "http://" + distroHost + ":" + strconv.Itoa(distroPort) +
			"/api/v1/notify/registrations"
		req, err := http.NewRequest(http.MethodPut, url, nil)
		if err != nil {
			logger.Error("Error creating http request")
			return
		}
		_, err = client.Do(req)
		if err != nil {
			logger.Error("Error notifying updated registrations to distro", zap.String("url", url))
		}
	}()
}
<commit_msg>Use constants for building reference reply<commit_after>//
// Copyright (c) 2017
// Mainflux
// Cavium
//
// SPDX-License-Identifier: Apache-2.0
//

package client

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/drasko/edgex-export"
	"github.com/drasko/edgex-export/mongo"
	"github.com/go-zoo/bone"
	"go.uber.org/zap"
	"gopkg.in/mgo.v2/bson"
)

const (
	// TODO this consts need to be configurable somehow
	distroHost     = "127.0.0.1"
	distroPort int = 48070
)

func getRegByID(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	id := bone.GetValue(r, "id")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	reg := export.Registration{}
	if err := c.Find(bson.M{"id": id}).One(&reg); err != nil {
		logger.Error("Failed to query by id", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	res, err := json.Marshal(reg)
	if err != nil {
		logger.Error("Failed to query by id", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, string(res))
}

func getRegList(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	t := bone.GetValue(r, "type")

	var l string

	switch t {
	case "algorithms":
		l = `["` + export.EncNone + `"," ` + export.EncAes + `"]`
	case "compressions":
		l = `["` + export.CompNone + `","` + export.CompGzip + `","` +
			export.CompZip + `"]`
	case "formats":
		l = `["` + export.FormatJSON + `","` + export.FormatXML + `"]`
	case "destinations":
		l = `["` + export.DestMQTT + `", "` + export.DestRest + `"]`
	default:
		logger.Error("Unknown type: " + t)
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, "Unknown type: "+t)
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, l)
}

func getAllReg(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	reg := []export.Registration{}
	if err := c.Find(nil).All(&reg); err != nil {
		logger.Error("Failed to query all registrations", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	res, err := json.Marshal(reg)
	if err != nil {
		logger.Error("Failed to query all registrations", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, string(res))
}

func getRegByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	name := bone.GetValue(r, "name")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	reg := export.Registration{}
	if err := c.Find(bson.M{"name": name}).One(&reg); err != nil {
		logger.Error("Failed to query by name", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	res, err := json.Marshal(reg)
	if err != nil {
		logger.Error("Failed to query by name", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, string(res))
}

func addReg(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, err.Error())
		return
	}

	reg := export.Registration{}
	if err := json.Unmarshal(data, &reg); err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, err.Error())
		return
	}

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	count, err := c.Find(bson.M{"name": reg.Name}).Count()
	if err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}
	if count != 0 {
		logger.Error("Username already taken: " + reg.Name)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if err := c.Insert(reg); err != nil {
		logger.Error("Failed to query add registration", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusCreated)
	notifyUpdatedRegistrations()
}

func updateReg(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		logger.Error("Failed to query update registration", zap.Error(err))
		w.WriteHeader(http.StatusBadRequest)
		io.WriteString(w, err.Error())
		return
	}

	var body map[string]interface{}
	if err := json.Unmarshal(data, &body); err != nil {
		logger.Error("Failed to query update registration", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
	}

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	name := body["name"]
	query := bson.M{"name": name}
	update := bson.M{"$set": body}

	if err := c.Update(query, update); err != nil {
		logger.Error("Failed to query update registration", zap.Error(err))
		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	notifyUpdatedRegistrations()
}

func delRegByID(w http.ResponseWriter, r *http.Request) {
	id := bone.GetValue(r, "id")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	if err := c.Remove(bson.M{"id": id}); err != nil {
		logger.Error("Failed to query by id", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	notifyUpdatedRegistrations()
}

func delRegByName(w http.ResponseWriter, r *http.Request) {
	name := bone.GetValue(r, "name")

	s := repo.Session.Copy()
	defer s.Close()
	c := s.DB(mongo.DBName).C(mongo.CollectionName)

	if err := c.Remove(bson.M{"name": name}); err != nil {
		logger.Error("Failed to query by name", zap.Error(err))
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	notifyUpdatedRegistrations()
}

func notifyUpdatedRegistrations() {
	go func() {
		// TODO make configurable distro host/port
		client := &http.Client{}
		url := "http://" + distroHost + ":" + strconv.Itoa(distroPort) +
			"/api/v1/notify/registrations"
		req, err := http.NewRequest(http.MethodPut, url, nil)
		if err != nil {
			logger.Error("Error creating http request")
			return
		}
		_, err = client.Do(req)
		if err != nil {
			logger.Error("Error notifying updated registrations to distro", zap.String("url", url))
		}
	}()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"time"

	log "github.com/cihub/seelog"
	"github.com/stampzilla/gocast"
	"github.com/stampzilla/gocast/events"
	"github.com/stampzilla/gocast/handlers"
	"github.com/stampzilla/gocast/responses"
)

type Chromecast struct {
	Id    string
	Name_ string `json:"Name"`

	PrimaryApp      string
	PrimaryEndpoint string
	Playing         bool
	//Paused          bool

	IsStandBy     bool
	IsActiveInput bool

	Volume float64
	Muted  bool

	Addr net.IP
	Port int

	publish func()

	mediaHandler           *handlers.Media
	mediaConnectionHandler *handlers.Connection

	appLaunch chan string

	*gocast.Device
}

func NewChromecast(d *gocast.Device) *Chromecast {
	c := &Chromecast{
		Device:                 d,
		mediaHandler:           &handlers.Media{},
		mediaConnectionHandler: &handlers.Connection{},
		appLaunch:              make(chan string),
	}

	c.OnEvent(c.Event)
	return c
}

func (c *Chromecast) Play() {
	c.mediaHandler.Play()
}
func (c *Chromecast) Pause() {
	c.mediaHandler.Pause()
}
func (c *Chromecast) Stop() {
	c.mediaHandler.Stop()
}

func (c *Chromecast) PlayUrl(url string, contentType string) {
	err := c.Device.ReceiverHandler.LaunchApp(gocast.AppMedia)
	if err != nil && err != handlers.ErrAppAlreadyLaunched {
		log.Error(err)
		return
	}

	if err != handlers.ErrAppAlreadyLaunched {
		//Wait for new media connection to launched app
		if err := c.waitForAppLaunch(gocast.AppMedia); err != nil {
			log.Error(err)
			return
		}
	}

	if contentType == "" {
		contentType = "audio/mpeg"
	}
	item := responses.MediaItem{
		ContentId:   url,
		StreamType:  "BUFFERED",
		ContentType: contentType,
	}
	err = c.mediaHandler.LoadMedia(item, 0, true, map[string]interface{}{})
	if err != nil {
		log.Error(err)
		return
	}
}

func (c *Chromecast) waitForAppLaunch(app string) error {
	select {
	case launchedApp := <-c.appLaunch:
		if app == launchedApp {
			return nil
		}
		return fmt.Errorf("Wrong app launched. Expected %s got %s", app, launchedApp)
	case <-time.After(time.Second * 20):
		return fmt.Errorf("timeout waiting for app launch after 20 seconds")

	}

}
func (c *Chromecast) appLaunched(app string) {
	select {
	case c.appLaunch <- app:
		log.Info("Notified c.appLaunch")
	default:
		log.Info("No one is waiting for appLaunch event")
	}
}

func (c *Chromecast) Event(event events.Event) {
	switch data := event.(type) {
	case events.Connected:
		log.Info(c.Name(), "- Connected")

		c.Addr = c.Ip()
		c.Port = c.Device.Port()
		c.Id = c.Uuid()
		c.Name_ = c.Name()

		state.Add(c)
	case events.Disconnected:
		log.Warn(c.Name(), "- Disconnected")

		state.Remove(c)
	case events.AppStarted:
		log.Info(c.Name(), "- App started:", data.DisplayName, "(", data.AppID, ")")
		//spew.Dump("Data:", data)

		c.PrimaryApp = data.DisplayName
		c.PrimaryEndpoint = data.TransportId

		//If the app supports media controls lets subscribe to it
		if data.HasNamespace("urn:x-cast:com.google.cast.media") {
			log.Info(c.Name(), "- Subscribe cast.tp.connection:", data.DisplayName, "(", data.AppID, ")")
			c.Subscribe("urn:x-cast:com.google.cast.tp.connection", data.TransportId, c.mediaConnectionHandler)
			log.Info(c.Name(), "- Subscribe cast.media:", data.DisplayName, "(", data.AppID, ")")
			c.Subscribe("urn:x-cast:com.google.cast.media", data.TransportId, c.mediaHandler)
		}
		c.appLaunched(data.AppID)
		log.Info(c.Name(), "- Notifying appLanunched:", data.DisplayName, "(", data.AppID, ")")

	case events.AppStopped:
		log.Info(c.Name(), "- App stopped:", data.DisplayName, "(", data.AppID, ")")
		//spew.Dump("Data:", data)

		//unsubscribe from old channels
		for _, v := range data.Namespaces {
			c.UnsubscribeByUrnAndDestinationId(v.Name, data.TransportId)
		}
		c.PrimaryApp = ""
		c.PrimaryEndpoint = ""
		c.Playing = false

	case events.ReceiverStatus:
		c.IsStandBy = data.Status.IsStandBy
		c.IsActiveInput = data.Status.IsActiveInput
		c.Volume = data.Status.Volume.Level
		c.Muted = data.Status.Volume.Muted
	case events.Media:
		playing := c.Playing
		if data.PlayerState == "PLAYING" {
			c.Playing = true
		} else {
			c.Playing = false
		}

		//Only publish if playing state changed
		if playing != c.Playing {
			c.publish()
		}
		return

	//gocast.MediaEvent:
	default:
		log.Warn("unexpected event %T: %#v\n", data, data)
	}

	c.publish()
}
<commit_msg>possible bugfix<commit_after>package main

import (
	"fmt"
	"net"
	"time"

	log "github.com/cihub/seelog"
	"github.com/stampzilla/gocast"
	"github.com/stampzilla/gocast/events"
	"github.com/stampzilla/gocast/handlers"
	"github.com/stampzilla/gocast/responses"
)

type Chromecast struct {
	Id    string
	Name_ string `json:"Name"`

	PrimaryApp      string
	PrimaryEndpoint string
	Playing         bool
	//Paused          bool

	IsStandBy     bool
	IsActiveInput bool

	Volume float64
	Muted  bool

	Addr net.IP
	Port int

	publish func()

	mediaHandler           *handlers.Media
	mediaConnectionHandler *handlers.Connection

	appLaunch chan string

	*gocast.Device
}

func NewChromecast(d *gocast.Device) *Chromecast {
	c := &Chromecast{
		Device:                 d,
		mediaHandler:           &handlers.Media{},
		mediaConnectionHandler: &handlers.Connection{},
		appLaunch:              make(chan string),
	}

	c.OnEvent(c.Event)
	return c
}

func (c *Chromecast) Play() {
	c.mediaHandler.Play()
}
func (c *Chromecast) Pause() {
	c.mediaHandler.Pause()
}
func (c *Chromecast) Stop() {
	c.mediaHandler.Stop()
}

func (c *Chromecast) PlayUrl(url string, contentType string) {
	err := c.Device.ReceiverHandler.LaunchApp(gocast.AppMedia)
	if err != nil && err != handlers.ErrAppAlreadyLaunched {
		log.Error(err)
		return
	}

	if err != handlers.ErrAppAlreadyLaunched {
		//Wait for new media connection to launched app
		if err := c.waitForAppLaunch(gocast.AppMedia); err != nil {
			log.Error(err)
			return
		}
	}

	if contentType == "" {
		contentType = "audio/mpeg"
	}
	item := responses.MediaItem{
		ContentId:   url,
		StreamType:  "BUFFERED",
		ContentType: contentType,
	}
	err = c.mediaHandler.LoadMedia(item, 0, true, map[string]interface{}{})
	if err != nil {
		log.Error(err)
		return
	}
}

func (c *Chromecast) waitForAppLaunch(app string) error {
	log.Info("waitForAppLaunch pointer: %p", c.appLaunch)
	select {
	case launchedApp := <-c.appLaunch:
		if app == launchedApp {
			return nil
		}
		return fmt.Errorf("Wrong app launched. Expected %s got %s", app, launchedApp)
	case <-time.After(time.Second * 20):
		return fmt.Errorf("timeout waiting for app launch after 20 seconds")

	}

}
func (c *Chromecast) appLaunched(app string) {
	log.Info("appLaunched pointer: %p", c.appLaunch)
	select {
	case c.appLaunch <- app:
		log.Info("Notified c.appLaunch")
	default:
		log.Info("No one is waiting for appLaunch event")
	}
}

func (c *Chromecast) Event(event events.Event) {
	switch data := event.(type) {
	case events.Connected:
		log.Info(c.Name(), "- Connected")

		c.Addr = c.Ip()
		c.Port = c.Device.Port()
		c.Id = c.Uuid()
		c.Name_ = c.Name()

		state.Add(c)
	case events.Disconnected:
		log.Warn(c.Name(), "- Disconnected")

		state.Remove(c)
	case events.AppStarted:
		log.Info(c.Name(), "- App started:", data.DisplayName, "(", data.AppID, ")")
		//spew.Dump("Data:", data)

		c.PrimaryApp = data.DisplayName
		c.PrimaryEndpoint = data.TransportId

		//If the app supports media controls lets subscribe to it
		if data.HasNamespace("urn:x-cast:com.google.cast.media") {
			log.Info(c.Name(), "- Subscribe cast.tp.connection:", data.DisplayName, "(", data.AppID, ")")
			c.Subscribe("urn:x-cast:com.google.cast.tp.connection", data.TransportId, c.mediaConnectionHandler)
			log.Info(c.Name(), "- Subscribe cast.media:", data.DisplayName, "(", data.AppID, ")")
			c.Subscribe("urn:x-cast:com.google.cast.media", data.TransportId, c.mediaHandler)
		}
		c.appLaunched(data.AppID)
		log.Info(c.Name(), "- Notifying appLanunched:", data.DisplayName, "(", data.AppID, ")")

	case events.AppStopped:
		log.Info(c.Name(), "- App stopped:", data.DisplayName, "(", data.AppID, ")")
		//spew.Dump("Data:", data)

		//unsubscribe from old channels
		for _, v := range data.Namespaces {
			c.UnsubscribeByUrnAndDestinationId(v.Name, data.TransportId)
		}
		c.PrimaryApp = ""
		c.PrimaryEndpoint = ""
		c.Playing = false

	case events.ReceiverStatus:
		c.IsStandBy = data.Status.IsStandBy
		c.IsActiveInput = data.Status.IsActiveInput
		c.Volume = data.Status.Volume.Level
		c.Muted = data.Status.Volume.Muted
	case events.Media:
		playing := c.Playing
		if data.PlayerState == "PLAYING" {
			c.Playing = true
		} else {
			c.Playing = false
		}

		//Only publish if playing state changed
		if playing != c.Playing {
			c.publish()
		}
		return

	//gocast.MediaEvent:
	default:
		log.Warn("unexpected event %T: %#v\n", data, data)
	}

	c.publish()
}
<|endoftext|>


<commit_before>// Copyright (C) 2013  gokabinet authors.
// Use of this source code is governed by a GPLv3
// license that can be found in the LICENSE file.

package kt

import (
	"bytes"
	"encoding/base64"
	"errors"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"
)

const DEFAULT_TIMEOUT = 2 * time.Second

// Conn represents a connection to a kyoto tycoon endpoint.
// It uses a connection pool to efficiently communicate with the server.
// Conn is safe for concurrent use.
type Conn struct {
	timeout   time.Duration
	host      string
	transport *http.Transport
}

// KT supports a "RESTful" interface that is cheaper than the TSV-RPC,
// We use the TSV interface because the RESTful interface is underspecified.
// It is not clear how you are to escape the URLs in a couple of cases, specifically
// around the use of slash as a key. Since we might have to escape the slashes inside
// a path, that means using the opaque URL field, which I really don't like

// NewConn creates a connection to an Kyoto Tycoon endpoint.
func NewConn(host string, port int, poolsize int, timeout time.Duration) *Conn {
	portstr := strconv.Itoa(port)
	c := &Conn{
		timeout: timeout,
		host:    net.JoinHostPort(host, portstr),
		transport: &http.Transport{
			ResponseHeaderTimeout: timeout,
			MaxIdleConnsPerHost:   poolsize,
		},
	}

	return c
}

var (
	ErrTimeout = errors.New("kt: operation timeout")
	// the wording on this error is deliberately weird,
	// because users would search for the string logical inconsistency
	// in order to find lookup misses.
	ErrNotFound = errors.New("kt: entry not found aka logical inconsistency")
	// old gokabinet returned this error on success. Keeping around "for compatibility" until
	// I can kill it with fire.
	ErrSuccess = errors.New("kt: success")
)

// Count returns the number of records in the database
func (c *Conn) Count() (int, error) {
	code, m, err := c.doRPC("/rpc/status", nil)
	if err != nil {
		return 0, err
	}
	if code != 200 {
		return 0, makeError(m)
	}
	return strconv.Atoi(string(m["count"]))
}

// Remove deletes the data at key in the database.
func (c *Conn) Remove(key string) error {
	vals := map[string][]byte{
		"key": []byte(key),
	}
	code, m, err := c.doRPC("/rpc/remove", vals)
	if err != nil {
		return err
	}
	if code != 200 {
		return makeError(m)
	}
	return nil
}

// GetBulk retrieves the keys in the map. The results will be filled in on function return.
// If a key was not found in the database, it will be removed from the map.
func (c *Conn) GetBulk(keysAndVals map[string]string) error {
	m := make(map[string][]byte)
	for k := range keysAndVals {
		m[k] = zeroslice
	}
	err := c.GetBulkBytes(m)
	if err != nil {
		return err
	}
	for k := range keysAndVals {
		b, ok := m[k]
		if ok {
			keysAndVals[k] = string(b)
		} else {
			delete(keysAndVals, k)
		}
	}
	return nil
}

// Get retrieves the data stored at key. ErrNotFound is
// returned if no such data exists
func (c *Conn) Get(key string) (string, error) {
	s, err := c.GetBytes(key)
	if err != nil {
		return "", err
	}
	return string(s), nil
}

// GetBytes retrieves the data stored at key in the format of a byte slice
// A nil slice and nil error is returned if no data at key exists.
func (c *Conn) GetBytes(key string) ([]byte, error) {
	vals := map[string][]byte{
		"key": []byte(key),
	}
	code, m, err := c.doRPC("/rpc/get", vals)
	if err != nil {
		return nil, err
	}
	switch code {
	case 200:
		break
	case 450:
		return nil, ErrNotFound
	default:
		return nil, makeError(m)
	}
	return m["value"], nil

}

// Set stores the data at key
func (c *Conn) Set(key string, value []byte) error {
	vals := map[string][]byte{
		"key":   []byte(key),
		"value": value,
	}
	code, m, err := c.doRPC("/rpc/set", vals)
	if err != nil {
		return err
	}
	switch code {
	case 200:
		return nil
	default:
		return makeError(m)
	}
}

var zeroslice = []byte("0")

// GetBulkBytes retrieves the keys in the map. The results will be filled in on function return.
// If a key was not found in the database, it will be removed from the map.
func (c *Conn) GetBulkBytes(keys map[string][]byte) error {
	keystransmit := make(map[string][]byte)
	for k, _ := range keys {
		keystransmit["_"+k] = zeroslice
	}
	code, m, err := c.doRPC("/rpc/get_bulk", keystransmit)
	if err != nil {
		return err
	}
	if code != 200 {
		return makeError(m)
	}
	for k := range keys {
		val, ok := m["_"+k]
		if !ok {
			delete(keys, k)
			continue
		}
		keys[k] = val
	}
	return nil
}

// SetBulk stores the values in the map.
func (c *Conn) SetBulk(values map[string]string) (int64, error) {
	vals := make(map[string][]byte)
	for k, v := range values {
		vals["_"+k] = []byte(v)
	}
	code, m, err := c.doRPC("/rpc/set_bulk", vals)
	if err != nil {
		return 0, err
	}
	if code != 200 {
		return 0, makeError(m)
	}
	return strconv.ParseInt(string(m["num"]), 10, 64)
}

// RemoveBulk deletes the values
func (c *Conn) RemoveBulk(keys []string) (int64, error) {
	vals := make(map[string][]byte)
	for _, k := range keys {
		vals["_"+k] = zeroslice
	}
	code, m, err := c.doRPC("/rpc/remove_bulk", vals)
	if err != nil {
		return 0, err
	}
	if code != 200 {
		return 0, makeError(m)
	}
	return strconv.ParseInt(string(m["num"]), 10, 64)
}

// MatchPrefix performs the match_prefix operation against the server
// It returns a sorted list of strings.
// The error may be ErrSuccess in the case that no records were found.
// This is for compatibility with the old gokabinet library.
func (c *Conn) MatchPrefix(key string, maxrecords int64) ([]string, error) {
	keystransmit := map[string][]byte{
		"prefix": []byte(key),
		"max":    []byte(strconv.FormatInt(maxrecords, 10)),
	}
	code, m, err := c.doRPC("/rpc/match_prefix", keystransmit)
	if err != nil {
		return nil, err
	}
	if code != 200 {
		return nil, makeError(m)
	}
	res := make([]string, 0, len(m))
	for k := range m {
		if k[0] == '_' {
			res = append(res, string(k[1:]))
		}
	}
	if len(res) == 0 {
		// yeah, gokabinet was weird here.
		return nil, ErrSuccess
	}
	// kt spits the prefixes out in sorted order
	// so do that. Users depend on the order.
	sort.StringSlice(res).Sort()
	return res, nil
}

// prefabHeader is the header that rpc request share.
var prefabHeader = makeprefab()

func makeprefab() http.Header {
	r := make(http.Header)
	r.Set("Content-Type", "text/tab-separated-values; colenc=B")
	return r
}

// Do an RPC call against the KT endpoint.
func (c *Conn) doRPC(path string, values map[string][]byte) (code int, vals map[string][]byte, err error) {
	url := &url.URL{
		Scheme: "http",
		Host:   c.host,
		Path:   path,
	}
	req := &http.Request{
		Method: "POST",
		URL:    url,
		Header: prefabHeader,
	}
	body := tsvEncode(values)

	bodyReader := bytes.NewBuffer(body)
	req.Body = ioutil.NopCloser(bodyReader)
	req.ContentLength = int64(len(body))
	t := time.Now()
	resp, err := c.transport.RoundTrip(req)
	if err != nil {
		return 0, nil, err
	}
	dur := time.Since(t)
	timeout := c.timeout - dur
	if timeout < 0 {
		return 0, nil, ErrTimeout
	}
	resultBody, err := timeoutRead(resp.Body, timeout)
	if err != nil {
		return 0, nil, err
	}
	m := decodeValues(resultBody, resp.Header.Get("Content-Type"))
	return resp.StatusCode, m, nil
}

// Encode the request body in base64 encoded TSV
func tsvEncode(values map[string][]byte) []byte {
	var bufsize int
	for k, v := range values {
		// length of key
		bufsize += base64.StdEncoding.EncodedLen(len(k))
		// tab
		bufsize += 1
		// value
		bufsize += base64.StdEncoding.EncodedLen(len(v))
		// newline
		bufsize += 1
	}
	buf := make([]byte, bufsize)
	var n int
	for k, v := range values {
		base64.StdEncoding.Encode(buf[n:], []byte(k))
		n += base64.StdEncoding.EncodedLen(len(k))
		buf[n] = '\t'
		n++
		base64.StdEncoding.Encode(buf[n:], v)
		n += base64.StdEncoding.EncodedLen(len(v))
		buf[n] = '\n'
		n++
	}
	return buf
}

func timeoutRead(body io.ReadCloser, timeout time.Duration) ([]byte, error) {
	type readResult struct {
		buf []byte
		err error
	}
	ch := make(chan readResult, 1)
	go func() {
		buf, err := ioutil.ReadAll(body)
		ch <- readResult{buf, err}
	}()
	defer body.Close()
	var res readResult
	select {
	case <-time.After(timeout):
		return nil, ErrTimeout
	case res = <-ch:
		return res.buf, res.err
	}
}

// decodeValues takes a response from an KT RPC call and turns it into the
// values that it returned.
//
// KT can return values in 3 different formats, Tab separated values (TSV) without any field encoding,
// TSV with fields base64 encoded or TSV with URL encoding.
// KT does not give you any option as to the format that it returns, so we have to implement all of them
func decodeValues(buf []byte, contenttype string) map[string][]byte {
	if len(buf) == 0 {
		return nil
	}
	// Ideally, we should parse the mime media type here,
	// but this is an expensive operation because mime is just
	// that awful.
	//
	// KT responses are pretty simple and we can rely
	// on it putting the parameter of colenc=[BU] at
	// the end of the string. Just look for B, U or S
	// (last character of tab-separated-values)
	// to figure out which field encoding is used.
	var decodef decodefunc
	switch contenttype[len(contenttype)-1] {
	case 'B':
		decodef = base64Decode
	case 'U':
		decodef = urlDecode
	case 's':
		decodef = identityDecode
	default:
		panic("kt responded with unknown content-type: " + contenttype)
	}

	kv := make(map[string][]byte)
	b := bytes.NewBuffer(buf)
	for {
		key, err := b.ReadBytes('\t')
		if err != nil {
			return kv
		}
		key = decodef(key[:len(key)-1])
		value, err := b.ReadBytes('\n')
		if len(value) > 0 {
			fieldlen := len(value) - 1
			if value[len(value)-1] != '\n' {
				fieldlen = len(value)
			}
			value = decodef(value[:fieldlen])
			kv[string(key)] = value
		}
		if err != nil {
			return kv
		}
	}
}

// decodefunc takes a byte slice and decodes the
// value in place. It returns a slice pointing into
// the original byte slice. It is used for decoding the
// individual fields of the TSV that kt returns
type decodefunc func([]byte) []byte

// Don't do anything, this is pure TSV
func identityDecode(b []byte) []byte {
	return b
}

// Base64 decode each of the field
func base64Decode(b []byte) []byte {
	n, _ := base64.StdEncoding.Decode(b, b)
	return b[:n]
}

// Decode % escaped URL format
func urlDecode(b []byte) []byte {
	res := b
	resi := 0
	for i := 0; i < len(b); i++ {
		if b[i] != '%' {
			res[resi] = b[i]
			resi++
			continue
		}
		res[resi] = unhex(b[i+1])<<4 | unhex(b[i+2])
		resi++
		i += 2
	}
	return res[:resi]
}

// copied from net/url
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

// TODO: make this return errors that can be introspected more easily
// and make it trim components of the error to filter out unused information.
func makeError(m map[string][]byte) error {
	return errors.New(string(m["ERROR"]))
}
<commit_msg>get rid of defer.<commit_after>// Copyright (C) 2013  gokabinet authors.
// Use of this source code is governed by a GPLv3
// license that can be found in the LICENSE file.

package kt

import (
	"bytes"
	"encoding/base64"
	"errors"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"
)

const DEFAULT_TIMEOUT = 2 * time.Second

// Conn represents a connection to a kyoto tycoon endpoint.
// It uses a connection pool to efficiently communicate with the server.
// Conn is safe for concurrent use.
type Conn struct {
	timeout   time.Duration
	host      string
	transport *http.Transport
}

// KT supports a "RESTful" interface that is cheaper than the TSV-RPC,
// We use the TSV interface because the RESTful interface is underspecified.
// It is not clear how you are to escape the URLs in a couple of cases, specifically
// around the use of slash as a key. Since we might have to escape the slashes inside
// a path, that means using the opaque URL field, which I really don't like

// NewConn creates a connection to an Kyoto Tycoon endpoint.
func NewConn(host string, port int, poolsize int, timeout time.Duration) *Conn {
	portstr := strconv.Itoa(port)
	c := &Conn{
		timeout: timeout,
		host:    net.JoinHostPort(host, portstr),
		transport: &http.Transport{
			ResponseHeaderTimeout: timeout,
			MaxIdleConnsPerHost:   poolsize,
		},
	}

	return c
}

var (
	ErrTimeout = errors.New("kt: operation timeout")
	// the wording on this error is deliberately weird,
	// because users would search for the string logical inconsistency
	// in order to find lookup misses.
	ErrNotFound = errors.New("kt: entry not found aka logical inconsistency")
	// old gokabinet returned this error on success. Keeping around "for compatibility" until
	// I can kill it with fire.
	ErrSuccess = errors.New("kt: success")
)

// Count returns the number of records in the database
func (c *Conn) Count() (int, error) {
	code, m, err := c.doRPC("/rpc/status", nil)
	if err != nil {
		return 0, err
	}
	if code != 200 {
		return 0, makeError(m)
	}
	return strconv.Atoi(string(m["count"]))
}

// Remove deletes the data at key in the database.
func (c *Conn) Remove(key string) error {
	vals := map[string][]byte{
		"key": []byte(key),
	}
	code, m, err := c.doRPC("/rpc/remove", vals)
	if err != nil {
		return err
	}
	if code != 200 {
		return makeError(m)
	}
	return nil
}

// GetBulk retrieves the keys in the map. The results will be filled in on function return.
// If a key was not found in the database, it will be removed from the map.
func (c *Conn) GetBulk(keysAndVals map[string]string) error {
	m := make(map[string][]byte)
	for k := range keysAndVals {
		m[k] = zeroslice
	}
	err := c.GetBulkBytes(m)
	if err != nil {
		return err
	}
	for k := range keysAndVals {
		b, ok := m[k]
		if ok {
			keysAndVals[k] = string(b)
		} else {
			delete(keysAndVals, k)
		}
	}
	return nil
}

// Get retrieves the data stored at key. ErrNotFound is
// returned if no such data exists
func (c *Conn) Get(key string) (string, error) {
	s, err := c.GetBytes(key)
	if err != nil {
		return "", err
	}
	return string(s), nil
}

// GetBytes retrieves the data stored at key in the format of a byte slice
// A nil slice and nil error is returned if no data at key exists.
func (c *Conn) GetBytes(key string) ([]byte, error) {
	vals := map[string][]byte{
		"key": []byte(key),
	}
	code, m, err := c.doRPC("/rpc/get", vals)
	if err != nil {
		return nil, err
	}
	switch code {
	case 200:
		break
	case 450:
		return nil, ErrNotFound
	default:
		return nil, makeError(m)
	}
	return m["value"], nil

}

// Set stores the data at key
func (c *Conn) Set(key string, value []byte) error {
	vals := map[string][]byte{
		"key":   []byte(key),
		"value": value,
	}
	code, m, err := c.doRPC("/rpc/set", vals)
	if err != nil {
		return err
	}
	switch code {
	case 200:
		return nil
	default:
		return makeError(m)
	}
}

var zeroslice = []byte("0")

// GetBulkBytes retrieves the keys in the map. The results will be filled in on function return.
// If a key was not found in the database, it will be removed from the map.
func (c *Conn) GetBulkBytes(keys map[string][]byte) error {
	keystransmit := make(map[string][]byte)
	for k, _ := range keys {
		keystransmit["_"+k] = zeroslice
	}
	code, m, err := c.doRPC("/rpc/get_bulk", keystransmit)
	if err != nil {
		return err
	}
	if code != 200 {
		return makeError(m)
	}
	for k := range keys {
		val, ok := m["_"+k]
		if !ok {
			delete(keys, k)
			continue
		}
		keys[k] = val
	}
	return nil
}

// SetBulk stores the values in the map.
func (c *Conn) SetBulk(values map[string]string) (int64, error) {
	vals := make(map[string][]byte)
	for k, v := range values {
		vals["_"+k] = []byte(v)
	}
	code, m, err := c.doRPC("/rpc/set_bulk", vals)
	if err != nil {
		return 0, err
	}
	if code != 200 {
		return 0, makeError(m)
	}
	return strconv.ParseInt(string(m["num"]), 10, 64)
}

// RemoveBulk deletes the values
func (c *Conn) RemoveBulk(keys []string) (int64, error) {
	vals := make(map[string][]byte)
	for _, k := range keys {
		vals["_"+k] = zeroslice
	}
	code, m, err := c.doRPC("/rpc/remove_bulk", vals)
	if err != nil {
		return 0, err
	}
	if code != 200 {
		return 0, makeError(m)
	}
	return strconv.ParseInt(string(m["num"]), 10, 64)
}

// MatchPrefix performs the match_prefix operation against the server
// It returns a sorted list of strings.
// The error may be ErrSuccess in the case that no records were found.
// This is for compatibility with the old gokabinet library.
func (c *Conn) MatchPrefix(key string, maxrecords int64) ([]string, error) {
	keystransmit := map[string][]byte{
		"prefix": []byte(key),
		"max":    []byte(strconv.FormatInt(maxrecords, 10)),
	}
	code, m, err := c.doRPC("/rpc/match_prefix", keystransmit)
	if err != nil {
		return nil, err
	}
	if code != 200 {
		return nil, makeError(m)
	}
	res := make([]string, 0, len(m))
	for k := range m {
		if k[0] == '_' {
			res = append(res, string(k[1:]))
		}
	}
	if len(res) == 0 {
		// yeah, gokabinet was weird here.
		return nil, ErrSuccess
	}
	// kt spits the prefixes out in sorted order
	// so do that. Users depend on the order.
	sort.StringSlice(res).Sort()
	return res, nil
}

// prefabHeader is the header that rpc request share.
var prefabHeader = makeprefab()

func makeprefab() http.Header {
	r := make(http.Header)
	r.Set("Content-Type", "text/tab-separated-values; colenc=B")
	return r
}

// Do an RPC call against the KT endpoint.
func (c *Conn) doRPC(path string, values map[string][]byte) (code int, vals map[string][]byte, err error) {
	url := &url.URL{
		Scheme: "http",
		Host:   c.host,
		Path:   path,
	}
	req := &http.Request{
		Method: "POST",
		URL:    url,
		Header: prefabHeader,
	}
	body := tsvEncode(values)

	bodyReader := bytes.NewBuffer(body)
	req.Body = ioutil.NopCloser(bodyReader)
	req.ContentLength = int64(len(body))
	t := time.Now()
	resp, err := c.transport.RoundTrip(req)
	if err != nil {
		return 0, nil, err
	}
	dur := time.Since(t)
	timeout := c.timeout - dur
	if timeout < 0 {
		return 0, nil, ErrTimeout
	}
	resultBody, err := timeoutRead(resp.Body, timeout)
	if err != nil {
		return 0, nil, err
	}
	m := decodeValues(resultBody, resp.Header.Get("Content-Type"))
	return resp.StatusCode, m, nil
}

// Encode the request body in base64 encoded TSV
func tsvEncode(values map[string][]byte) []byte {
	var bufsize int
	for k, v := range values {
		// length of key
		bufsize += base64.StdEncoding.EncodedLen(len(k))
		// tab
		bufsize += 1
		// value
		bufsize += base64.StdEncoding.EncodedLen(len(v))
		// newline
		bufsize += 1
	}
	buf := make([]byte, bufsize)
	var n int
	for k, v := range values {
		base64.StdEncoding.Encode(buf[n:], []byte(k))
		n += base64.StdEncoding.EncodedLen(len(k))
		buf[n] = '\t'
		n++
		base64.StdEncoding.Encode(buf[n:], v)
		n += base64.StdEncoding.EncodedLen(len(v))
		buf[n] = '\n'
		n++
	}
	return buf
}

func timeoutRead(body io.ReadCloser, timeout time.Duration) ([]byte, error) {
	type readResult struct {
		buf []byte
		err error
	}
	ch := make(chan readResult, 1)
	go func() {
		buf, err := ioutil.ReadAll(body)
		ch <- readResult{buf, err}
	}()
	select {
	case <-time.After(timeout):
		body.Close()
		return nil, ErrTimeout
	case res := <-ch:
		body.Close()
		return res.buf, res.err
	}
}

// decodeValues takes a response from an KT RPC call and turns it into the
// values that it returned.
//
// KT can return values in 3 different formats, Tab separated values (TSV) without any field encoding,
// TSV with fields base64 encoded or TSV with URL encoding.
// KT does not give you any option as to the format that it returns, so we have to implement all of them
func decodeValues(buf []byte, contenttype string) map[string][]byte {
	if len(buf) == 0 {
		return nil
	}
	// Ideally, we should parse the mime media type here,
	// but this is an expensive operation because mime is just
	// that awful.
	//
	// KT responses are pretty simple and we can rely
	// on it putting the parameter of colenc=[BU] at
	// the end of the string. Just look for B, U or S
	// (last character of tab-separated-values)
	// to figure out which field encoding is used.
	var decodef decodefunc
	switch contenttype[len(contenttype)-1] {
	case 'B':
		decodef = base64Decode
	case 'U':
		decodef = urlDecode
	case 's':
		decodef = identityDecode
	default:
		panic("kt responded with unknown content-type: " + contenttype)
	}

	kv := make(map[string][]byte)
	b := bytes.NewBuffer(buf)
	for {
		key, err := b.ReadBytes('\t')
		if err != nil {
			return kv
		}
		key = decodef(key[:len(key)-1])
		value, err := b.ReadBytes('\n')
		if len(value) > 0 {
			fieldlen := len(value) - 1
			if value[len(value)-1] != '\n' {
				fieldlen = len(value)
			}
			value = decodef(value[:fieldlen])
			kv[string(key)] = value
		}
		if err != nil {
			return kv
		}
	}
}

// decodefunc takes a byte slice and decodes the
// value in place. It returns a slice pointing into
// the original byte slice. It is used for decoding the
// individual fields of the TSV that kt returns
type decodefunc func([]byte) []byte

// Don't do anything, this is pure TSV
func identityDecode(b []byte) []byte {
	return b
}

// Base64 decode each of the field
func base64Decode(b []byte) []byte {
	n, _ := base64.StdEncoding.Decode(b, b)
	return b[:n]
}

// Decode % escaped URL format
func urlDecode(b []byte) []byte {
	res := b
	resi := 0
	for i := 0; i < len(b); i++ {
		if b[i] != '%' {
			res[resi] = b[i]
			resi++
			continue
		}
		res[resi] = unhex(b[i+1])<<4 | unhex(b[i+2])
		resi++
		i += 2
	}
	return res[:resi]
}

// copied from net/url
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

// TODO: make this return errors that can be introspected more easily
// and make it trim components of the error to filter out unused information.
func makeError(m map[string][]byte) error {
	return errors.New(string(m["ERROR"]))
}
<|endoftext|>