<commit_before>package soft

import (
	"image"
	"image/draw"
	"math"
	"runtime"
	"sync"
)

type Context struct {
	Width        int
	Height       int
	ColorBuffer  *image.NRGBA
	DepthBuffer  []float64
	screenMatrix Matrix
	locks        []sync.Mutex
}

func NewContext(width, height int) *Context {
	dc := &Context{}
	dc.Width = width
	dc.Height = height
	dc.ColorBuffer = image.NewNRGBA(image.Rect(0, 0, width, height))
	dc.DepthBuffer = make([]float64, width*height)
	dc.screenMatrix = Scale(V(1, -1, 1)).Translate(V(1, 1, 0)).Scale(V(float64(width)/2, float64(height)/2, 1))
	dc.locks = make([]sync.Mutex, 128)
	dc.ClearDepthBuffer()
	return dc
}

func (dc *Context) Image() image.Image {
	return dc.ColorBuffer
}

func (dc *Context) ClearColorBuffer(color Vector) {
	im := dc.ColorBuffer
	src := image.NewUniform(color.NRGBA())
	draw.Draw(im, im.Bounds(), src, image.ZP, draw.Src)
}

func (dc *Context) ClearDepthBuffer() {
	for i := range dc.DepthBuffer {
		dc.DepthBuffer[i] = math.MaxFloat64
	}
}

func (dc *Context) rasterize(v1, v2, v3 Vertex, s1, s2, s3 Vector, shader Shader) {
	min := s1.Min(s2.Min(s3)).Floor()
	max := s1.Max(s2.Max(s3)).Ceil()
	x1 := int(min.X)
	x2 := int(max.X)
	y1 := int(min.Y)
	y2 := int(max.Y)
	d0 := s2.Sub(s1)
	d1 := s3.Sub(s1)
	d00 := d0.X*d0.X + d0.Y*d0.Y
	d01 := d0.X*d1.X + d0.Y*d1.Y
	d11 := d1.X*d1.X + d1.Y*d1.Y
	lock := &dc.locks[(x1+y1)%len(dc.locks)]
	for y := y1; y <= y2; y++ {
		for x := x1; x <= x2; x++ {
			p := Vector{float64(x) + 0.5, float64(y) + 0.5, 0}
			d2 := p.Sub(s1)
			d20 := d2.X*d0.X + d2.Y*d0.Y
			d21 := d2.X*d1.X + d2.Y*d1.Y
			d := d00*d11 - d01*d01
			if d > -0.001 && d < 0.001 {
				continue
			}
			by := (d11*d20 - d01*d21) / d
			if by < 0 {
				continue
			}
			bz := (d00*d21 - d01*d20) / d
			if bz < 0 {
				continue
			}
			bx := 1 - by - bz
			if bx < 0 {
				continue
			}
			z := bx*s1.Z + by*s2.Z + bz*s3.Z
			i := y*dc.Width + x
			if z >= dc.DepthBuffer[i] { // completely safe?
				continue
			}
			b := Vector{bx, by, bz}
			v := InterpolateVertexes(v1, v2, v3, b)
			color := shader.Fragment(v)
			if color == Discard {
				continue
			}
			c := color.NRGBA()
			lock.Lock()
			if z < dc.DepthBuffer[i] {
				dc.DepthBuffer[i] = z
				dc.ColorBuffer.SetNRGBA(x, y, c)
			}
			lock.Unlock()
		}
	}
}

func (dc *Context) drawClipped(v1, v2, v3 Vertex, shader Shader) {
	ndc1 := v1.Output.DivScalar(v1.Output.W).Vector()
	ndc2 := v2.Output.DivScalar(v2.Output.W).Vector()
	ndc3 := v3.Output.DivScalar(v3.Output.W).Vector()

	// back face culling
	var sum float64
	sum += (ndc2.X - ndc1.X) * (ndc2.Y + ndc1.Y)
	sum += (ndc3.X - ndc2.X) * (ndc3.Y + ndc2.Y)
	sum += (ndc1.X - ndc3.X) * (ndc1.Y + ndc3.Y)
	if sum >= 0 {
		return
	}

	s1 := dc.screenMatrix.MulPosition(ndc1)
	s2 := dc.screenMatrix.MulPosition(ndc2)
	s3 := dc.screenMatrix.MulPosition(ndc3)
	dc.rasterize(v1, v2, v3, s1, s2, s3, shader)
}

func (dc *Context) DrawTriangle(t *Triangle, shader Shader) {
	v1 := shader.Vertex(t.V1)
	v2 := shader.Vertex(t.V2)
	v3 := shader.Vertex(t.V3)
	if v1.Outside() || v2.Outside() || v3.Outside() {
		triangles := ClipTriangle(NewTriangle(v1, v2, v3))
		for _, t := range triangles {
			dc.drawClipped(t.V1, t.V2, t.V3, shader)
		}
	} else {
		dc.drawClipped(v1, v2, v3, shader)
	}
}

func (dc *Context) DrawTriangles(triangles []*Triangle, shader Shader) {
	wn := runtime.NumCPU()
	done := make(chan bool, wn)
	for wi := 0; wi < wn; wi++ {
		go func(wi int) {
			for i, t := range triangles {
				if i%wn == wi {
					dc.DrawTriangle(t, shader)
				}
			}
			done <- true
		}(wi)
	}
	for wi := 0; wi < wn; wi++ {
		<-done
	}
}

func (dc *Context) DrawMesh(mesh *Mesh, shader Shader) {
	dc.DrawTriangles(mesh.Triangles, shader)
}
<commit_msg>bug fix<commit_after>package soft

import (
	"image"
	"image/draw"
	"math"
	"runtime"
	"sync"
)

type Context struct {
	Width        int
	Height       int
	ColorBuffer  *image.NRGBA
	DepthBuffer  []float64
	screenMatrix Matrix
	locks        []sync.Mutex
}

func NewContext(width, height int) *Context {
	dc := &Context{}
	dc.Width = width
	dc.Height = height
	dc.ColorBuffer = image.NewNRGBA(image.Rect(0, 0, width, height))
	dc.DepthBuffer = make([]float64, width*height)
	dc.screenMatrix = Scale(V(1, -1, 1)).Translate(V(1, 1, 0)).Scale(V(float64(width)/2, float64(height)/2, 1))
	dc.locks = make([]sync.Mutex, 128)
	dc.ClearDepthBuffer()
	return dc
}

func (dc *Context) Image() image.Image {
	return dc.ColorBuffer
}

func (dc *Context) ClearColorBuffer(color Vector) {
	im := dc.ColorBuffer
	src := image.NewUniform(color.NRGBA())
	draw.Draw(im, im.Bounds(), src, image.ZP, draw.Src)
}

func (dc *Context) ClearDepthBuffer() {
	for i := range dc.DepthBuffer {
		dc.DepthBuffer[i] = math.MaxFloat64
	}
}

func (dc *Context) rasterize(v1, v2, v3 Vertex, s1, s2, s3 Vector, shader Shader) {
	min := s1.Min(s2.Min(s3)).Floor()
	max := s1.Max(s2.Max(s3)).Ceil()
	x1 := int(min.X)
	x2 := int(max.X)
	y1 := int(min.Y)
	y2 := int(max.Y)
	d0 := s2.Sub(s1)
	d1 := s3.Sub(s1)
	d00 := d0.X*d0.X + d0.Y*d0.Y
	d01 := d0.X*d1.X + d0.Y*d1.Y
	d11 := d1.X*d1.X + d1.Y*d1.Y
	for y := y1; y <= y2; y++ {
		for x := x1; x <= x2; x++ {
			p := Vector{float64(x) + 0.5, float64(y) + 0.5, 0}
			d2 := p.Sub(s1)
			d20 := d2.X*d0.X + d2.Y*d0.Y
			d21 := d2.X*d1.X + d2.Y*d1.Y
			d := d00*d11 - d01*d01
			if d > -0.001 && d < 0.001 {
				continue
			}
			by := (d11*d20 - d01*d21) / d
			if by < 0 {
				continue
			}
			bz := (d00*d21 - d01*d20) / d
			if bz < 0 {
				continue
			}
			bx := 1 - by - bz
			if bx < 0 {
				continue
			}
			z := bx*s1.Z + by*s2.Z + bz*s3.Z
			i := y*dc.Width + x
			if z >= dc.DepthBuffer[i] { // completely safe?
				continue
			}
			b := Vector{bx, by, bz}
			v := InterpolateVertexes(v1, v2, v3, b)
			color := shader.Fragment(v)
			if color == Discard {
				continue
			}
			c := color.NRGBA()
			lock := &dc.locks[(x+y)%len(dc.locks)]
			lock.Lock()
			if z < dc.DepthBuffer[i] {
				dc.DepthBuffer[i] = z
				dc.ColorBuffer.SetNRGBA(x, y, c)
			}
			lock.Unlock()
		}
	}
}

func (dc *Context) drawClipped(v1, v2, v3 Vertex, shader Shader) {
	ndc1 := v1.Output.DivScalar(v1.Output.W).Vector()
	ndc2 := v2.Output.DivScalar(v2.Output.W).Vector()
	ndc3 := v3.Output.DivScalar(v3.Output.W).Vector()

	// back face culling
	var sum float64
	sum += (ndc2.X - ndc1.X) * (ndc2.Y + ndc1.Y)
	sum += (ndc3.X - ndc2.X) * (ndc3.Y + ndc2.Y)
	sum += (ndc1.X - ndc3.X) * (ndc1.Y + ndc3.Y)
	if sum >= 0 {
		return
	}

	s1 := dc.screenMatrix.MulPosition(ndc1)
	s2 := dc.screenMatrix.MulPosition(ndc2)
	s3 := dc.screenMatrix.MulPosition(ndc3)
	dc.rasterize(v1, v2, v3, s1, s2, s3, shader)
}

func (dc *Context) DrawTriangle(t *Triangle, shader Shader) {
	v1 := shader.Vertex(t.V1)
	v2 := shader.Vertex(t.V2)
	v3 := shader.Vertex(t.V3)
	if v1.Outside() || v2.Outside() || v3.Outside() {
		triangles := ClipTriangle(NewTriangle(v1, v2, v3))
		for _, t := range triangles {
			dc.drawClipped(t.V1, t.V2, t.V3, shader)
		}
	} else {
		dc.drawClipped(v1, v2, v3, shader)
	}
}

func (dc *Context) DrawTriangles(triangles []*Triangle, shader Shader) {
	wn := runtime.NumCPU()
	done := make(chan bool, wn)
	for wi := 0; wi < wn; wi++ {
		go func(wi int) {
			for i, t := range triangles {
				if i%wn == wi {
					dc.DrawTriangle(t, shader)
				}
			}
			done <- true
		}(wi)
	}
	for wi := 0; wi < wn; wi++ {
		<-done
	}
}

func (dc *Context) DrawMesh(mesh *Mesh, shader Shader) {
	dc.DrawTriangles(mesh.Triangles, shader)
}
<|endoftext|>


<commit_before>package logger

import "fmt"
import "os"

const (
	DEBUG   = -1 + iota // start with -1
	DEFAULT // should be 0
	WARN
	ERROR
)

var verbosity int

/*
 Sets the verbosity level. Possible values are DEBUG, DEFAULT, WARN and ERROR.
 DEFAULT is the standard value which will print everything but debug messages.
*/
func SetVerbosityLevel(level int) { verbosity = level }

/*
 Prints debug messages. Same syntax as fmt.Printf.
*/
func Debug(format string, v ...) {
	if verbosity <= DEBUG {
		fmt.Printf("DEBUG: ")
		fmt.Printf(format, v)
	}
}

/*
 Prints a debug message if enabled but without the "DEBUG: " prefix.
 Same syntax as fmt.Printf.
*/
func DebugContinue(format string, v ...) {
	if verbosity <= DEBUG {
		fmt.Printf("       ")
		fmt.Printf(format, v)
	}
}

/*
 Prints an info message if enabled. This is for general feedback about what
 gobuild is currently doing. Same syntax as fmt.Printf.
*/
func Info(format string, v ...) {
	if verbosity <= DEFAULT {
		fmt.Printf(format, v)
	}
}

/*
 Prints a warning if warnings are enabled. Same syntax as fmt.Printf.
*/
func Warn(format string, v ...) {
	if verbosity <= WARN {
		fmt.Print("WARNING: ")
		fmt.Printf(format, v)
	}
}

/*
 Prints a warning message if enabled but without the "WARNING: " prefix.
 Same syntax as fmt.Printf.
*/
func WarnContinue(format string, v ...) {
	if verbosity <= WARN {
		fmt.Print("         ")
		fmt.Printf(format, v)
	}
}

/*
 Prints an error message. Same syntax as fmt.Printf.
*/
func Error(format string, v ...) {
	if verbosity <= ERROR {
		fmt.Fprint(os.Stderr, "ERROR: ")
		fmt.Fprintf(os.Stderr, format, v)
	}
}

/*
 Prints an error message but without the "ERROR: " prefix.
 Same syntax as fmt.Printf.
*/
func ErrorContinue(format string, v ...) {
	if verbosity <= ERROR {
		fmt.Fprint(os.Stderr, "       ")
		fmt.Fprintf(os.Stderr, format, v)
	}
}
<commit_msg>fmt.Printf changed, uses "...interface{}" instead of "..."<commit_after>package logger

import "fmt"
import "os"

const (
	DEBUG   = -1 + iota // start with -1
	DEFAULT // should be 0
	WARN
	ERROR
)

var verbosity int

/*
 Sets the verbosity level. Possible values are DEBUG, DEFAULT, WARN and ERROR.
 DEFAULT is the standard value which will print everything but debug messages.
*/
func SetVerbosityLevel(level int) { verbosity = level }

/*
 Prints debug messages. Same syntax as fmt.Printf.
*/
func Debug(format string, v ...interface{}) {
	if verbosity <= DEBUG {
		fmt.Printf("DEBUG: ")
		fmt.Printf(format, v)
	}
}

/*
 Prints a debug message if enabled but without the "DEBUG: " prefix.
 Same syntax as fmt.Printf.
*/
func DebugContinue(format string, v ...interface{}) {
	if verbosity <= DEBUG {
		fmt.Printf("       ")
		fmt.Printf(format, v)
	}
}

/*
 Prints an info message if enabled. This is for general feedback about what
 gobuild is currently doing. Same syntax as fmt.Printf.
*/
func Info(format string, v ...interface{}) {
	if verbosity <= DEFAULT {
		fmt.Printf(format, v)
	}
}

/*
 Prints a warning if warnings are enabled. Same syntax as fmt.Printf.
*/
func Warn(format string, v ...interface{}) {
	if verbosity <= WARN {
		fmt.Print("WARNING: ")
		fmt.Printf(format, v)
	}
}

/*
 Prints a warning message if enabled but without the "WARNING: " prefix.
 Same syntax as fmt.Printf.
*/
func WarnContinue(format string, v ...interface{}) {
	if verbosity <= WARN {
		fmt.Print("         ")
		fmt.Printf(format, v)
	}
}

/*
 Prints an error message. Same syntax as fmt.Printf.
*/
func Error(format string, v ...interface{}) {
	if verbosity <= ERROR {
		fmt.Fprint(os.Stderr, "ERROR: ")
		fmt.Fprintf(os.Stderr, format, v)
	}
}

/*
 Prints an error message but without the "ERROR: " prefix.
 Same syntax as fmt.Printf.
*/
func ErrorContinue(format string, v ...interface{}) {
	if verbosity <= ERROR {
		fmt.Fprint(os.Stderr, "       ")
		fmt.Fprintf(os.Stderr, format, v)
	}
}
<|endoftext|>


<commit_before>// Package logger provides functions to retrieve and set loggers on a context
package logger

import (
	"io"
	"io/ioutil"

	"github.com/Sirupsen/logrus"
	"golang.org/x/net/context"
	"regexp"
)

var (
	// Key is used for contexts to retrieve and store loggers
	Key = "logger"

	// LogFormatter is the logrus formatting function
	// It defaults to logrus.JSONFormatter.
	Formatter logrus.Formatter = new(logrus.JSONFormatter)

	// LogWriter is where the logs go. By default this empty buffer blackholes
	// logs. It must be an io.Writer such as os.Stdout
	Writer io.Writer = ioutil.Discard

	// regex to format grpc call logs
	// "/service.Service/Endpt" -> "Endpt"
	FormatServiceEndpoint = regexp.MustCompile(`\/([A-Za-z])\w+\.([A-Za-z])\w+\/`)
)

// FromContext takes a context and returns a pointer to a logrus.Entry
// If there is an existing entry on ctx.LoggerKey, that is returned
// Otherwise, a new logger is created and returned that blackholes logs
func FromContext(ctx context.Context) *logrus.Entry {
	entry, ok := ctx.Value(Key).(*logrus.Entry)
	if !ok {

		return logrus.NewEntry(&logrus.Logger{
			Formatter: Formatter,
			Out:       Writer,
			Level:     logrus.InfoLevel,
		})
	}

	return entry
}

// SetEntry replaces ctx.LoggerKey with the given log entry
func SetEntry(ctx context.Context, e *logrus.Entry) context.Context {
	return context.WithValue(ctx, Key, e)
}
<commit_msg>regex format now a const<commit_after>// Package logger provides functions to retrieve and set loggers on a context
package logger

import (
	"io"
	"io/ioutil"

	"github.com/Sirupsen/logrus"
	"golang.org/x/net/context"
	"regexp"
)
const (
	// regex to format grpc call logs
	// "/service.Service/Endpt" -> "Endpt"
	FormatServiceEndpoint = regexp.MustCompile(`\/([A-Za-z])\w+\.([A-Za-z])\w+\/`)
)

var (
	// Key is used for contexts to retrieve and store loggers
	Key = "logger"

	// LogFormatter is the logrus formatting function
	// It defaults to logrus.JSONFormatter.
	Formatter logrus.Formatter = new(logrus.JSONFormatter)

	// LogWriter is where the logs go. By default this empty buffer blackholes
	// logs. It must be an io.Writer such as os.Stdout
	Writer io.Writer = ioutil.Discard
)

// FromContext takes a context and returns a pointer to a logrus.Entry
// If there is an existing entry on ctx.LoggerKey, that is returned
// Otherwise, a new logger is created and returned that blackholes logs
func FromContext(ctx context.Context) *logrus.Entry {
	entry, ok := ctx.Value(Key).(*logrus.Entry)
	if !ok {

		return logrus.NewEntry(&logrus.Logger{
			Formatter: Formatter,
			Out:       Writer,
			Level:     logrus.InfoLevel,
		})
	}

	return entry
}

// SetEntry replaces ctx.LoggerKey with the given log entry
func SetEntry(ctx context.Context, e *logrus.Entry) context.Context {
	return context.WithValue(ctx, Key, e)
}
<|endoftext|>


<commit_before>// Minimal logger shared by everyone
package logger

import (
	"log"
	"os"

	"github.com/sjmudd/ps-top/lib"
)

var (
	enabled bool
	logger  *log.Logger
	logfile string
)

func Disable() bool {
	oldValue := enabled
	enabled = false

	return oldValue
}

// EnableLogging allows me to do this or not
func Enable() bool {
	if enabled {
		return enabled // as nothing to do
	}

	oldValue := enabled

	enabled = true
	logfile = lib.MyName() + ".log"

	file, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		log.Fatal("Failed to open log file", logfile, ":", err)
	}
	logger = log.New(file, "", log.Ldate|log.Ltime)

	return oldValue
}

// Println calls passed downstream if we have a valid logger setup
func Println(v ...interface{}) {
	if logger != nil {
		logger.Println(v)
	}
}

// Fatal calls passed downstream if we have a valid logger setup
func Fatal(v ...interface{}) {
	if logger != nil {
		logger.Fatal(v)
	}
}
<commit_msg>no need for package global variable 'logfile'<commit_after>// Minimal logger shared by everyone
package logger

import (
	"log"
	"os"

	"github.com/sjmudd/ps-top/lib"
)

var (
	enabled bool
	logger  *log.Logger
)

func Disable() bool {
	oldValue := enabled
	enabled = false

	return oldValue
}

// EnableLogging allows me to do this or not
func Enable() bool {
	if enabled {
		return enabled // as nothing to do
	}

	oldValue := enabled

	enabled = true
	logfile := lib.MyName() + ".log"

	file, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		log.Fatal("Failed to open log file", logfile, ":", err)
	}
	logger = log.New(file, "", log.Ldate|log.Ltime)

	return oldValue
}

// Println calls passed downstream if we have a valid logger setup
func Println(v ...interface{}) {
	if logger != nil {
		logger.Println(v)
	}
}

// Fatal calls passed downstream if we have a valid logger setup
func Fatal(v ...interface{}) {
	if logger != nil {
		logger.Fatal(v)
	}
}
<|endoftext|>


<commit_before>// Package livelog implements a webhook handler for serving up livelogs of a task
// sandbox.
package livelog

import (
	"compress/gzip"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	tcclient "github.com/taskcluster/taskcluster-client-go"
	"github.com/taskcluster/taskcluster-worker/plugins"
	"github.com/taskcluster/taskcluster-worker/runtime"
	"github.com/taskcluster/taskcluster-worker/runtime/atomics"
	"github.com/taskcluster/taskcluster-worker/runtime/ioext"
)

type pluginProvider struct {
	plugins.PluginProviderBase
}

type plugin struct {
	plugins.PluginBase
	monitor     runtime.Monitor
	environment *runtime.Environment
}

type taskPlugin struct {
	plugins.TaskPluginBase
	context     *runtime.TaskContext
	url         string
	detach      func()
	log         *logrus.Entry
	environment *runtime.Environment
	expiration  tcclient.Time
	monitor     runtime.Monitor
	uploaded    atomics.Once
	setupDone   sync.WaitGroup
	setupErr    error
}

func (pluginProvider) NewPlugin(options plugins.PluginOptions) (plugins.Plugin, error) {
	debug("Created livelog plugin")
	return plugin{
		monitor:     options.Monitor,
		environment: options.Environment,
	}, nil
}

func (p plugin) NewTaskPlugin(options plugins.TaskPluginOptions) (plugins.TaskPlugin, error) {
	debug("Creating taskPlugin")
	tp := &taskPlugin{
		context:     options.TaskContext,
		monitor:     options.Monitor,
		environment: p.environment,
	}
	tp.setupDone.Add(1)
	go tp.setup()
	return tp, nil
}

func (tp *taskPlugin) setup() {
	defer tp.setupDone.Done()

	if tp.environment.WebHookServer == nil {
		tp.monitor.Info("livelog disabled when WebHookServer isn't provided")
		return
	}

	tp.url, tp.detach = tp.environment.WebHookServer.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet && r.Method != http.MethodHead {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Expose-Headers", "X-Streaming")
		if r.Method == http.MethodHead {
			w.WriteHeader(http.StatusOK)
			return
		}
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")

		// Get an HTTP flusher if supported in the current context, or wrap in
		// a NopFlusher, if flushing isn't available.
		wf, ok := w.(ioext.WriteFlusher)
		if ok {
			w.Header().Set("X-Streaming", "true") // Allow clients to detect that we're streaming
		} else {
			wf = ioext.NopFlusher(w)
		}

		// TODO (garndt): add support for range headers.  Might not be used at all currently
		logReader, err := tp.context.NewLogReader()
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte("Error opening up live log"))
			return
		}
		defer logReader.Close()

		w.WriteHeader(http.StatusOK)
		ioext.CopyAndFlush(wf, logReader, 100*time.Millisecond)
	}))

	err := tp.context.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     "public/logs/live.log",
		Mimetype: "text/plain; charset=utf-8",
		URL:      tp.url,
		Expires:  tp.context.TaskInfo.Expires,
	})
	if err != nil {
		incidentID := tp.monitor.ReportError(err, "Failed to setup live logging")
		tp.context.LogError("Failed to setup livelogging: ", incidentID)
		// This isn't good, but let's not consider it fatal...
		tp.setupErr = runtime.ErrNonFatalInternalError
	}
}

func (tp *taskPlugin) Finished(success bool) error {
	tp.setupDone.Wait()
	var err error
	tp.uploaded.Do(func() {
		err = tp.uploadLog()
	})
	if err == nil {
		return tp.setupErr
	}
	return err
}

func (tp *taskPlugin) Exception(runtime.ExceptionReason) error {
	tp.setupDone.Wait()
	var err error
	tp.uploaded.Do(func() {
		err = tp.uploadLog()
	})
	if err == nil {
		return tp.setupErr
	}
	return err
}

func (tp *taskPlugin) Dispose() error {
	// Detach livelog webhook, if not already done
	if tp.detach != nil {
		tp.detach()
		tp.detach = nil
	}
	return nil
}

func (tp *taskPlugin) uploadLog() error {
	// Detach livelog webhook
	if tp.detach != nil {
		tp.detach()
		tp.detach = nil
	}

	file, err := tp.context.ExtractLog()
	if err != nil {
		return err
	}
	defer file.Close()

	tempFile, err := tp.environment.TemporaryStorage.NewFile()
	if err != nil {
		return err
	}

	defer tempFile.Close()

	zip := gzip.NewWriter(tempFile)
	if _, err = io.Copy(zip, file); err != nil {
		return err
	}

	if err = zip.Close(); err != nil {
		return err
	}

	_, err = tempFile.Seek(0, 0)
	if err != nil {
		return err
	}

	debug("Uploading live_backing.log")
	err = tp.context.UploadS3Artifact(runtime.S3Artifact{
		Name:     "public/logs/live_backing.log",
		Mimetype: "text/plain; charset=utf-8",
		Expires:  tp.context.TaskInfo.Expires,
		Stream:   tempFile,
		AdditionalHeaders: map[string]string{
			"Content-Encoding": "gzip",
		},
	})

	if err != nil {
		return err
	}

	backingURL := fmt.Sprintf("https://queue.taskcluster.net/v1/task/%s/runs/%d/artifacts/public/logs/live_backing.log", tp.context.TaskInfo.TaskID, tp.context.TaskInfo.RunID)
	err = tp.context.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     "public/logs/live.log",
		Mimetype: "text/plain; charset=utf-8",
		URL:      backingURL,
		Expires:  tp.context.TaskInfo.Expires,
	})
	if err != nil {
		tp.monitor.Error(err)
		return err
	}

	return nil
}

func init() {
	plugins.Register("livelog", &pluginProvider{})
}
<commit_msg>Improve livelog error handling<commit_after>// Package livelog implements a webhook handler for serving up livelogs of a task
// sandbox.
package livelog

import (
	"compress/gzip"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/pkg/errors"
	tcclient "github.com/taskcluster/taskcluster-client-go"
	"github.com/taskcluster/taskcluster-worker/plugins"
	"github.com/taskcluster/taskcluster-worker/runtime"
	"github.com/taskcluster/taskcluster-worker/runtime/atomics"
	"github.com/taskcluster/taskcluster-worker/runtime/ioext"
)

type pluginProvider struct {
	plugins.PluginProviderBase
}

type plugin struct {
	plugins.PluginBase
	monitor     runtime.Monitor
	environment *runtime.Environment
}

type taskPlugin struct {
	plugins.TaskPluginBase
	context     *runtime.TaskContext
	url         string
	detach      func()
	log         *logrus.Entry
	environment *runtime.Environment
	expiration  tcclient.Time
	monitor     runtime.Monitor
	uploaded    atomics.Once
	setupDone   sync.WaitGroup
	setupErr    error
}

func (pluginProvider) NewPlugin(options plugins.PluginOptions) (plugins.Plugin, error) {
	debug("Created livelog plugin")
	return plugin{
		monitor:     options.Monitor,
		environment: options.Environment,
	}, nil
}

func (p plugin) NewTaskPlugin(options plugins.TaskPluginOptions) (plugins.TaskPlugin, error) {
	debug("Creating taskPlugin")
	tp := &taskPlugin{
		context:     options.TaskContext,
		monitor:     options.Monitor,
		environment: p.environment,
	}
	tp.setupDone.Add(1)
	go tp.setup()
	return tp, nil
}

func (tp *taskPlugin) setup() {
	defer tp.setupDone.Done()

	if tp.environment.WebHookServer == nil {
		tp.monitor.Info("livelog disabled when WebHookServer isn't provided")
		return
	}

	tp.url, tp.detach = tp.environment.WebHookServer.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet && r.Method != http.MethodHead {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Expose-Headers", "X-Streaming")
		if r.Method == http.MethodHead {
			w.WriteHeader(http.StatusOK)
			return
		}
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")

		// Get an HTTP flusher if supported in the current context, or wrap in
		// a NopFlusher, if flushing isn't available.
		wf, ok := w.(ioext.WriteFlusher)
		if ok {
			w.Header().Set("X-Streaming", "true") // Allow clients to detect that we're streaming
		} else {
			wf = ioext.NopFlusher(w)
		}

		// TODO (garndt): add support for range headers.  Might not be used at all currently
		logReader, err := tp.context.NewLogReader()
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte("Error opening up live log"))
			return
		}
		defer logReader.Close()

		w.WriteHeader(http.StatusOK)
		ioext.CopyAndFlush(wf, logReader, 100*time.Millisecond)
	}))

	err := tp.context.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     "public/logs/live.log",
		Mimetype: "text/plain; charset=utf-8",
		URL:      tp.url,
		Expires:  tp.context.TaskInfo.Expires,
	})
	if err != nil {
		incidentID := tp.monitor.ReportError(err, "Failed to setup live logging")
		tp.context.LogError("Failed to setup livelogging: ", incidentID)
		// This isn't good, but let's not consider it fatal...
		tp.setupErr = runtime.ErrNonFatalInternalError
	}
}

func (tp *taskPlugin) Finished(success bool) error {
	tp.setupDone.Wait()
	var err error
	tp.uploaded.Do(func() {
		err = tp.uploadLog()
	})
	if err == nil {
		return tp.setupErr
	}
	return err
}

func (tp *taskPlugin) Exception(runtime.ExceptionReason) error {
	tp.setupDone.Wait()
	var err error
	tp.uploaded.Do(func() {
		err = tp.uploadLog()
	})
	if err == nil {
		return tp.setupErr
	}
	return err
}

func (tp *taskPlugin) Dispose() error {
	// Detach livelog webhook, if not already done
	if tp.detach != nil {
		tp.detach()
		tp.detach = nil
	}
	return nil
}

func (tp *taskPlugin) uploadLog() error {
	// Detach livelog webhook
	if tp.detach != nil {
		tp.detach()
		tp.detach = nil
	}

	file, err := tp.context.ExtractLog()
	if err != nil {
		return err
	}
	defer file.Close()

	tempFile, err := tp.environment.TemporaryStorage.NewFile()
	if err != nil {
		return err
	}

	defer tempFile.Close()

	zip := gzip.NewWriter(tempFile)
	if _, err = io.Copy(zip, file); err != nil {
		return err
	}

	if err = zip.Close(); err != nil {
		return err
	}

	_, err = tempFile.Seek(0, 0)
	if err != nil {
		return err
	}

	debug("Uploading live_backing.log")
	err = tp.context.UploadS3Artifact(runtime.S3Artifact{
		Name:     "public/logs/live_backing.log",
		Mimetype: "text/plain; charset=utf-8",
		Expires:  tp.context.TaskInfo.Expires,
		Stream:   tempFile,
		AdditionalHeaders: map[string]string{
			"Content-Encoding": "gzip",
		},
	})
	if err != nil {
		err = errors.Wrap(err, "failed to upload live_backing.log")
		tp.monitor.Error(err)
		return err // Upload error isn't fatal
	}

	backingURL := fmt.Sprintf("https://queue.taskcluster.net/v1/task/%s/runs/%d/artifacts/public/logs/live_backing.log", tp.context.TaskInfo.TaskID, tp.context.TaskInfo.RunID)
	err = tp.context.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     "public/logs/live.log",
		Mimetype: "text/plain; charset=utf-8",
		URL:      backingURL,
		Expires:  tp.context.TaskInfo.Expires,
	})
	if err != nil {
		err = errors.Wrap(err, "failed to update live.log")
		tp.monitor.Error(err)
		return runtime.ErrNonFatalInternalError // Upload error isn't fatal
	}

	return nil
}

func init() {
	plugins.Register("livelog", &pluginProvider{})
}
<|endoftext|>


<commit_before>package cmp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/internal"
	"github.com/influxdata/telegraf/plugins/outputs"
)

type Cmp struct {
	ServerKey   string
	ResourceId  string
	CmpInstance string
	Timeout     internal.Duration
	Headers     []string

	client *http.Client
}

var sampleConfig = `
  # Cmp Server Key
  server_key = "my-server-key" # required.
  resource_id = "1234"

  # Cmp Instance URL
  cmp_instance = "https://yourcmpinstance" # required

  # Connection timeout.
  # timeout = "5s"

  headers = ["X-Header1:12345", "X-Header2:23456"]
`

var translateMap = map[string]Translation{
	"cpu-usage.user": {
		Name: "cpu-usage.user",
		Unit: "percent",
	},
	"cpu-usage.system": {
		Name: "cpu-usage.system",
		Unit: "percent",
	},
	"mem-available.percent": {
		Name:       "memory-usage",
		Unit:       "percent",
		Conversion: memory_used_from_available,
	},
	"system-load1": {
		Name: "load-avg.1",
	},
	"system-load5": {
		Name: "load-avg.5",
	},
	"system-load15": {
		Name: "load-avg.15",
	},
	"disk-used.percent": {
		Name: "disk-usage",
	},
	//     "system-uptime": {
	//         Name: "uptime",
	//     },
	"docker_cpu-usage.percent": {
		Name: "docker-cpu-usage.system",
		Unit: "percent",
	},
	"docker_mem-usage.percent": {
		Name: "docker-memory-usage",
		Unit: "percent",
	},
}

type Translation struct {
	Name       string
	Unit       string
	Conversion func(interface{}) interface{}
}

func memory_used_from_available(available interface{}) interface{} {
	return (100.0 - available.(float64))
}

type CmpData struct {
	ResourceId string      `json:"resource_id"`
	Metrics    []CmpMetric `json:"metrics"`
}

type CmpMetric struct {
	Metric string      `json:"metric"`
	Unit   string      `json:"unit"`
	Value  interface{} `json:"value"`
}

func (data *CmpData) AddMetric(item CmpMetric) []CmpMetric {
	data.Metrics = append(data.Metrics, item)
	return data.Metrics
}

func (a *Cmp) Connect() error {
	if a.ServerKey == "" || a.CmpInstance == "" || a.ResourceId == "" {
		return fmt.Errorf("server_key, resource_id and cmp_instance are required fields for cmp output")
	}
	a.client = &http.Client{
		Timeout: a.Timeout.Duration,
	}
	return nil
}

func (a *Cmp) Write(metrics []telegraf.Metric) error {
	if len(metrics) == 0 {
		return nil
	}
	cmp_data := &CmpData{
		ResourceId: a.ResourceId,
	}

	for _, m := range metrics {

		suffix := ""
		cpu := m.Tags()["cpu"]
		path := m.Tags()["path"]
		container_name := m.Tags()["cont_name"]

		if len(cpu) > 0 && cpu != "cpu-total" {
			suffix = cpu[3:]
		} else if len(path) > 0 {
			suffix = path
		} else if len(container_name) > 0 {
			suffix = container_name
		}

		for k, v := range m.Fields() {
			metric_name := m.Name() + "-" + strings.Replace(k, "_", ".", -1)
			translation, found := translateMap[metric_name]
			if found {
				cmp_name := translation.Name
				if len(suffix) > 0 {
					cmp_name += "." + suffix
				}

				conversion := translation.Conversion
				if conversion != nil {
					v = conversion(v)
				}

				cmp_data.AddMetric(CmpMetric{
					Metric: cmp_name,
					Unit:   translation.Unit,
					Value:  v,
				})
			}
		}
	}

	cmp_bytes, err := json.Marshal(cmp_data)
	if err != nil {
		return fmt.Errorf("unable to marshal TimeSeries, %s\n", err.Error())
	}
	req, err := http.NewRequest("POST", a.authenticatedUrl(), bytes.NewBuffer(cmp_bytes))
	if err != nil {
		return fmt.Errorf("unable to create http.Request, %s\n", err.Error())
	}
	req.Header.Add("Content-Type", "application/json")

	for _, header := range a.Headers {
		s := strings.Split(header, ":")
		req.Header.Add(s[0], s[1])
	}

	resp, err := a.client.Do(req)
	if err != nil {
		return fmt.Errorf("error POSTing metrics, %s\n", err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode > 209 {
		return fmt.Errorf("received bad status code, %d\n", resp.StatusCode)
	}

	return nil
}

func (a *Cmp) SampleConfig() string {
	return sampleConfig
}

func (a *Cmp) Description() string {
	return "Configuration for Cmp Server to send metrics to."
}

func (a *Cmp) authenticatedUrl() string {

	return fmt.Sprintf("%s/metrics", a.CmpInstance)
}

func (a *Cmp) Close() error {
	return nil
}

func init() {
	outputs.Add("cmp", func() telegraf.Output {
		return &Cmp{}
	})
}
<commit_msg>Add monitoring_system and time to cmp metrics output<commit_after>package cmp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/internal"
	"github.com/influxdata/telegraf/plugins/outputs"
)

type Cmp struct {
	ServerKey   string
	ResourceId  string
	CmpInstance string
	Timeout     internal.Duration
	Headers     []string

	client *http.Client
}

var sampleConfig = `
  # Cmp Server Key
  server_key = "my-server-key" # required.
  resource_id = "1234"

  # Cmp Instance URL
  cmp_instance = "https://yourcmpinstance" # required

  # Connection timeout.
  # timeout = "5s"

  headers = ["X-Header1:12345", "X-Header2:23456"]
`

var translateMap = map[string]Translation{
	"cpu-usage.user": {
		Name: "cpu-usage.user",
		Unit: "percent",
	},
	"cpu-usage.system": {
		Name: "cpu-usage.system",
		Unit: "percent",
	},
	"mem-available.percent": {
		Name:       "memory-usage",
		Unit:       "percent",
		Conversion: memory_used_from_available,
	},
	"system-load1": {
		Name: "load-avg.1",
	},
	"system-load5": {
		Name: "load-avg.5",
	},
	"system-load15": {
		Name: "load-avg.15",
	},
	"disk-used.percent": {
		Name: "disk-usage",
	},
	//     "system-uptime": {
	//         Name: "uptime",
	//     },
	"docker_cpu-usage.percent": {
		Name: "docker-cpu-usage.system",
		Unit: "percent",
	},
	"docker_mem-usage.percent": {
		Name: "docker-memory-usage",
		Unit: "percent",
	},
}

type Translation struct {
	Name       string
	Unit       string
	Conversion func(interface{}) interface{}
}

func memory_used_from_available(available interface{}) interface{} {
	return (100.0 - available.(float64))
}

type CmpData struct {
	MonitoringSystem string      `json:"monitoring_system"`
	ResourceId       string      `json:"resource_id"`
	Metrics          []CmpMetric `json:"metrics"`
}

type CmpMetric struct {
	Metric string    `json:"metric"`
	Unit   string    `json:"unit"`
	Value  float64   `json:"value"`
	Time   time.Time `json:"time"`
}

func (data *CmpData) AddMetric(item CmpMetric) []CmpMetric {
	data.Metrics = append(data.Metrics, item)
	return data.Metrics
}

func (a *Cmp) Connect() error {
	if a.ServerKey == "" || a.CmpInstance == "" || a.ResourceId == "" {
		return fmt.Errorf("server_key, resource_id and cmp_instance are required fields for cmp output")
	}
	a.client = &http.Client{
		Timeout: a.Timeout.Duration,
	}
	return nil
}

func (a *Cmp) Write(metrics []telegraf.Metric) error {
	if len(metrics) == 0 {
		return nil
	}
	cmp_data := &CmpData{
		MonitoringSystem: "telegraf",
		ResourceId:       a.ResourceId,
	}

	for _, m := range metrics {

		suffix := ""
		cpu := m.Tags()["cpu"]
		path := m.Tags()["path"]
		container_name := m.Tags()["cont_name"]

		if len(cpu) > 0 && cpu != "cpu-total" {
			suffix = cpu[3:]
		} else if len(path) > 0 {
			suffix = path
		} else if len(container_name) > 0 {
			suffix = container_name
		}

		timestamp := m.Time()
		for k, v := range m.Fields() {
			metric_name := m.Name() + "-" + strings.Replace(k, "_", ".", -1)
			translation, found := translateMap[metric_name]
			if found {
				cmp_name := translation.Name
				if len(suffix) > 0 {
					cmp_name += "." + suffix
				}

				conversion := translation.Conversion
				if conversion != nil {
					v = conversion(v)
				}

				cmp_data.AddMetric(CmpMetric{
					Metric: cmp_name,
					Unit:   translation.Unit,
					Value:  v.(float64),
					Time:   timestamp,
				})
			}
		}
	}

	cmp_bytes, err := json.Marshal(cmp_data)
	if err != nil {
		return fmt.Errorf("unable to marshal TimeSeries, %s\n", err.Error())
	}
	req, err := http.NewRequest("POST", a.authenticatedUrl(), bytes.NewBuffer(cmp_bytes))
	if err != nil {
		return fmt.Errorf("unable to create http.Request, %s\n", err.Error())
	}
	req.Header.Add("Content-Type", "application/json")

	for _, header := range a.Headers {
		s := strings.Split(header, ":")
		req.Header.Add(s[0], s[1])
	}

	resp, err := a.client.Do(req)
	if err != nil {
		return fmt.Errorf("error POSTing metrics, %s\n", err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode > 209 {
		return fmt.Errorf("received bad status code, %d\n", resp.StatusCode)
	}

	return nil
}

func (a *Cmp) SampleConfig() string {
	return sampleConfig
}

func (a *Cmp) Description() string {
	return "Configuration for Cmp Server to send metrics to."
}

func (a *Cmp) authenticatedUrl() string {

	return fmt.Sprintf("%s/metrics", a.CmpInstance)
}

func (a *Cmp) Close() error {
	return nil
}

func init() {
	outputs.Add("cmp", func() telegraf.Output {
		return &Cmp{}
	})
}
<|endoftext|>


<commit_before>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package policy

import (
	"errors"
	"net"
	"regexp"
	"strings"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/net/publicsuffix"
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
	"github.com/letsencrypt/boulder/core"
	blog "github.com/letsencrypt/boulder/log"
)

// PolicyAuthorityImpl enforces CA policy decisions.
type PolicyAuthorityImpl struct {
	log *blog.AuditLogger
	DB  *PolicyAuthorityDatabaseImpl

	EnforceWhitelist    bool
	supportedChallenges map[string]bool
}

// NewPolicyAuthorityImpl constructs a Policy Authority.
func NewPolicyAuthorityImpl(dbMap *gorp.DbMap, enforceWhitelist bool, challengeTypes []string) (*PolicyAuthorityImpl, error) {
	logger := blog.GetAuditLogger()
	logger.Notice("Policy Authority Starting")

	// Setup policy db
	padb, err := NewPolicyAuthorityDatabaseImpl(dbMap)
	if err != nil {
		return nil, err
	}
	pa := PolicyAuthorityImpl{
		log:              logger,
		DB:               padb,
		EnforceWhitelist: enforceWhitelist,
	}

	// Take note of which challenges to offer
	pa.supportedChallenges = map[string]bool{}
	for _, challengeType := range challengeTypes {
		pa.supportedChallenges[challengeType] = true
	}

	return &pa, nil
}

const (
	maxLabels = 10

	// DNS defines max label length as 63 characters. Some implementations allow
	// more, but we will be conservative.
	maxLabelLength = 63

	// This is based off maxLabels * maxLabelLength, but is also a restriction based
	// on the max size of indexed storage in the issuedNames table.
	maxDNSIdentifierLength = 640

	// whitelistedPartnerRegID is the registartion ID we check for to see if we need
	// to skip the domain whitelist (but not the blacklist). This is for an
	// early partner integration during the beta period and should be removed
	// later.
	whitelistedPartnerRegID = 131
)

var dnsLabelRegexp = regexp.MustCompile("^[a-z0-9][a-z0-9-]{0,62}$")
var punycodeRegexp = regexp.MustCompile("^xn--")

func isDNSCharacter(ch byte) bool {
	return ('a' <= ch && ch <= 'z') ||
		('A' <= ch && ch <= 'Z') ||
		('0' <= ch && ch <= '9') ||
		ch == '.' || ch == '-'
}

// Test whether the domain name indicated by the label set is a label-wise
// suffix match for the provided suffix set.  If the `properSuffix` flag is
// set, then the name is required to not be in the suffix set (i.e., it must
// have at least one label beyond any suffix in the set).
func suffixMatch(labels []string, suffixSet map[string]bool, properSuffix bool) bool {
	for i := range labels {
		if domain := strings.Join(labels[i:], "."); suffixSet[domain] {
			// If we match on the whole domain, gate on properSuffix
			return !properSuffix || (i > 0)
		}
	}
	return false
}

// InvalidIdentifierError indicates that we didn't understand the IdentifierType
// provided.
type InvalidIdentifierError struct{}

// SyntaxError indicates that the user input was not well formatted.
type SyntaxError struct{}

// NonPublicError indicates that one or more identifiers were not on the public
// Internet.
type NonPublicError struct{}

// ErrICANNTLD indicates that one or more identifiers was an ICANN-managed TLD
var ErrICANNTLD = errors.New("Name is an ICANN TLD")

// ErrBlacklisted indicates we have blacklisted one or more of these
// identifiers.
var ErrBlacklisted = errors.New("Name is blacklisted")

// ErrNotWhitelisted indicates we have not whitelisted one or more of these
// identifiers.
var ErrNotWhitelisted = errors.New("Name is not whitelisted")

func (e InvalidIdentifierError) Error() string { return "Invalid identifier type" }
func (e SyntaxError) Error() string            { return "Syntax error" }
func (e NonPublicError) Error() string         { return "Name does not end in a public suffix" }

// WillingToIssue determines whether the CA is willing to issue for the provided
// identifier. It expects domains in id to be lowercase to prevent mismatched
// cases breaking queries.
//
// We place several criteria on identifiers we are willing to issue for:
//
//  * MUST self-identify as DNS identifiers
//  * MUST contain only bytes in the DNS hostname character set
//  * MUST NOT have more than maxLabels labels
//  * MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
//    In particular:
//    * MUST NOT contain underscores
//  * MUST NOT contain IDN labels (xn--)
//  * MUST NOT match the syntax of an IP address
//  * MUST end in a public suffix
//  * MUST have at least one label in addition to the public suffix
//  * MUST NOT be a label-wise suffix match for a name on the black list,
//    where comparison is case-independent (normalized to lower case)
//
// XXX: Is there any need for this method to be constant-time?  We're
//      going to refuse to issue anyway, but timing could leak whether
//      names are on the blacklist.
func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64) error {
	if id.Type != core.IdentifierDNS {
		return InvalidIdentifierError{}
	}
	domain := id.Value

	for _, ch := range []byte(domain) {
		if !isDNSCharacter(ch) {
			return SyntaxError{}
		}
	}

	if len(domain) > 255 {
		return SyntaxError{}
	}

	if ip := net.ParseIP(domain); ip != nil {
		return SyntaxError{}
	}

	labels := strings.Split(domain, ".")
	if len(labels) > maxLabels || len(labels) < 2 {
		return SyntaxError{}
	}
	for _, label := range labels {
		if len(label) < 1 || len(label) > maxLabelLength {
			return SyntaxError{}
		}

		if !dnsLabelRegexp.MatchString(label) {
			return SyntaxError{}
		}

		if punycodeRegexp.MatchString(label) {
			return SyntaxError{}
		}
	}

	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
	icannTLD, err := publicsuffix.ICANNTLD(domain)
	if err != nil {
		return NonPublicError{}
	}
	if icannTLD == domain {
		return ErrICANNTLD
	}

	// Use the domain whitelist if the PA has been asked to. However, if the
	// registration ID is from a whitelisted partner we're allowing to register
	// any domain, they can get in, too.
	enforceWhitelist := pa.EnforceWhitelist
	if regID == whitelistedPartnerRegID {
		enforceWhitelist = false
	}

	// Require no match against blacklist and if enforceWhitelist is true
	// require domain to match a whitelist rule.
	if err := pa.DB.CheckHostLists(domain, enforceWhitelist); err != nil {
		return err
	}

	return nil
}

// ChallengesFor makes a decision of what challenges, and combinations, are
// acceptable for the given identifier.
//
// Note: Current implementation is static, but future versions may not be.
func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) {
	challenges = []core.Challenge{}
	combinations = [][]int{}

	// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
	if pa.supportedChallenges[core.ChallengeTypeSimpleHTTP] {
		challenges = append(challenges, core.SimpleHTTPChallenge(accountKey))
	}

	// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
	if pa.supportedChallenges[core.ChallengeTypeDVSNI] {
		challenges = append(challenges, core.SimpleHTTPChallenge(accountKey))
	}

	if pa.supportedChallenges[core.ChallengeTypeHTTP01] {
		challenges = append(challenges, core.HTTPChallenge01(accountKey))
	}

	if pa.supportedChallenges[core.ChallengeTypeTLSSNI01] {
		challenges = append(challenges, core.TLSSNIChallenge01(accountKey))
	}

	if pa.supportedChallenges[core.ChallengeTypeDNS01] {
		challenges = append(challenges, core.TLSSNIChallenge01(accountKey))
	}

	combinations = make([][]int, len(challenges))
	for i := range combinations {
		combinations[i] = []int{i}
	}
	return
}
<commit_msg>Further copy/paste errors<commit_after>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package policy

import (
	"errors"
	"net"
	"regexp"
	"strings"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/net/publicsuffix"
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
	"github.com/letsencrypt/boulder/core"
	blog "github.com/letsencrypt/boulder/log"
)

// PolicyAuthorityImpl enforces CA policy decisions.
type PolicyAuthorityImpl struct {
	log *blog.AuditLogger
	DB  *PolicyAuthorityDatabaseImpl

	EnforceWhitelist    bool
	supportedChallenges map[string]bool
}

// NewPolicyAuthorityImpl constructs a Policy Authority.
func NewPolicyAuthorityImpl(dbMap *gorp.DbMap, enforceWhitelist bool, challengeTypes []string) (*PolicyAuthorityImpl, error) {
	logger := blog.GetAuditLogger()
	logger.Notice("Policy Authority Starting")

	// Setup policy db
	padb, err := NewPolicyAuthorityDatabaseImpl(dbMap)
	if err != nil {
		return nil, err
	}
	pa := PolicyAuthorityImpl{
		log:              logger,
		DB:               padb,
		EnforceWhitelist: enforceWhitelist,
	}

	// Take note of which challenges to offer
	pa.supportedChallenges = map[string]bool{}
	for _, challengeType := range challengeTypes {
		pa.supportedChallenges[challengeType] = true
	}

	return &pa, nil
}

const (
	maxLabels = 10

	// DNS defines max label length as 63 characters. Some implementations allow
	// more, but we will be conservative.
	maxLabelLength = 63

	// This is based off maxLabels * maxLabelLength, but is also a restriction based
	// on the max size of indexed storage in the issuedNames table.
	maxDNSIdentifierLength = 640

	// whitelistedPartnerRegID is the registartion ID we check for to see if we need
	// to skip the domain whitelist (but not the blacklist). This is for an
	// early partner integration during the beta period and should be removed
	// later.
	whitelistedPartnerRegID = 131
)

var dnsLabelRegexp = regexp.MustCompile("^[a-z0-9][a-z0-9-]{0,62}$")
var punycodeRegexp = regexp.MustCompile("^xn--")

func isDNSCharacter(ch byte) bool {
	return ('a' <= ch && ch <= 'z') ||
		('A' <= ch && ch <= 'Z') ||
		('0' <= ch && ch <= '9') ||
		ch == '.' || ch == '-'
}

// Test whether the domain name indicated by the label set is a label-wise
// suffix match for the provided suffix set.  If the `properSuffix` flag is
// set, then the name is required to not be in the suffix set (i.e., it must
// have at least one label beyond any suffix in the set).
func suffixMatch(labels []string, suffixSet map[string]bool, properSuffix bool) bool {
	for i := range labels {
		if domain := strings.Join(labels[i:], "."); suffixSet[domain] {
			// If we match on the whole domain, gate on properSuffix
			return !properSuffix || (i > 0)
		}
	}
	return false
}

// InvalidIdentifierError indicates that we didn't understand the IdentifierType
// provided.
type InvalidIdentifierError struct{}

// SyntaxError indicates that the user input was not well formatted.
type SyntaxError struct{}

// NonPublicError indicates that one or more identifiers were not on the public
// Internet.
type NonPublicError struct{}

// ErrICANNTLD indicates that one or more identifiers was an ICANN-managed TLD
var ErrICANNTLD = errors.New("Name is an ICANN TLD")

// ErrBlacklisted indicates we have blacklisted one or more of these
// identifiers.
var ErrBlacklisted = errors.New("Name is blacklisted")

// ErrNotWhitelisted indicates we have not whitelisted one or more of these
// identifiers.
var ErrNotWhitelisted = errors.New("Name is not whitelisted")

func (e InvalidIdentifierError) Error() string { return "Invalid identifier type" }
func (e SyntaxError) Error() string            { return "Syntax error" }
func (e NonPublicError) Error() string         { return "Name does not end in a public suffix" }

// WillingToIssue determines whether the CA is willing to issue for the provided
// identifier. It expects domains in id to be lowercase to prevent mismatched
// cases breaking queries.
//
// We place several criteria on identifiers we are willing to issue for:
//
//  * MUST self-identify as DNS identifiers
//  * MUST contain only bytes in the DNS hostname character set
//  * MUST NOT have more than maxLabels labels
//  * MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
//    In particular:
//    * MUST NOT contain underscores
//  * MUST NOT contain IDN labels (xn--)
//  * MUST NOT match the syntax of an IP address
//  * MUST end in a public suffix
//  * MUST have at least one label in addition to the public suffix
//  * MUST NOT be a label-wise suffix match for a name on the black list,
//    where comparison is case-independent (normalized to lower case)
//
// XXX: Is there any need for this method to be constant-time?  We're
//      going to refuse to issue anyway, but timing could leak whether
//      names are on the blacklist.
func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64) error {
	if id.Type != core.IdentifierDNS {
		return InvalidIdentifierError{}
	}
	domain := id.Value

	for _, ch := range []byte(domain) {
		if !isDNSCharacter(ch) {
			return SyntaxError{}
		}
	}

	if len(domain) > 255 {
		return SyntaxError{}
	}

	if ip := net.ParseIP(domain); ip != nil {
		return SyntaxError{}
	}

	labels := strings.Split(domain, ".")
	if len(labels) > maxLabels || len(labels) < 2 {
		return SyntaxError{}
	}
	for _, label := range labels {
		if len(label) < 1 || len(label) > maxLabelLength {
			return SyntaxError{}
		}

		if !dnsLabelRegexp.MatchString(label) {
			return SyntaxError{}
		}

		if punycodeRegexp.MatchString(label) {
			return SyntaxError{}
		}
	}

	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
	icannTLD, err := publicsuffix.ICANNTLD(domain)
	if err != nil {
		return NonPublicError{}
	}
	if icannTLD == domain {
		return ErrICANNTLD
	}

	// Use the domain whitelist if the PA has been asked to. However, if the
	// registration ID is from a whitelisted partner we're allowing to register
	// any domain, they can get in, too.
	enforceWhitelist := pa.EnforceWhitelist
	if regID == whitelistedPartnerRegID {
		enforceWhitelist = false
	}

	// Require no match against blacklist and if enforceWhitelist is true
	// require domain to match a whitelist rule.
	if err := pa.DB.CheckHostLists(domain, enforceWhitelist); err != nil {
		return err
	}

	return nil
}

// ChallengesFor makes a decision of what challenges, and combinations, are
// acceptable for the given identifier.
//
// Note: Current implementation is static, but future versions may not be.
func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) {
	challenges = []core.Challenge{}
	combinations = [][]int{}

	// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
	if pa.supportedChallenges[core.ChallengeTypeSimpleHTTP] {
		challenges = append(challenges, core.SimpleHTTPChallenge(accountKey))
	}

	// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
	if pa.supportedChallenges[core.ChallengeTypeDVSNI] {
		challenges = append(challenges, core.DvsniChallenge(accountKey))
	}

	if pa.supportedChallenges[core.ChallengeTypeHTTP01] {
		challenges = append(challenges, core.HTTPChallenge01(accountKey))
	}

	if pa.supportedChallenges[core.ChallengeTypeTLSSNI01] {
		challenges = append(challenges, core.TLSSNIChallenge01(accountKey))
	}

	if pa.supportedChallenges[core.ChallengeTypeDNS01] {
		challenges = append(challenges, core.DNSChallenge01(accountKey))
	}

	combinations = make([][]int, len(challenges))
	for i := range combinations {
		combinations[i] = []int{i}
	}
	return
}
<|endoftext|>


<commit_before>package poller

import (
	"encoding/json"
	"github.com/zetafunction/steam-monster-game/messages"
	"github.com/zetafunction/steam-monster-game/steam"
	"log"
	"sort"
	"time"
)

// TODO: Remember where the search started previously.
const searchStart = 48308

func findGameIndex(service *steam.APIService, start int) (int, error) {
	log.Print("Searching for invalid games starting at game ", start)
	lastValid := -1
	lastInvalid := -1
	errors := 0
	// Probe around the starting index to find a range to binary search over.
	for i, inc := start, 1; ; i, inc = i+inc, inc*2 {
		log.Print("new game scanner: probing game ", i)
		result := <-service.GetGameData(i)
		if result.Err != nil {
			log.Print("GetGameData failed: ", result.Err)
			if errors > 8 {
				log.Print("new game scanner: too many errors while finding next invalid game, giving up!")
				return 0, result.Err
			}
			errors++
		}
		switch result.Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			lastInvalid = i
			if lastValid == -1 {
				log.Print("new game scanner: initial index was invalid: searching downwards!")
				inc = -1
			}
		default:
			log.Print("Saw valid game at index ", i)
			lastValid = i
		}
		if lastValid != -1 && lastInvalid != -1 {
			break
		}
	}
	log.Printf("new game scanner: binary searching between valid game at %d and invalid game at %d", lastValid, lastInvalid)
	// Strictly speaking, a binary search is a bit dangerous because things might change.
	// Hopefully it returns close enough to the right result.
	var err error
	invalidOffset := sort.Search(lastInvalid-lastValid+1, func(i int) bool {
		// TODO: Panic?
		result := <-service.GetGameData(lastValid + i)
		if result.Err != nil {
			err = result.Err
		}
		return result.Response.GetGameData().GetStatus() == messages.EMiniGameStatus_k_EMiniGameStatus_Invalid
	})
	return lastValid + invalidOffset, err
}

type NewGameScanner struct {
	service *steam.APIService
	// The first invalid game ID. This may occasionally point to a valid game, since
	// the scanner scans 5 games ahead at a time.
	invalid int
	// If there are a lot of games in the waiting state, the new game scanner
	// sometimes has to temporarily increase the number of games to poll. The flex count
	// indicates the number of extra games that need to be polled at a given point.
	flex int

	DataUpdate        chan []byte
	InvalidGameUpdate chan int
}

func (s *NewGameScanner) Start() {
	go func() {
		for {
			if json, err := s.updateData(); err == nil {
				s.DataUpdate <- json
			} else {
				log.Print("updateData failed: ", err)
			}
			time.Sleep(time.Second)
		}
	}()
}

func (s *NewGameScanner) updateData() ([]byte, error) {
	log.Printf("new game scanner: updating (invalid: %d, flex: %d)\n", s.invalid, s.flex)
	start := s.invalid - 25 - s.flex
	end := s.invalid + 5

	type update struct {
		id     int
		result *steam.GameDataResult
	}
	c := make(chan update)
	requests := 0
	failed := 0
	for i := start; i < end; i++ {
		go func(i int) {
			result := <-s.service.GetGameData(i)
			if result.Err != nil {
				failed++
			}
			c <- update{i, result}
		}(i)
		requests++
	}
	m := make(map[int]*steam.GameDataResult)
	for requests > 0 {
		update := <-c
		m[update.id] = update.result
		requests--
	}

	type statusEntry struct {
		ID      int
		Status  string
		Players uint32
	}
	var results []statusEntry
	firstWaiting := end
	firstInvalid := end
	errors := 0
	for i := start; i < end; i++ {
		// Sometimes, the server likes to give out 500 errors, just because...
		if m[i].Err != nil {
			results = append(results, statusEntry{i, "???????", 0})
			errors++
			continue
		}
		var status string
		switch m[i].Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			if i < firstInvalid {
				firstInvalid = i
			}
			status = "invalid"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Running:
			status = "running"
		case messages.EMiniGameStatus_k_EMiniGameStatus_WaitingForPlayers:
			if i < firstWaiting {
				firstWaiting = i
			}
			status = "waiting"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Ended:
			status = "ended"
		}
		results = append(results, statusEntry{
			i,
			status,
			m[i].Response.GetStats().GetNumPlayers(),
		})
	}

	// Always try to have at least one actively updated non-waiting entry.
	reclaimableFlex := firstWaiting - (start + 1)
	if reclaimableFlex > 0 && s.flex > 0 {
		s.flex -= reclaimableFlex
		if s.flex < 0 {
			s.flex = 0
		}
	}

	// The index of the first invalid game changed: try to find the next one.
	if s.invalid != firstInvalid {
		// Only update the index of the first invalid game if the Steam API is mostly working.
		if errors < 8 {
			log.Print("new game scanner: finding next invalid game...")
			nextInvalid, err := findGameIndex(s.service, firstInvalid-1)
			if err != nil {
				log.Print("findGameIndex failed: ", err)
			} else {
				log.Print("new game scanner: next invalid game at ", nextInvalid)
				firstInvalid = nextInvalid
			}
			s.flex += firstInvalid - s.invalid
			s.invalid = firstInvalid
		} else {
			log.Print("new game scanner: skipping invalid game search due to Steam errors")
		}
	}

	return json.Marshal(results)
}

func NewNewGameScanner(service *steam.APIService) (*NewGameScanner, error) {
	// TODO: This should probably be a receiver method of NewGameScanner.
	invalid, err := findGameIndex(service, searchStart)
	if err != nil {
		log.Print("findGameIndex failed: ", err)
		return nil, err
	}
	log.Print("First invalid game around ", invalid)
	p := &NewGameScanner{service, invalid, 25, make(chan []byte), make(chan int)}
	return p, nil
}
<commit_msg>Generalize the code to find invalid games.<commit_after>package poller

import (
	"encoding/json"
	"github.com/zetafunction/steam-monster-game/messages"
	"github.com/zetafunction/steam-monster-game/steam"
	"log"
	"sort"
	"time"
)

type finderFunc func(*steam.GameDataResult) bool

func invalidGameFinder(r *steam.GameDataResult) bool {
	return r.Response.GetGameData().GetStatus() == messages.EMiniGameStatus_k_EMiniGameStatus_Invalid
}

func findGame(service *steam.APIService, start int, finder finderFunc) (int, error) {
	log.Print("new game scanner: searching for games starting at ", start)
	end := start
	errors := 0
	// Exponentially probe upwards to start.
	for i, inc := start, 1; ; i, inc = i+inc, inc*2 {
		log.Print("new game scanner: probing game ", i)
		result := <-service.GetGameData(i)
		if result.Err != nil {
			log.Print("GetGameData failed: ", result.Err)
			if errors > 8 {
				log.Print("new game scanner: too many errors while finding next invalid game, giving up!")
				return 0, result.Err
			}
			errors++
			continue
		}
		if finder(result) {
			end = i
			break
		}
		start = i
	}
	log.Print("new game scanner: binary searching between ", start, " and ", end)
	// Strictly speaking, a binary search is a bit dangerous because things might change.
	// Hopefully it returns close enough to the right result.
	offset := sort.Search(end-start, func(i int) bool {
		// TODO: Should this do the same error limiting that the previous loop does?
		result := <-service.GetGameData(start + i)
		if result.Err != nil {
			return false
		}
		return finder(result)
	})
	return start + offset, nil
}

type NewGameScanner struct {
	service *steam.APIService
	// The first invalid game ID. This may occasionally point to a valid game, since
	// the scanner scans 5 games ahead at a time.
	invalid int
	// If there are a lot of games in the waiting state, the new game scanner
	// sometimes has to temporarily increase the number of games to poll. The flex count
	// indicates the number of extra games that need to be polled at a given point.
	flex int

	DataUpdate        chan []byte
	InvalidGameUpdate chan int
}

func (s *NewGameScanner) Start() {
	go func() {
		for {
			if json, err := s.updateData(); err == nil {
				s.DataUpdate <- json
			} else {
				log.Print("updateData failed: ", err)
			}
			time.Sleep(time.Second)
		}
	}()
}

func (s *NewGameScanner) updateData() ([]byte, error) {
	log.Printf("new game scanner: updating (invalid: %d, flex: %d)\n", s.invalid, s.flex)
	start := s.invalid - 25 - s.flex
	end := s.invalid + 5

	type update struct {
		id     int
		result *steam.GameDataResult
	}
	c := make(chan update)
	requests := 0
	failed := 0
	for i := start; i < end; i++ {
		go func(i int) {
			result := <-s.service.GetGameData(i)
			if result.Err != nil {
				failed++
			}
			c <- update{i, result}
		}(i)
		requests++
	}
	m := make(map[int]*steam.GameDataResult)
	for requests > 0 {
		update := <-c
		m[update.id] = update.result
		requests--
	}

	type statusEntry struct {
		ID      int
		Status  string
		Players uint32
	}
	var results []statusEntry
	firstWaiting := end
	firstInvalid := end
	errors := 0
	for i := start; i < end; i++ {
		// Sometimes, the server likes to give out 500 errors, just because...
		if m[i].Err != nil {
			results = append(results, statusEntry{i, "???????", 0})
			errors++
			continue
		}
		var status string
		switch m[i].Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			if i < firstInvalid {
				firstInvalid = i
			}
			status = "invalid"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Running:
			status = "running"
		case messages.EMiniGameStatus_k_EMiniGameStatus_WaitingForPlayers:
			if i < firstWaiting {
				firstWaiting = i
			}
			status = "waiting"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Ended:
			status = "ended"
		}
		results = append(results, statusEntry{
			i,
			status,
			m[i].Response.GetStats().GetNumPlayers(),
		})
	}

	// Always try to have at least one actively updated non-waiting entry.
	reclaimableFlex := firstWaiting - (start + 1)
	if reclaimableFlex > 0 && s.flex > 0 {
		s.flex -= reclaimableFlex
		if s.flex < 0 {
			s.flex = 0
		}
	}

	// The index of the first invalid game changed: try to find the next one.
	if s.invalid != firstInvalid {
		// Only update the index of the first invalid game if the Steam API is mostly working.
		if errors < 8 {
			log.Print("new game scanner: finding next invalid game...")
			nextInvalid, err := findGame(s.service, firstInvalid, invalidGameFinder)
			if err != nil {
				log.Print("findGamefailed: ", err)
			} else {
				log.Print("new game scanner: next invalid game at ", nextInvalid)
				firstInvalid = nextInvalid
			}
			s.flex += firstInvalid - s.invalid
			s.invalid = firstInvalid
		} else {
			log.Print("new game scanner: skipping invalid game search due to Steam errors")
		}
	}

	return json.Marshal(results)
}

func NewNewGameScanner(service *steam.APIService) (*NewGameScanner, error) {
	// TODO: This should probably be a receiver method of NewGameScanner.
	invalid, err := findGame(service, 1, invalidGameFinder)
	if err != nil {
		log.Print("findGamefailed: ", err)
		return nil, err
	}
	log.Print("First invalid game around ", invalid)
	p := &NewGameScanner{service, invalid, 25, make(chan []byte), make(chan int)}
	return p, nil
}
<|endoftext|>


<commit_before>package the_platinum_searcher

import (
	"fmt"
	"path/filepath"
	"strings"
)

type GitIgnore struct {
	ignorePatterns patterns
	acceptPatterns patterns
	depth          int
}

func (g GitIgnore) IsMatch(path string, isDir bool, depth int) bool {
	if match := g.acceptPatterns.IsMatch(path, isDir, depth == g.depth); match {
		return false
	}
	return g.ignorePatterns.IsMatch(path, isDir, depth == g.depth)
}

type patterns []pattern

func (ps patterns) IsMatch(path string, isDir, isRoot bool) bool {
	for _, p := range ps {
		match := p.IsMatch(path, isDir, isRoot)
		if match {
			return true
		}
	}
	return false
}

type pattern string

func (p pattern) IsMatch(path string, isDir, isRoot bool) bool {

	if p.hasRootPrefix() && !isRoot {
		return false
	}

	if p.hasDirSuffix() && !isDir {
		return false
	}

	pattern := p.trimedPattern()

	match, _ := filepath.Match(pattern, p.equalizeDepth(path))
	fmt.Printf("ptn:%s path:%s(%s) => %t\n", pattern, p.equalizeDepth(path), path, match)
	return match
}

func (p pattern) equalizeDepth(path string) string {
	patternDepth := strings.Count(string(p), "/")
	pathDepth := strings.Count(path, string(filepath.Separator))
	start := 0
	if diff := pathDepth - patternDepth; diff >= 0 {
		start = diff
	}
	return filepath.Join(strings.Split(path, "/")[start:]...)
}

func (p pattern) prefix() string {
	return string(p[0])
}

func (p pattern) suffix() string {
	return string(p[len(p)-1])
}

func (p pattern) hasRootPrefix() bool {
	return p.prefix() == "/"
}

func (p pattern) hasNegativePrefix() bool {
	return p.prefix() == "!"
}

func (p pattern) hasDirSuffix() bool {
	return p.suffix() == "/"
}

func (p pattern) trimedPattern() string {
	return strings.Trim(string(p), "/")
}
<commit_msg>Added parse gitignore file method.<commit_after>package the_platinum_searcher

import (
	"bufio"
	"fmt"
	"io"
	"path/filepath"
	"strings"
)

type GitIgnore struct {
	ignorePatterns patterns
	acceptPatterns patterns
	depth          int
}

func (g *GitIgnore) Parse(r io.Reader) {
	reader := bufio.NewReader(r)
	for {
		buf, _, err := reader.ReadLine()
		if err != nil {
			break
		}
		line := strings.Trim(string(buf), " ")
		if len(line) == 0 || strings.HasPrefix(line, "#") {
			continue
		}

		if strings.HasPrefix(line, "!") {
			g.acceptPatterns = append(g.acceptPatterns,
				pattern(strings.TrimPrefix(line, "!")))
		} else {
			g.ignorePatterns = append(g.ignorePatterns, pattern(line))
		}
	}
}

func (g GitIgnore) IsMatch(path string, isDir bool, depth int) bool {
	if match := g.acceptPatterns.IsMatch(path, isDir, depth == g.depth); match {
		return false
	}
	return g.ignorePatterns.IsMatch(path, isDir, depth == g.depth)
}

type patterns []pattern

func (ps patterns) IsMatch(path string, isDir, isRoot bool) bool {
	for _, p := range ps {
		match := p.IsMatch(path, isDir, isRoot)
		if match {
			return true
		}
	}
	return false
}

type pattern string

func (p pattern) IsMatch(path string, isDir, isRoot bool) bool {

	if p.hasRootPrefix() && !isRoot {
		return false
	}

	if p.hasDirSuffix() && !isDir {
		return false
	}

	pattern := p.trimedPattern()

	match, _ := filepath.Match(pattern, p.equalizeDepth(path))
	fmt.Printf("ptn:%s path:%s(%s) => %t\n", pattern, p.equalizeDepth(path), path, match)
	return match
}

func (p pattern) equalizeDepth(path string) string {
	patternDepth := strings.Count(string(p), "/")
	pathDepth := strings.Count(path, string(filepath.Separator))
	start := 0
	if diff := pathDepth - patternDepth; diff >= 0 {
		start = diff
	}
	return filepath.Join(strings.Split(path, "/")[start:]...)
}

func (p pattern) prefix() string {
	return string(p[0])
}

func (p pattern) suffix() string {
	return string(p[len(p)-1])
}

func (p pattern) hasRootPrefix() bool {
	return p.prefix() == "/"
}

func (p pattern) hasNegativePrefix() bool {
	return p.prefix() == "!"
}

func (p pattern) hasDirSuffix() bool {
	return p.suffix() == "/"
}

func (p pattern) trimedPattern() string {
	return strings.Trim(string(p), "/")
}
<|endoftext|>


<commit_before>package glue

import (
	instance "../instance"
	"fmt"
	"github.com/GeertJohan/go.linenoise"
	lua "github.com/vifino/golua/lua"
	luar "github.com/vifino/luar"
	"regexp"
	"time"
)

// Adds regmatch and regsub for easy regex usage.
const luacode string = `
regmatch = function(regex, input, num)
	if type(regex) == "string" then
		regex = regexp(regex)
	end
	return luar.slice2table(regex.FindAllString(tostring(input), tonumber(num) or -1))
end
regsub = function(regex, src, replacement)
	if type(regex) == "string" then
		regex = regexp(regex)
	end
	return regex.ReplaceAllString(tostring(src), tostring(replacement))
end
ln_read = function(prompt)
	if prompt == nil then
		return _LN_READ("")
	else
		return _LN_READ(tostring(prompt))
	end
end
`

func BasicGlue(id int, state *lua.State) {
	luar.Register(state, "", luar.Map{
		"state_id":      id,
		"regexp":        regexp.Compile, // Regex
		"println":       fmt.Println,    // Println, just fmt.Println
		"sleep":         sleep,
		"_LN_READ":      linenoise.Line, // Line noise binding, for better repls and user input.
		"ln_addhistory": linenoise.AddHistory,
	})
	instance.Eval(state, luacode)
}

// Sleep
func sleep(seconds int) {
	time.Sleep(time.Duration(seconds) * time.Second)
}
<commit_msg>Added ln_clear()<commit_after>package glue

import (
	instance "../instance"
	"fmt"
	"github.com/GeertJohan/go.linenoise"
	lua "github.com/vifino/golua/lua"
	luar "github.com/vifino/luar"
	"regexp"
	"time"
)

// Adds regmatch and regsub for easy regex usage.
const luacode string = `
regmatch = function(regex, input, num)
	if type(regex) == "string" then
		regex = regexp(regex)
	end
	return luar.slice2table(regex.FindAllString(tostring(input), tonumber(num) or -1))
end
regsub = function(regex, src, replacement)
	if type(regex) == "string" then
		regex = regexp(regex)
	end
	return regex.ReplaceAllString(tostring(src), tostring(replacement))
end
ln_read = function(prompt)
	if prompt == nil then
		return _LN_READ("")
	else
		return _LN_READ(tostring(prompt))
	end
end
`

func BasicGlue(id int, state *lua.State) {
	luar.Register(state, "", luar.Map{
		"state_id":      id,
		"regexp":        regexp.Compile, // Regex
		"println":       fmt.Println,    // Println, just fmt.Println
		"sleep":         sleep,
		"_LN_READ":      linenoise.Line, // Line noise binding, for better repls and user input.
		"ln_addhistory": linenoise.AddHistory,
		"ln_clear":      linenoise.Clear,
	})
	instance.Eval(state, luacode)
}

// Sleep
func sleep(seconds int) {
	time.Sleep(time.Duration(seconds) * time.Second)
}
<|endoftext|>


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

// Help linls:
// https://codereview.appspot.com/7305072
package main

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"log"
	"os"
	"path"
)

// Copy from http://golang.org/misc/dist/bindist.go
// Mode constants from the tar spec.
const (
	c_ISDIR  = 040000
	c_ISFIFO = 010000
	c_ISREG  = 0100000
	c_ISLNK  = 0120000
	c_ISBLK  = 060000
	c_ISCHR  = 020000
	c_ISSOCK = 0140000
)

func main() {
	targetFilePath := "testdata.tar.gz"
	srcDirPath := "testdata"
	TarGz(srcDirPath, targetFilePath)
	UnTarGz(targetFilePath, srcDirPath+"_temp")
	fmt.Println("Finish!")
}

// Gzip and tar from source directory to destination file
// you need check file exist before you call this function
func TarGz(srcDirPath string, destFilePath string) {
	fmt.Println("Cerating tar.gz...")
	fw, err := os.Create(destFilePath)
	handleError(err)
	defer fw.Close()

	// Gzip writer
	gw := gzip.NewWriter(fw)
	defer gw.Close()

	// Tar writer
	tw := tar.NewWriter(gw)
	defer tw.Close()

	// handle source directory
	TarGzDir(srcDirPath, path.Base(srcDirPath), tw)
}

// Deal with directories
// if find files, handle them with HandleFile
// Every recurrence append the base path to the recPath
// recPath is the path inside of tar.gz
func TarGzDir(srcDirPath string, recPath string, tw *tar.Writer) {
	// Open source diretory
	dir, err := os.Open(srcDirPath)
	handleError(err)
	defer dir.Close()

	// Get file info slice
	fis, err := dir.Readdir(0)
	handleError(err)
	for _, fi := range fis {
		// Append path
		curPath := srcDirPath + "/" + fi.Name()
		// Check it is directory or file
		if fi.IsDir() {
			// Directory
			// (Directory won't add unitl all subfiles are added)
			fmt.Printf("Adding path...%s\n", curPath)
			TarGzDir(curPath, recPath+"/"+fi.Name(), tw)
		} else {
			// File
			fmt.Printf("Adding file...%s\n", curPath)
		}

		TarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi)
	}
}

// Deal with files
func TarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) {
	if fi.IsDir() {
		// Create tar header
		hdr := new(tar.Header)
		hdr.Name = recPath + "/"
		hdr.Typeflag = tar.TypeDir
		hdr.Size = 0
		//hdr.Mode = 0755 | c_ISDIR
		hdr.Mode = int64(fi.Mode())
		hdr.ModTime = fi.ModTime()
		// if last character of header name is '/' it also can be directory

		// Write hander
		err := tw.WriteHeader(hdr)
		handleError(err)
	} else {
		// File reader
		fr, err := os.Open(srcFile)
		handleError(err)
		defer fr.Close()

		// Create tar header
		hdr := new(tar.Header)
		hdr.Name = recPath
		hdr.Size = fi.Size()
		hdr.Mode = int64(fi.Mode())
		hdr.ModTime = fi.ModTime()

		// Write hander
		err = tw.WriteHeader(hdr)
		handleError(err)

		// Write file data
		_, err = io.Copy(tw, fr)
		handleError(err)
	}
}

// Ungzip and untar from source file to destination directory
// you need check file exist before you call this function
func UnTarGz(srcFilePath string, destDirPath string) {
	fmt.Println("UnTarGzing " + srcFilePath + "...")
	// Create destination directory
	os.Mkdir(destDirPath, os.ModePerm)

	fr, err := os.Open(srcFilePath)
	handleError(err)
	defer fr.Close()

	// Gzip reader
	gr, err := gzip.NewReader(fr)

	// Tar reader
	tr := tar.NewReader(gr)

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// End of tar archive
			break
		}
		//handleError(err)
		fmt.Println("UnTarGzing file..." + hdr.Name)

		// If header name ends with '/' means it is directory ,
		// and doesn't need extract
		c := []byte(hdr.Name)
		if c[len(hdr.Name)-1] != '/' {
			// Get files from archive
			os.MkdirAll(destDirPath+"/"+path.Dir(hdr.Name), os.ModePerm)
			// Write data to file
			fw, _ := os.Create(destDirPath + "/" + hdr.Name)
			handleError(err)
			_, err = io.Copy(fw, tr)
			handleError(err)
		}
	}
}

func handleError(e error) {
	if e != nil {
		log.Fatal(e)
	}

}
<commit_msg>bug fix<commit_after>// Copyright 2013 The Author - Unknown. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Help linls:
// https://codereview.appspot.com/7305072
package main

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"log"
	"os"
	"path"
)

// Copy from http://golang.org/misc/dist/bindist.go
// Mode constants from the tar spec.
const (
	c_ISDIR  = 040000
	c_ISFIFO = 010000
	c_ISREG  = 0100000
	c_ISLNK  = 0120000
	c_ISBLK  = 060000
	c_ISCHR  = 020000
	c_ISSOCK = 0140000
)

func main() {
	targetFilePath := "testdata.tar.gz"
	srcDirPath := "testdata"
	TarGz(srcDirPath, targetFilePath)
	UnTarGz(targetFilePath, srcDirPath+"_temp")
	fmt.Println("Finish!")
}

// Gzip and tar from source directory to destination file
// you need check file exist before you call this function
func TarGz(srcDirPath string, destFilePath string) {
	fmt.Println("Cerating tar.gz...")
	fw, err := os.Create(destFilePath)
	handleError(err)
	defer fw.Close()

	// Gzip writer
	gw := gzip.NewWriter(fw)
	defer gw.Close()

	// Tar writer
	tw := tar.NewWriter(gw)
	defer tw.Close()

	// handle source directory
	TarGzDir(srcDirPath, path.Base(srcDirPath), tw)
}

// Deal with directories
// if find files, handle them with HandleFile
// Every recurrence append the base path to the recPath
// recPath is the path inside of tar.gz
func TarGzDir(srcDirPath string, recPath string, tw *tar.Writer) {
	// Open source diretory
	dir, err := os.Open(srcDirPath)
	handleError(err)
	defer dir.Close()

	// Get file info slice
	fis, err := dir.Readdir(0)
	handleError(err)
	for _, fi := range fis {
		// Append path
		curPath := srcDirPath + "/" + fi.Name()
		// Check it is directory or file
		if fi.IsDir() {
			// Directory
			// (Directory won't add unitl all subfiles are added)
			fmt.Printf("Adding path...%s\n", curPath)
			TarGzDir(curPath, recPath+"/"+fi.Name(), tw)
		} else {
			// File
			fmt.Printf("Adding file...%s\n", curPath)
		}

		TarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi)
	}
}

// Deal with files
func TarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) {
	if fi.IsDir() {
		// Create tar header
		hdr := new(tar.Header)
		hdr.Name = recPath + "/"
		hdr.Typeflag = tar.TypeDir
		hdr.Size = 0
		//hdr.Mode = 0755 | c_ISDIR
		hdr.Mode = int64(fi.Mode())
		hdr.ModTime = fi.ModTime()
		// if last character of header name is '/' it also can be directory

		// Write hander
		err := tw.WriteHeader(hdr)
		handleError(err)
	} else {
		// File reader
		fr, err := os.Open(srcFile)
		handleError(err)
		defer fr.Close()

		// Create tar header
		hdr := new(tar.Header)
		hdr.Name = recPath
		hdr.Size = fi.Size()
		hdr.Mode = int64(fi.Mode())
		hdr.ModTime = fi.ModTime()

		// Write hander
		err = tw.WriteHeader(hdr)
		handleError(err)

		// Write file data
		_, err = io.Copy(tw, fr)
		handleError(err)
	}
}

// Ungzip and untar from source file to destination directory
// you need check file exist before you call this function
func UnTarGz(srcFilePath string, destDirPath string) {
	fmt.Println("UnTarGzing " + srcFilePath + "...")
	// Create destination directory
	os.Mkdir(destDirPath, os.ModePerm)

	fr, err := os.Open(srcFilePath)
	handleError(err)
	defer fr.Close()

	// Gzip reader
	gr, err := gzip.NewReader(fr)

	// Tar reader
	tr := tar.NewReader(gr)

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// End of tar archive
			break
		}
		//handleError(err)
		fmt.Println("UnTarGzing file..." + hdr.Name)

		if hdr.Typeflag != tar.TypeDir {
			// Get files from archive
			os.MkdirAll(destDirPath+"/"+path.Dir(hdr.Name), os.ModePerm)
			// Write data to file
			fw, _ := os.Create(destDirPath + "/" + hdr.Name)
			handleError(err)
			_, err = io.Copy(fw, tr)
			handleError(err)
		}
	}
}

func handleError(e error) {
	if e != nil {
		log.Fatal(e)
	}

}
<|endoftext|>


<commit_before>package golangsdk

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	zincBaseURL = "https://api.zinc.io/v1"
)

type Retailer string

const (
	Amazon     Retailer = "amazon"
	AmazonUK   Retailer = "amazon_uk"
	AmazonCA   Retailer = "amazon_ca"
	AmazonMX   Retailer = "amazon_mx"
	Walmart    Retailer = "walmart"
	Aliexpress Retailer = "aliexpress"
)

var DefaultProductOptions = ProductOptions{
	Timeout: time.Duration(time.Second * 90),
}

type Zinc struct {
	ClientToken string
	ZincBaseURL string
}

func NewZinc(clientToken string) (*Zinc, error) {
	z := Zinc{
		ClientToken: clientToken,
		ZincBaseURL: zincBaseURL,
	}
	return &z, nil
}

type ProductOffersResponse struct {
	Code     string            `json:"code"`
	Data     ErrorDataResponse `json:"data"`
	Status   string            `json:"status"`
	Retailer string            `json:"retailer"`
	Offers   []ProductOffer    `json:"offers"`
}

type ProductOffer struct {
	Available            bool             `json:"available"`
	Addon                bool             `json:"addon"`
	Condition            string           `json:"condition"`
	ShippingOptions      []ShippingOption `json:"shipping_options"`
	HandlingDays         HandlingDays     `json:"handling_days"`
	PrimeOnly            bool             `json:"prime_only"`
	MarketplaceFulfilled bool             `json:"marketplace_fulfilled"`
	Currency             string           `json:"currency"`
	Seller               Seller           `json:"seller"`
	BuyBoxWinner         bool             `json:"buy_box_winner"`
	International        bool             `json:"international"`
	OfferId              string           `json:"offer_id"`
	Price                int              `json:"price"`
}

type ShippingOption struct {
	Price int `json:"price"`
}

type HandlingDays struct {
	Max int `json:"max"`
	Min int `json:"min"`
}

type Seller struct {
	NumRatings      int    `json:"num_ratings"`
	PercentPositive int    `json:"percent_positive"`
	FirstParty      bool   `json:"first_party"`
	Name            string `json:"name"`
	Id              string `json:"id"`
}

type ProductDetailsResponse struct {
	Code               string              `json:"code"`
	Data               ErrorDataResponse   `json:"data"`
	Status             string              `json:"status"`
	ProductDescription string              `json:"product_description"`
	PostDescription    string              `json:"post_description"`
	Retailer           string              `json:"retailer"`
	Epids              []ExternalProductId `json:"epids"`
	ProductDetails     []string            `json:"product_details"`
	Title              string              `json:"title"`
	VariantSpecifics   []VariantSpecific   `json:"variant_specifics"`
	ProductId          string              `json:"product_id"`
	MainImage          string              `json:"main_image"`
	Brand              string              `json:"brand"`
	MPN                string              `json:"mpn"`
	Images             []string            `json:"images"`
	FeatureBullets     []string            `json:"feature_bullets"`
}

type ExternalProductId struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type VariantSpecific struct {
	Dimension string `json:"dimension"`
	Value     string `json:"value"`
}

type ErrorDataResponse struct {
	Message string `json:"message"`
}

type ProductOptions struct {
	MaxAge    int           `json:"max_age"`
	NewerThan time.Time     `json:"newer_than"`
	Timeout   time.Duration `json:"timeout"`
}

type ZincError struct {
	ErrorMessage string            `json:"error"`
	Data         ErrorDataResponse `json:"data"`
}

func (z ZincError) Error() string {
	return z.ErrorMessage
}

func SimpleError(errorStr string) ZincError {
	return ZincError{ErrorMessage: errorStr}
}

func (z Zinc) GetProductInfo(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, *ProductDetailsResponse, error) {
	offersChan := make(chan *ProductOffersResponse, 1)
	detailsChan := make(chan *ProductDetailsResponse, 1)
	errorsChan := make(chan error, 2)

	go func() {
		offers, err := z.GetProductOffers(productId, retailer, options)
		errorsChan <- err
		offersChan <- offers
	}()

	go func() {
		details, err := z.GetProductDetails(productId, retailer, options)
		errorsChan <- err
		detailsChan <- details
	}()

	offers := <-offersChan
	details := <-detailsChan
	for i := 0; i < 2; i++ {
		err := <-errorsChan
		if err != nil {
			return nil, nil, err
		}
	}
	return offers, details, nil
}

func (z Zinc) GetProductOffers(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	values.Set("version", "2")
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v/offers?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductOffersResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		return &resp, ZincError{ErrorMessage: "Zinc API returned status 'failed'", Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) GetProductDetails(productId string, retailer Retailer, options ProductOptions) (*ProductDetailsResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductDetailsResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		return &resp, ZincError{ErrorMessage: "Zinc API returned status 'failed'", Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) sendGetRequest(requestPath string, timeout time.Duration) ([]byte, error) {
	httpReq, err := http.NewRequest("GET", requestPath, nil)
	if err != nil {
		return nil, err
	}
	httpReq.SetBasicAuth(z.ClientToken, "")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr, Timeout: timeout}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respBody, nil
}
<commit_msg>Zinc api message.<commit_after>package golangsdk

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	zincBaseURL = "https://api.zinc.io/v1"
)

type Retailer string

const (
	Amazon     Retailer = "amazon"
	AmazonUK   Retailer = "amazon_uk"
	AmazonCA   Retailer = "amazon_ca"
	AmazonMX   Retailer = "amazon_mx"
	Walmart    Retailer = "walmart"
	Aliexpress Retailer = "aliexpress"
)

var DefaultProductOptions = ProductOptions{
	Timeout: time.Duration(time.Second * 90),
}

type Zinc struct {
	ClientToken string
	ZincBaseURL string
}

func NewZinc(clientToken string) (*Zinc, error) {
	z := Zinc{
		ClientToken: clientToken,
		ZincBaseURL: zincBaseURL,
	}
	return &z, nil
}

type ProductOffersResponse struct {
	Code     string            `json:"code"`
	Data     ErrorDataResponse `json:"data"`
	Status   string            `json:"status"`
	Retailer string            `json:"retailer"`
	Offers   []ProductOffer    `json:"offers"`
}

type ProductOffer struct {
	Available            bool             `json:"available"`
	Addon                bool             `json:"addon"`
	Condition            string           `json:"condition"`
	ShippingOptions      []ShippingOption `json:"shipping_options"`
	HandlingDays         HandlingDays     `json:"handling_days"`
	PrimeOnly            bool             `json:"prime_only"`
	MarketplaceFulfilled bool             `json:"marketplace_fulfilled"`
	Currency             string           `json:"currency"`
	Seller               Seller           `json:"seller"`
	BuyBoxWinner         bool             `json:"buy_box_winner"`
	International        bool             `json:"international"`
	OfferId              string           `json:"offer_id"`
	Price                int              `json:"price"`
}

type ShippingOption struct {
	Price int `json:"price"`
}

type HandlingDays struct {
	Max int `json:"max"`
	Min int `json:"min"`
}

type Seller struct {
	NumRatings      int    `json:"num_ratings"`
	PercentPositive int    `json:"percent_positive"`
	FirstParty      bool   `json:"first_party"`
	Name            string `json:"name"`
	Id              string `json:"id"`
}

type ProductDetailsResponse struct {
	Code               string              `json:"code"`
	Data               ErrorDataResponse   `json:"data"`
	Status             string              `json:"status"`
	ProductDescription string              `json:"product_description"`
	PostDescription    string              `json:"post_description"`
	Retailer           string              `json:"retailer"`
	Epids              []ExternalProductId `json:"epids"`
	ProductDetails     []string            `json:"product_details"`
	Title              string              `json:"title"`
	VariantSpecifics   []VariantSpecific   `json:"variant_specifics"`
	ProductId          string              `json:"product_id"`
	MainImage          string              `json:"main_image"`
	Brand              string              `json:"brand"`
	MPN                string              `json:"mpn"`
	Images             []string            `json:"images"`
	FeatureBullets     []string            `json:"feature_bullets"`
}

type ExternalProductId struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type VariantSpecific struct {
	Dimension string `json:"dimension"`
	Value     string `json:"value"`
}

type ErrorDataResponse struct {
	Message string `json:"message"`
}

type ProductOptions struct {
	MaxAge    int           `json:"max_age"`
	NewerThan time.Time     `json:"newer_than"`
	Timeout   time.Duration `json:"timeout"`
}

type ZincError struct {
	ErrorMessage string            `json:"error"`
	Data         ErrorDataResponse `json:"data"`
}

func (z ZincError) Error() string {
	return z.ErrorMessage
}

func SimpleError(errorStr string) ZincError {
	return ZincError{ErrorMessage: errorStr}
}

func (z Zinc) GetProductInfo(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, *ProductDetailsResponse, error) {
	offersChan := make(chan *ProductOffersResponse, 1)
	detailsChan := make(chan *ProductDetailsResponse, 1)
	errorsChan := make(chan error, 2)

	go func() {
		offers, err := z.GetProductOffers(productId, retailer, options)
		errorsChan <- err
		offersChan <- offers
	}()

	go func() {
		details, err := z.GetProductDetails(productId, retailer, options)
		errorsChan <- err
		detailsChan <- details
	}()

	offers := <-offersChan
	details := <-detailsChan
	for i := 0; i < 2; i++ {
		err := <-errorsChan
		if err != nil {
			return nil, nil, err
		}
	}
	return offers, details, nil
}

func (z Zinc) GetProductOffers(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	values.Set("version", "2")
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v/offers?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductOffersResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		msg := fmt.Sprintf("Zinc API returned status 'failed' data=%+v", resp.Data)
		return &resp, ZincError{ErrorMessage: msg, Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) GetProductDetails(productId string, retailer Retailer, options ProductOptions) (*ProductDetailsResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductDetailsResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		msg := fmt.Sprintf("Zinc API returned status 'failed' data=%+v", resp.Data)
		return &resp, ZincError{ErrorMessage: msg, Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) sendGetRequest(requestPath string, timeout time.Duration) ([]byte, error) {
	httpReq, err := http.NewRequest("GET", requestPath, nil)
	if err != nil {
		return nil, err
	}
	httpReq.SetBasicAuth(z.ClientToken, "")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr, Timeout: timeout}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respBody, nil
}
<|endoftext|>