<commit_before>package rc

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/go-errors/errors"

	"github.com/markelog/eclectica/variables"
)

var (
	rcs = map[string][]string{
		"bash": []string{".bash_profile", ".bashrc", ".profile"},
		"zsh":  []string{".zshrc"},
	}
)

type Rc struct {
	command string
	path    string
}

func New(command string) *Rc {
	rc := &Rc{}

	rc.command = command
	rc.path = rc.Find()

	return rc
}

// Add bash configs on Linux system
// .bashrc works when you open new bash session (open terminal)
// .bash_profile is executed when you login
//
// So in order for our env variables to be consistently exposed we need to modify both of them
// Note: on Mac, .bash_profile is executed when new bash session is opened,
// so we don't need to this in there
func (rc *Rc) Add() error {
	shell := variables.GetShellName()

	if shell != "bash" {
		return rc.add()
	}

	pathsRc := filepath.Join(os.Getenv("HOME"), ".bashrc")
	pathsProfile := filepath.Join(os.Getenv("HOME"), ".bash_profile")

	// Make sure we have those files
	if _, err := os.Stat(pathsRc); err != nil {
		return rc.add()
	}
	if _, err := os.Stat(pathsProfile); err != nil {
		return rc.add()
	}

	bashrc := &Rc{
		command: rc.command,
		path:    pathsRc,
	}

	bashProfile := &Rc{
		command: rc.command,
		path:    pathsProfile,
	}

	err := bashrc.add()
	if err != nil {
		return err
	}

	err = bashProfile.add()
	if err != nil {
		return err
	}

	return nil
}

func (rc *Rc) add() error {
	if rc.Exists() {
		return nil
	}

	file, err := os.OpenFile(rc.path, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	defer file.Close()

	if err != nil {
		return errors.New(err)
	}

	_, err = file.WriteString(rc.command)
	return errors.New(err)
}

func (rc *Rc) Exists() bool {
	contents, err := ioutil.ReadFile(rc.path)
	if err != nil {
		return false
	}

	str := string(contents)

	return strings.Contains(str, rc.command)
}

func (rc *Rc) Find() string {
	home := os.Getenv("HOME")
	shell := variables.GetShellName()

	files, _ := ioutil.ReadDir(home)

	for _, possibility := range rcs[shell] {
		for _, file := range files {
			if file.Name() == possibility {
				return filepath.Join(home, possibility)
			}
		}
	}

	return ""
}
<commit_msg>Correct error return in rc module<commit_after>package rc

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/go-errors/errors"

	"github.com/markelog/eclectica/variables"
)

var (
	rcs = map[string][]string{
		"bash": []string{".bash_profile", ".bashrc", ".profile"},
		"zsh":  []string{".zshrc"},
	}
)

type Rc struct {
	command string
	path    string
}

func New(command string) *Rc {
	rc := &Rc{}

	rc.command = command
	rc.path = rc.Find()

	return rc
}

// Add bash configs on Linux system
// .bashrc works when you open new bash session (open terminal)
// .bash_profile is executed when you login
//
// So in order for our env variables to be consistently exposed we need to modify both of them
// Note: on Mac, .bash_profile is executed when new bash session is opened,
// so we don't need to this in there
func (rc *Rc) Add() error {
	shell := variables.GetShellName()

	if shell != "bash" {
		return rc.add()
	}

	pathsRc := filepath.Join(os.Getenv("HOME"), ".bashrc")
	pathsProfile := filepath.Join(os.Getenv("HOME"), ".bash_profile")

	// Make sure we have those files
	if _, err := os.Stat(pathsRc); err != nil {
		return rc.add()
	}
	if _, err := os.Stat(pathsProfile); err != nil {
		return rc.add()
	}

	bashrc := &Rc{
		command: rc.command,
		path:    pathsRc,
	}

	bashProfile := &Rc{
		command: rc.command,
		path:    pathsProfile,
	}

	err := bashrc.add()
	if err != nil {
		return err
	}

	err = bashProfile.add()
	if err != nil {
		return err
	}

	return nil
}

func (rc *Rc) add() (err error) {
	if rc.Exists() {
		return
	}

	file, err := os.OpenFile(rc.path, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	defer file.Close()

	if err != nil {
		return errors.New(err)
	}

	_, err = file.WriteString(rc.command)
	if err != nil {
		err = errors.New(err)
	}

	return
}

func (rc *Rc) Exists() bool {
	contents, err := ioutil.ReadFile(rc.path)
	if err != nil {
		return false
	}

	str := string(contents)

	return strings.Contains(str, rc.command)
}

func (rc *Rc) Find() string {
	home := os.Getenv("HOME")
	shell := variables.GetShellName()

	files, _ := ioutil.ReadDir(home)

	for _, possibility := range rcs[shell] {
		for _, file := range files {
			if file.Name() == possibility {
				return filepath.Join(home, possibility)
			}
		}
	}

	return ""
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"github.com/coredns/coredns/plugin"
	"github.com/coredns/coredns/plugin/kubernetes/object"
	"github.com/coredns/coredns/request"
)

// AutoPath implements the AutoPathFunc call from the autopath plugin.
// It returns a per-query search path or nil indicating no searchpathing should happen.
func (k *Kubernetes) AutoPath(state request.Request) []string {
	// Check if the query falls in a zone we are actually authoritative for and thus if we want autopath.
	zone := plugin.Zones(k.Zones).Matches(state.Name())
	if zone == "" {
		return nil
	}

	ip := state.IP()

	pod := k.podWithIP(ip)
	if pod == nil {
		return nil
	}

	search := make([]string, 3)
	if zone == "." {
		search[0] = pod.Namespace + ".svc."
		search[1] = "svc."
		search[2] = "."
	} else {
		search[0] = pod.Namespace + ".svc." + zone
		search[1] = "svc." + zone
		search[2] = zone
	}

	search = append(search, k.autoPathSearch...)
	search = append(search, "") // sentinel
	return search
}

// podWithIP return the api.Pod for source IP ip. It returns nil if nothing can be found.
func (k *Kubernetes) podWithIP(ip string) *object.Pod {
	ps := k.APIConn.PodIndex(ip)
	if len(ps) == 0 {
		return nil
	}
	return ps[0]
}
<commit_msg>add opts.initPodCache to avoid panic (#2279)<commit_after>package kubernetes

import (
	"github.com/coredns/coredns/plugin"
	"github.com/coredns/coredns/plugin/kubernetes/object"
	"github.com/coredns/coredns/request"
)

// AutoPath implements the AutoPathFunc call from the autopath plugin.
// It returns a per-query search path or nil indicating no searchpathing should happen.
func (k *Kubernetes) AutoPath(state request.Request) []string {
	// Check if the query falls in a zone we are actually authoritative for and thus if we want autopath.
	zone := plugin.Zones(k.Zones).Matches(state.Name())
	if zone == "" {
		return nil
	}

	// cluster.local {
	//    autopath @kubernetes
	//    kubernetes {
	//        pods verified #
	//    }
	// }
	// if pods != verified will cause panic and return SERVFAIL, expect worked as normal without autopath function
	if !k.opts.initPodCache {
		return nil
	}

	ip := state.IP()

	pod := k.podWithIP(ip)
	if pod == nil {
		return nil
	}

	search := make([]string, 3)
	if zone == "." {
		search[0] = pod.Namespace + ".svc."
		search[1] = "svc."
		search[2] = "."
	} else {
		search[0] = pod.Namespace + ".svc." + zone
		search[1] = "svc." + zone
		search[2] = zone
	}

	search = append(search, k.autoPathSearch...)
	search = append(search, "") // sentinel
	return search
}

// podWithIP return the api.Pod for source IP ip. It returns nil if nothing can be found.
func (k *Kubernetes) podWithIP(ip string) *object.Pod {
	ps := k.APIConn.PodIndex(ip)
	if len(ps) == 0 {
		return nil
	}
	return ps[0]
}
<|endoftext|>


<commit_before>// reply
package web

import (
	"net/http"

	"fmt"
	"github.com/coffeehc/logger"
	"github.com/unrolled/render"
)

type Reply interface {
	GetStatusCode() int
	SetStatusCode(statusCode int) Reply
	SetCookie(cookie http.Cookie) Reply
	SetHeader(key, value string) Reply
	AddHeader(key, value string) Reply
	DelHeader(key string) Reply
	GetHeader(key string) string
	Header() http.Header
	Redirect(code int, url string) Reply

	With(data interface{}) Reply
	As(transport Transport) Reply
	GetResponseWriter() http.ResponseWriter
	AdapterHttpHandler(adapter bool)
	GetRequestContext() RequestContext
}

type httpReply struct {
	statusCode         int
	data               interface{}
	header             http.Header
	cookies            []http.Cookie
	transport          Transport
	responseWriter     http.ResponseWriter
	adapterHttpHandler bool
	requestContext     RequestContext
}

func newHttpReply(responseWriter http.ResponseWriter, config *ServerConfig) *httpReply {
	return &httpReply{
		statusCode:     200,
		transport:      config.getDefaultTransport(),
		cookies:        make([]http.Cookie, 0),
		responseWriter: responseWriter,
		header:         responseWriter.Header(),
	}
}

func (this *httpReply) GetRequestContext() RequestContext {
	if this.requestContext == nil {
		this.requestContext = make(RequestContext)
	}
	return this.requestContext
}

func (this *httpReply) AdapterHttpHandler(adapter bool) {
	this.adapterHttpHandler = adapter
}

func (this *httpReply) GetStatusCode() int {
	return this.statusCode
}

func (this *httpReply) SetStatusCode(statusCode int) Reply {
	this.statusCode = statusCode
	return this
}

func (this *httpReply) SetCookie(cookie http.Cookie) Reply {
	this.cookies = append(this.cookies, cookie)
	return this
}

func (this *httpReply) SetHeader(key, value string) Reply {
	this.header.Set(key, value)
	return this
}
func (this *httpReply) AddHeader(key, value string) Reply {
	this.header.Add(key, value)
	return this
}
func (this *httpReply) DelHeader(key string) Reply {
	this.header.Del(key)
	return this
}

func (this *httpReply) GetHeader(key string) string {
	return this.header.Get(key)
}

func (this *httpReply) Header() http.Header {
	return this.header
}

func (this *httpReply) Redirect(code int, url string) Reply {
	this.responseWriter.Header().Set("Location", url)
	this.statusCode = code
	return this
}

func (this *httpReply) With(data interface{}) Reply {
	this.data = data
	return this
}

func (this *httpReply) As(transport Transport) Reply {
	if transport != nil {
		this.transport = transport
	}
	return this
}

func (this *httpReply) GetResponseWriter() http.ResponseWriter {
	return this.responseWriter
}

//Reply 最后的清理工作
func (this *httpReply) finishReply(request *http.Request, render *render.Render) {
	if this.adapterHttpHandler {
		return
	}
	this.writeWarpHeader()
	if this.data == nil {
		this.data = ""
	}
	err := this.transport(render, request, this.GetResponseWriter(), this.GetStatusCode(), this.data)
	if err != nil {
		logger.Error("render error %#v", err)
		Transport_Json(render, request, this.GetResponseWriter(), 500, fmt.Sprintf("render error :%s", err))
	}
}

func (this *httpReply) writeWarpHeader() {
	header := this.Header()
	for _, cookie := range this.cookies {
		header.Set("Set-Cookie", cookie.String())
	}
}
<commit_msg>修改response 异常时,返回消息的 ContentType 格式<commit_after>// reply
package web

import (
	"net/http"

	"fmt"
	"github.com/coffeehc/logger"
	"github.com/unrolled/render"
)

type Reply interface {
	GetStatusCode() int
	SetStatusCode(statusCode int) Reply
	SetCookie(cookie http.Cookie) Reply
	SetHeader(key, value string) Reply
	AddHeader(key, value string) Reply
	DelHeader(key string) Reply
	GetHeader(key string) string
	Header() http.Header
	Redirect(code int, url string) Reply

	With(data interface{}) Reply
	As(transport Transport) Reply
	GetResponseWriter() http.ResponseWriter
	AdapterHttpHandler(adapter bool)
	GetRequestContext() RequestContext
}

type httpReply struct {
	statusCode         int
	data               interface{}
	header             http.Header
	cookies            []http.Cookie
	transport          Transport
	responseWriter     http.ResponseWriter
	adapterHttpHandler bool
	requestContext     RequestContext
}

func newHttpReply(responseWriter http.ResponseWriter, config *ServerConfig) *httpReply {
	return &httpReply{
		statusCode:     200,
		transport:      config.getDefaultTransport(),
		cookies:        make([]http.Cookie, 0),
		responseWriter: responseWriter,
		header:         responseWriter.Header(),
	}
}

func (this *httpReply) GetRequestContext() RequestContext {
	if this.requestContext == nil {
		this.requestContext = make(RequestContext)
	}
	return this.requestContext
}

func (this *httpReply) AdapterHttpHandler(adapter bool) {
	this.adapterHttpHandler = adapter
}

func (this *httpReply) GetStatusCode() int {
	return this.statusCode
}

func (this *httpReply) SetStatusCode(statusCode int) Reply {
	this.statusCode = statusCode
	return this
}

func (this *httpReply) SetCookie(cookie http.Cookie) Reply {
	this.cookies = append(this.cookies, cookie)
	return this
}

func (this *httpReply) SetHeader(key, value string) Reply {
	this.header.Set(key, value)
	return this
}
func (this *httpReply) AddHeader(key, value string) Reply {
	this.header.Add(key, value)
	return this
}
func (this *httpReply) DelHeader(key string) Reply {
	this.header.Del(key)
	return this
}

func (this *httpReply) GetHeader(key string) string {
	return this.header.Get(key)
}

func (this *httpReply) Header() http.Header {
	return this.header
}

func (this *httpReply) Redirect(code int, url string) Reply {
	this.responseWriter.Header().Set("Location", url)
	this.statusCode = code
	return this
}

func (this *httpReply) With(data interface{}) Reply {
	this.data = data
	return this
}

func (this *httpReply) As(transport Transport) Reply {
	if transport != nil {
		this.transport = transport
	}
	return this
}

func (this *httpReply) GetResponseWriter() http.ResponseWriter {
	return this.responseWriter
}

//Reply 最后的清理工作
func (this *httpReply) finishReply(request *http.Request, render *render.Render) {
	if this.adapterHttpHandler {
		return
	}
	this.writeWarpHeader()
	if this.data == nil {
		this.data = ""
	}
	err := this.transport(render, request, this.GetResponseWriter(), this.GetStatusCode(), this.data)
	if err != nil {
		logger.Error("render error %#v", err)
		Transport_Text(render, request, this.GetResponseWriter(), 500, fmt.Sprintf("render error :%s", err))
	}
}

func (this *httpReply) writeWarpHeader() {
	header := this.Header()
	for _, cookie := range this.cookies {
		header.Set("Set-Cookie", cookie.String())
	}
}
<|endoftext|>


<commit_before>package relay

import (
	"errors"
	"log"
	"sync"
	"time"

	"github.com/armon/relay/broker"
)

var (
	// Error returned when an invalid number of attempts is specified. Protects
	// overflows of these values.
	errAttemptsRange = errors.New("Attempts must be between 1 and 32")

	// Error returned if a zero value is given for min/max wait time.
	errWaitRange = errors.New("Min/Max wait times must be positive")
)

// retryBroker implements a resilient AMQP broker which automatically replaces
// the underlying channel if an unexpected error is observed. Due to AMQP's
// persistent connections, this becomes necessary to survive even minor network
// hiccups which may temporarily sever the connection to the server.
//
// The retryBroker does not come without a price, however. Clients are still
// responsible for deduplicating messages or handling them idempotently, as
// duplications are a regular happening in the face of network errors due to
// message redelivery provided by the protocol. It is also important to note
// that acknowledgements - both ack and nack - cannot be retried across
// connection resets. This stems from the fact that a given consumer's channel
// cannot be revived and must be thrown away if the consumer is to be reused.
type retryBroker struct {
	broker   *relayBroker
	attempts int
	min      time.Duration
	max      time.Duration
}

// RetryBroker returns a new retrying broker with the given settings.
func (r *Relay) RetryBroker(attempts int, min, max time.Duration) (*retryBroker, error) {
	if attempts < 1 || attempts > 32 {
		return nil, errAttemptsRange
	}
	if min == 0 || max == 0 {
		return nil, errWaitRange
	}
	return &retryBroker{&relayBroker{r}, attempts, min, max}, nil
}

// Publisher returns a new retrying broker.Publisher.
func (rb *retryBroker) Publisher(queue string) (broker.Publisher, error) {
	return &retryPublisher{
		broker:   rb.broker,
		queue:    queue,
		attempts: rb.attempts,
		min:      rb.min,
		max:      rb.max,
	}, nil
}

// Consumer returns a new retrying broker.Consumer.
func (rb *retryBroker) Consumer(queue string) (broker.Consumer, error) {
	return &retryConsumer{
		broker:   rb.broker,
		queue:    queue,
		attempts: rb.attempts,
		min:      rb.min,
		max:      rb.max,
	}, nil
}

// Close closes the broker.
func (rb *retryBroker) Close() error {
	if rb.broker != nil {
		return rb.broker.Close()
	}
	return nil
}

// retryConsumer tracks the relay broker and an associated consumer, allowing
// the consumer to be replaced transparently in the face of failure.
type retryConsumer struct {
	broker   broker.Broker
	cons     broker.Consumer
	queue    string
	attempts int
	min      time.Duration
	max      time.Duration
	l        sync.RWMutex
}

// consumer is used to connect the consumer to the relay queue.
func (rc *retryConsumer) consumer(create bool) (broker.Consumer, error) {
	// Check for an existing consumer
	rc.l.RLock()
	cons := rc.cons
	rc.l.RUnlock()

	if cons != nil || !create {
		return cons, nil
	}

	// Make a new consumer
	rc.l.Lock()
	defer rc.l.Unlock()

	cons, err := rc.broker.Consumer(rc.queue)
	if err != nil {
		return nil, err
	}
	rc.cons = cons
	return cons, nil
}

// discard is used to remove a broken consumer.
func (rc *retryConsumer) discard(cons broker.Consumer) {
	if cons == nil {
		return
	}

	rc.l.Lock()
	defer rc.l.Unlock()

	cons.Close()
	if cons == rc.cons {
		rc.cons = nil
	}
}

// Close closes the consumer.
func (rc *retryConsumer) Close() error {
	cons, err := rc.consumer(false)
	if err != nil || cons == nil {
		return err
	}
	return cons.Close()
}

// Ack marks message(s) as delivered.
func (rc *retryConsumer) Ack() error {
	cons, err := rc.consumer(false)
	if err != nil || cons == nil {
		return err
	}
	err = cons.Ack()
	if err != nil {
		log.Printf("[ERR] relay: consumer failed ack: %v", err)
		rc.discard(cons)
	}
	return err
}

// Nack sends message(s) back to the queue.
func (rc *retryConsumer) Nack() error {
	cons, err := rc.consumer(false)
	if err != nil || cons == nil {
		return err
	}
	err = cons.Nack()
	if err != nil {
		log.Printf("[ERR] relay: consumer failed nack: %v", err)
		rc.discard(cons)
	}
	return err
}

// ConsumeAck is used to consume with automatic ack. The consume operation
// is able to be retried, but if a message is consumed and the acknowledgement
// fails, the broker will re-deliver the message. Because of this, it is
// critical that the returned error is checked, as it is possible that both
// the output is written and an error is encountered if we see connection
// loss between Consume() and Ack().
func (rc *retryConsumer) ConsumeAck(out interface{}) error {
	if err := rc.Consume(out); err != nil {
		return err
	}
	return rc.Ack()
}

// Consume consumes a single message from the queue.
func (rc *retryConsumer) Consume(out interface{}) error {
	return rc.ConsumeTimeout(out, -1)
}

// ConsumeTimeout consumes a single message from the queue with an upper bound
// on the time spent waiting.
func (rc *retryConsumer) ConsumeTimeout(out interface{}, timeout time.Duration) error {
	// Record the deadline so we can honor the timeout
	var deadline time.Time
	if timeout >= 0 {
		deadline = time.Now().Add(timeout)
	}

	for i := 0; ; i++ {
		cons, err := rc.consumer(true)
		if err != nil {
			goto RETRY
		}

		err = cons.ConsumeTimeout(out, timeout)
		if err == nil {
			break
		}

		// Respect the case where the consume legitimately times out.
		if err == TimedOut {
			return err
		}

	RETRY:
		log.Printf("[ERR] relay: consumer got error: %v", err)
		rc.discard(cons)
		if i == rc.attempts {
			log.Printf("[ERR] relay: consumer giving up after %d attempts", i)
			return err
		}

		// Check if we are already passed the deadline
		now := time.Now()
		if !deadline.IsZero() && now.After(deadline) {
			log.Printf("[DEBUG] relay: consumer reached deadline")
			return TimedOut
		}

		// Calculate the next wait period
		wait := rc.min * time.Duration(int64(1)<<uint(i))
		if wait > rc.max {
			wait = rc.max
		}

		// Ensure we don't wait past the deadline. Adjust the timeout
		// so that the next call to consume will return earlier.
		if !deadline.IsZero() && now.Add(wait).After(deadline) {
			wait = deadline.Sub(now)
		}
		timeout -= wait

		log.Printf("[DEBUG] relay: consumer retrying in %s", wait)
		time.Sleep(wait)
	}
	return nil
}

// retryPublisher wraps a relay broker and an associated publisher, allowing
// the underlying publisher's channel to be automatically swapped out if any
// errors are seen during a publish.
type retryPublisher struct {
	broker   broker.Broker
	pub      broker.Publisher
	queue    string
	attempts int
	min      time.Duration
	max      time.Duration
	l        sync.RWMutex
}

// publisher is used to connect the publisher to the relay queue.
func (rp *retryPublisher) publisher(create bool) (broker.Publisher, error) {
	// Check for an existing publisher
	rp.l.RLock()
	pub := rp.pub
	rp.l.RUnlock()

	if pub != nil || !create {
		return pub, nil
	}

	// Make a new publisher
	rp.l.Lock()
	defer rp.l.Unlock()

	pub, err := rp.broker.Publisher(rp.queue)
	if err != nil {
		return nil, err
	}
	rp.pub = pub
	return pub, nil
}

// discard is used to remove a broken publisher.
func (rp *retryPublisher) discard(pub broker.Publisher) {
	if pub == nil {
		return
	}

	rp.l.Lock()
	defer rp.l.Unlock()

	pub.Close()
	if pub == rp.pub {
		rp.pub = nil
	}
}

// Close closes the publisher.
func (rp *retryPublisher) Close() error {
	pub, err := rp.publisher(false)
	if err != nil || pub == nil {
		return err
	}
	return pub.Close()
}

// Publish publishes a single message to the queue. If an error is encountered,
// the broker automatically tries to replace the underlying channel and submits
// the message again. If publisher confirms are enabled, this may result in the
// same message being published multiple times. If this is not desirable, the
// config struct allows disabling publisher confirms, which may drop messages.
func (rp *retryPublisher) Publish(in interface{}) error {
	for i := 0; ; i++ {
		pub, err := rp.publisher(true)
		if err != nil {
			goto RETRY
		}

		err = pub.Publish(in)
		if err == nil {
			break
		}

	RETRY:
		log.Printf("[ERR] relay: publisher got error: %v", err)
		rp.discard(pub)
		if i == rp.attempts {
			log.Printf("[ERR] relay: publisher giving up after %d attempts", i)
			return err
		}
		wait := rp.min * (1 << uint(i))
		if wait > rp.max {
			wait = rp.max
		}
		log.Printf("[DEBUG] relay: publisher retrying in %s", wait)
		time.Sleep(wait)
	}
	return nil
}
<commit_msg>retry: fix potential time-of-check vs. time-of-use error<commit_after>package relay

import (
	"errors"
	"log"
	"sync"
	"time"

	"github.com/armon/relay/broker"
)

var (
	// Error returned when an invalid number of attempts is specified. Protects
	// overflows of these values.
	errAttemptsRange = errors.New("Attempts must be between 1 and 32")

	// Error returned if a zero value is given for min/max wait time.
	errWaitRange = errors.New("Min/Max wait times must be positive")
)

// retryBroker implements a resilient AMQP broker which automatically replaces
// the underlying channel if an unexpected error is observed. Due to AMQP's
// persistent connections, this becomes necessary to survive even minor network
// hiccups which may temporarily sever the connection to the server.
//
// The retryBroker does not come without a price, however. Clients are still
// responsible for deduplicating messages or handling them idempotently, as
// duplications are a regular happening in the face of network errors due to
// message redelivery provided by the protocol. It is also important to note
// that acknowledgements - both ack and nack - cannot be retried across
// connection resets. This stems from the fact that a given consumer's channel
// cannot be revived and must be thrown away if the consumer is to be reused.
type retryBroker struct {
	broker   *relayBroker
	attempts int
	min      time.Duration
	max      time.Duration
}

// RetryBroker returns a new retrying broker with the given settings.
func (r *Relay) RetryBroker(attempts int, min, max time.Duration) (*retryBroker, error) {
	if attempts < 1 || attempts > 32 {
		return nil, errAttemptsRange
	}
	if min == 0 || max == 0 {
		return nil, errWaitRange
	}
	return &retryBroker{&relayBroker{r}, attempts, min, max}, nil
}

// Publisher returns a new retrying broker.Publisher.
func (rb *retryBroker) Publisher(queue string) (broker.Publisher, error) {
	return &retryPublisher{
		broker:   rb.broker,
		queue:    queue,
		attempts: rb.attempts,
		min:      rb.min,
		max:      rb.max,
	}, nil
}

// Consumer returns a new retrying broker.Consumer.
func (rb *retryBroker) Consumer(queue string) (broker.Consumer, error) {
	return &retryConsumer{
		broker:   rb.broker,
		queue:    queue,
		attempts: rb.attempts,
		min:      rb.min,
		max:      rb.max,
	}, nil
}

// Close closes the broker.
func (rb *retryBroker) Close() error {
	if rb.broker != nil {
		return rb.broker.Close()
	}
	return nil
}

// retryConsumer tracks the relay broker and an associated consumer, allowing
// the consumer to be replaced transparently in the face of failure.
type retryConsumer struct {
	broker   broker.Broker
	cons     broker.Consumer
	queue    string
	attempts int
	min      time.Duration
	max      time.Duration
	l        sync.RWMutex
}

// consumer is used to connect the consumer to the relay queue.
func (rc *retryConsumer) consumer(create bool) (broker.Consumer, error) {
	// Check for an existing consumer
	rc.l.RLock()
	cons := rc.cons
	rc.l.RUnlock()

	if cons != nil || !create {
		return cons, nil
	}

	// Make a new consumer
	rc.l.Lock()
	defer rc.l.Unlock()

	cons, err := rc.broker.Consumer(rc.queue)
	if err != nil {
		return nil, err
	}
	rc.cons = cons
	return cons, nil
}

// discard is used to remove a broken consumer.
func (rc *retryConsumer) discard(cons broker.Consumer) {
	rc.l.Lock()
	defer rc.l.Unlock()

	if cons == nil {
		return
	}

	cons.Close()
	if cons == rc.cons {
		rc.cons = nil
	}
}

// Close closes the consumer.
func (rc *retryConsumer) Close() error {
	cons, err := rc.consumer(false)
	if err != nil || cons == nil {
		return err
	}
	return cons.Close()
}

// Ack marks message(s) as delivered.
func (rc *retryConsumer) Ack() error {
	cons, err := rc.consumer(false)
	if err != nil || cons == nil {
		return err
	}
	err = cons.Ack()
	if err != nil {
		log.Printf("[ERR] relay: consumer failed ack: %v", err)
		rc.discard(cons)
	}
	return err
}

// Nack sends message(s) back to the queue.
func (rc *retryConsumer) Nack() error {
	cons, err := rc.consumer(false)
	if err != nil || cons == nil {
		return err
	}
	err = cons.Nack()
	if err != nil {
		log.Printf("[ERR] relay: consumer failed nack: %v", err)
		rc.discard(cons)
	}
	return err
}

// ConsumeAck is used to consume with automatic ack. The consume operation
// is able to be retried, but if a message is consumed and the acknowledgement
// fails, the broker will re-deliver the message. Because of this, it is
// critical that the returned error is checked, as it is possible that both
// the output is written and an error is encountered if we see connection
// loss between Consume() and Ack().
func (rc *retryConsumer) ConsumeAck(out interface{}) error {
	if err := rc.Consume(out); err != nil {
		return err
	}
	return rc.Ack()
}

// Consume consumes a single message from the queue.
func (rc *retryConsumer) Consume(out interface{}) error {
	return rc.ConsumeTimeout(out, -1)
}

// ConsumeTimeout consumes a single message from the queue with an upper bound
// on the time spent waiting.
func (rc *retryConsumer) ConsumeTimeout(out interface{}, timeout time.Duration) error {
	// Record the deadline so we can honor the timeout
	var deadline time.Time
	if timeout >= 0 {
		deadline = time.Now().Add(timeout)
	}

	for i := 0; ; i++ {
		cons, err := rc.consumer(true)
		if err != nil {
			goto RETRY
		}

		err = cons.ConsumeTimeout(out, timeout)
		if err == nil {
			break
		}

		// Respect the case where the consume legitimately times out.
		if err == TimedOut {
			return err
		}

	RETRY:
		log.Printf("[ERR] relay: consumer got error: %v", err)
		rc.discard(cons)
		if i == rc.attempts {
			log.Printf("[ERR] relay: consumer giving up after %d attempts", i)
			return err
		}

		// Check if we are already passed the deadline
		now := time.Now()
		if !deadline.IsZero() && now.After(deadline) {
			log.Printf("[DEBUG] relay: consumer reached deadline")
			return TimedOut
		}

		// Calculate the next wait period
		wait := rc.min * time.Duration(int64(1)<<uint(i))
		if wait > rc.max {
			wait = rc.max
		}

		// Ensure we don't wait past the deadline. Adjust the timeout
		// so that the next call to consume will return earlier.
		if !deadline.IsZero() && now.Add(wait).After(deadline) {
			wait = deadline.Sub(now)
		}
		timeout -= wait

		log.Printf("[DEBUG] relay: consumer retrying in %s", wait)
		time.Sleep(wait)
	}
	return nil
}

// retryPublisher wraps a relay broker and an associated publisher, allowing
// the underlying publisher's channel to be automatically swapped out if any
// errors are seen during a publish.
type retryPublisher struct {
	broker   broker.Broker
	pub      broker.Publisher
	queue    string
	attempts int
	min      time.Duration
	max      time.Duration
	l        sync.RWMutex
}

// publisher is used to connect the publisher to the relay queue.
func (rp *retryPublisher) publisher(create bool) (broker.Publisher, error) {
	// Check for an existing publisher
	rp.l.RLock()
	pub := rp.pub
	rp.l.RUnlock()

	if pub != nil || !create {
		return pub, nil
	}

	// Make a new publisher
	rp.l.Lock()
	defer rp.l.Unlock()

	pub, err := rp.broker.Publisher(rp.queue)
	if err != nil {
		return nil, err
	}
	rp.pub = pub
	return pub, nil
}

// discard is used to remove a broken publisher.
func (rp *retryPublisher) discard(pub broker.Publisher) {
	rp.l.Lock()
	defer rp.l.Unlock()

	if pub == nil {
		return
	}

	pub.Close()
	if pub == rp.pub {
		rp.pub = nil
	}
}

// Close closes the publisher.
func (rp *retryPublisher) Close() error {
	pub, err := rp.publisher(false)
	if err != nil || pub == nil {
		return err
	}
	return pub.Close()
}

// Publish publishes a single message to the queue. If an error is encountered,
// the broker automatically tries to replace the underlying channel and submits
// the message again. If publisher confirms are enabled, this may result in the
// same message being published multiple times. If this is not desirable, the
// config struct allows disabling publisher confirms, which may drop messages.
func (rp *retryPublisher) Publish(in interface{}) error {
	for i := 0; ; i++ {
		pub, err := rp.publisher(true)
		if err != nil {
			goto RETRY
		}

		err = pub.Publish(in)
		if err == nil {
			break
		}

	RETRY:
		log.Printf("[ERR] relay: publisher got error: %v", err)
		rp.discard(pub)
		if i == rp.attempts {
			log.Printf("[ERR] relay: publisher giving up after %d attempts", i)
			return err
		}
		wait := rp.min * (1 << uint(i))
		if wait > rp.max {
			wait = rp.max
		}
		log.Printf("[DEBUG] relay: publisher retrying in %s", wait)
		time.Sleep(wait)
	}
	return nil
}
<|endoftext|>


<commit_before>package hal

import (
	"net/http"
	"os"
	"os/signal"
	"syscall"
)

// Robot receives messages from an adapter and sends them to listeners
type Robot struct {
	Name       string
	Alias      string
	Adapter    Adapter
	Store      Store
	handlers   []handler
	Users      *UserMap
	signalChan chan os.Signal
}

// Handlers returns the robot's handlers
func (robot *Robot) Handlers() []handler {
	return robot.handlers
}

// NewRobot returns a new Robot instance
func NewRobot() (*Robot, error) {
	robot := &Robot{
		Name:       Config.Name,
		Alias:      Config.Alias,
		signalChan: make(chan os.Signal, 1),
	}

	adapter, err := NewAdapter(robot)
	if err != nil {
		Logger.Error(err)
		return nil, err
	}
	robot.SetAdapter(adapter)

	store, err := NewStore(robot)
	if err != nil {
		Logger.Error(err)
		return nil, err
	}
	robot.SetStore(store)
	robot.Users = NewUserMap(robot)

	return robot, nil
}

// Handle registers a new handler with the robot
func (robot *Robot) Handle(handlers ...handler) {
	for _, h := range handlers {
		robot.handlers = append(robot.handlers, NewHandler(h))
	}
	// robot.handlers = append(robot.handlers, handlers...)
}

// Receive dispatches messages to our handlers
func (robot *Robot) Receive(msg *Message) error {
	Logger.Debugf("%s - robot received message", Config.AdapterName)

	// check if we've seen this user yet, and add if we haven't.
	user := msg.User
	if _, err := robot.Users.Get(user.ID); err != nil {
		Logger.Debug(err)
		robot.Users.Set(user.ID, user)
		robot.Users.Save()
	}

	for _, handler := range robot.handlers {
		response := NewResponse(robot, msg)
		err := handler.Handle(response)
		if err != nil {
			Logger.Error(err)
			return err
		}
	}
	return nil
}

// Run initiates the startup process
func (robot *Robot) Run() error {
	Logger.Info("starting robot")

	// HACK
	Logger.Debugf("opening %s store connection", Config.StoreName)
	go func() {
		robot.Store.Open()

		Logger.Debug("loading users from store")
		robot.Users.Load()
	}()

	Logger.Debugf("starting %s adapter", Config.AdapterName)
	go robot.Adapter.Run()

	// Start the HTTP server after the adapter, as adapter.Run() adds additional
	// handlers to the router.
	Logger.Debug("starting HTTP server")
	go http.ListenAndServe(`:`+string(Config.Port), Router)

	signal.Notify(robot.signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	stop := false
	for !stop {
		select {
		case sig := <-robot.signalChan:
			switch sig {
			case syscall.SIGINT, syscall.SIGTERM:
				stop = true
			}
		}
	}
	// Stop listening for new signals
	signal.Stop(robot.signalChan)

	// Initiate the shutdown process for our robot
	robot.Stop()

	return nil
}

// Stop initiates the shutdown process
func (robot *Robot) Stop() error {
	Logger.Info() // so we don't break up the log formatting when running interactively ;)

	Logger.Debugf("stopping %s adapter", Config.AdapterName)
	if err := robot.Adapter.Stop(); err != nil {
		return err
	}

	Logger.Debugf("closing %s store connection", Config.StoreName)
	if err := robot.Store.Close(); err != nil {
		return err
	}

	Logger.Info("stopping robot")
	return nil
}

// SetName sets robot's name
func (robot *Robot) SetName(name string) {
	robot.Name = name
}

// SetAdapter sets robot's adapter
func (robot *Robot) SetAdapter(adapter Adapter) {
	robot.Adapter = adapter
}

// SetStore sets robot's adapter
func (robot *Robot) SetStore(store Store) {
	robot.Store = store
}
<commit_msg>fix casting for HTTP port<commit_after>package hal

import (
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"syscall"
)

// Robot receives messages from an adapter and sends them to listeners
type Robot struct {
	Name       string
	Alias      string
	Adapter    Adapter
	Store      Store
	handlers   []handler
	Users      *UserMap
	signalChan chan os.Signal
}

// Handlers returns the robot's handlers
func (robot *Robot) Handlers() []handler {
	return robot.handlers
}

// NewRobot returns a new Robot instance
func NewRobot() (*Robot, error) {
	robot := &Robot{
		Name:       Config.Name,
		Alias:      Config.Alias,
		signalChan: make(chan os.Signal, 1),
	}

	adapter, err := NewAdapter(robot)
	if err != nil {
		Logger.Error(err)
		return nil, err
	}
	robot.SetAdapter(adapter)

	store, err := NewStore(robot)
	if err != nil {
		Logger.Error(err)
		return nil, err
	}
	robot.SetStore(store)
	robot.Users = NewUserMap(robot)

	return robot, nil
}

// Handle registers a new handler with the robot
func (robot *Robot) Handle(handlers ...handler) {
	for _, h := range handlers {
		robot.handlers = append(robot.handlers, NewHandler(h))
	}
	// robot.handlers = append(robot.handlers, handlers...)
}

// Receive dispatches messages to our handlers
func (robot *Robot) Receive(msg *Message) error {
	Logger.Debugf("%s - robot received message", Config.AdapterName)

	// check if we've seen this user yet, and add if we haven't.
	user := msg.User
	if _, err := robot.Users.Get(user.ID); err != nil {
		Logger.Debug(err)
		robot.Users.Set(user.ID, user)
		robot.Users.Save()
	}

	for _, handler := range robot.handlers {
		response := NewResponse(robot, msg)
		err := handler.Handle(response)
		if err != nil {
			Logger.Error(err)
			return err
		}
	}
	return nil
}

// Run initiates the startup process
func (robot *Robot) Run() error {
	Logger.Info("starting robot")

	// HACK
	Logger.Debugf("opening %s store connection", Config.StoreName)
	go func() {
		robot.Store.Open()

		Logger.Debug("loading users from store")
		robot.Users.Load()
	}()

	Logger.Debugf("starting %s adapter", Config.AdapterName)
	go robot.Adapter.Run()

	// Start the HTTP server after the adapter, as adapter.Run() adds additional
	// handlers to the router.
	Logger.Debug("starting HTTP server")
	go func() {
		if err := http.ListenAndServe(`:`+strconv.Itoa(Config.Port), Router); err != nil {
			Logger.Debug(err)
		}
	}()

	signal.Notify(robot.signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	stop := false
	for !stop {
		select {
		case sig := <-robot.signalChan:
			switch sig {
			case syscall.SIGINT, syscall.SIGTERM:
				stop = true
			}
		}
	}
	// Stop listening for new signals
	signal.Stop(robot.signalChan)

	// Initiate the shutdown process for our robot
	robot.Stop()

	return nil
}

// Stop initiates the shutdown process
func (robot *Robot) Stop() error {
	Logger.Info() // so we don't break up the log formatting when running interactively ;)

	Logger.Debugf("stopping %s adapter", Config.AdapterName)
	if err := robot.Adapter.Stop(); err != nil {
		return err
	}

	Logger.Debugf("closing %s store connection", Config.StoreName)
	if err := robot.Store.Close(); err != nil {
		return err
	}

	Logger.Info("stopping robot")
	return nil
}

// SetName sets robot's name
func (robot *Robot) SetName(name string) {
	robot.Name = name
}

// SetAdapter sets robot's adapter
func (robot *Robot) SetAdapter(adapter Adapter) {
	robot.Adapter = adapter
}

// SetStore sets robot's adapter
func (robot *Robot) SetStore(store Store) {
	robot.Store = store
}
<|endoftext|>


<commit_before>package router

import (
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

const (
	DefaultConverter = "any"

	HttpMethodGet    = "GET"
	HttpMethodPost   = "POST"
	HttpMethodPut    = "PUT"
	HttpMethodDelete = "DELETE"
)

var (
	DefaultReplacer = []string{"int", "\\d+", "string", "[\\w\\-]+", "any", "[^/]+"}
)

type Route struct {
	pattern     string
	regexp      *regexp.Regexp
	handler     http.Handler
	name        string
	methods     []string
	strictSlash bool
	middleware  []http.Handler
}

type MatchedRoute struct {
	route   *Route
	handler http.Handler
	params  url.Values
}

func NewRoute(pattern string, handler http.Handler) *Route {
	route := &Route{pattern: pattern, handler: handler}
	route.parsePattern()
	return route
}

func (r *Route) Name(name string) *Route {
	r.name = name
	return r
}

func (r *Route) Methods(v ...string) *Route {
	for _, method := range v {
		r.methods = append(r.methods, strings.ToUpper(method))
	}
	return r
}

func (r *Route) StrictSlash(strictSlash bool) *Route {
	r.strictSlash = strictSlash
	return r
}

func (r *Route) Middleware(middleware ...http.Handler) *Route {
	r.middleware = append(r.middleware, middleware...)
	return r
}

func (r *Route) parsePattern() {
	if r.regexp != nil {
		return
	}

	pattern := r.pattern
	buf := make([]byte, 0, len(pattern)+3)
	buf = append(buf, '^')

	if strings.ContainsRune(pattern, '<') && strings.ContainsRune(pattern, '>') {
		re := regexp.MustCompile("<[^>]+>")
		reString := re.ReplaceAllStringFunc(pattern, func(in string) string {
			converter := DefaultConverter
			var name string
			if segments := strings.SplitN(in, ":", 2); len(segments) == 2 {
				converter, name = segments[0][1:], segments[1][:len(segments[1])-1]
			} else {
				name = segments[0][1 : len(segments[0])-1]
			}
			return fmt.Sprintf("(?P<%s>%s)", name, strings.NewReplacer(DefaultReplacer...).Replace(converter))
		})
		buf = append(buf, reString...)
	} else {
		buf = append(buf, pattern...)
	}

	if strings.HasSuffix(pattern, "/") {
		buf = append(buf, "?$"...)
	} else {
		buf = append(buf, "/?$"...)
	}
	r.regexp = regexp.MustCompile(string(buf))
}

func (r *Route) supportsHttpMethod(httpMethod string) bool {
	httpMethod = strings.ToUpper(httpMethod)
	if len(r.methods) == 0 {
		if httpMethod == HttpMethodGet {
			return true
		}
	} else {
		for _, method := range r.methods {
			if httpMethod == method {
				return true
			}
		}
	}
	return false
}

func (r *Route) matches(resourceUri string) (MatchedRoute, bool) {
	matched := MatchedRoute{params: url.Values{}}

	if out := r.regexp.FindAllStringSubmatch(resourceUri, -1); out != nil {
		if r.strictSlash {
			p1 := strings.HasSuffix(r.pattern, "/")
			p2 := strings.HasSuffix(resourceUri, "/")
			if p1 != p2 {
				if p1 {
					resourceUri += "/"
				} else {
					resourceUri = resourceUri[:len(resourceUri)-1]
				}
				matched.handler = http.RedirectHandler(resourceUri, http.StatusMovedPermanently)
				return matched, true
			}
		}

		names := r.regexp.SubexpNames()
		for k, v := range out[0] {
			if k > 0 {
				matched.params.Add((names[k]), v)
			}
		}
		matched.handler = r.handler
		return matched, true
	}
	return matched, false
}
<commit_msg>fix bug<commit_after>package router

import (
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

const (
	DefaultConverter = "any"

	HttpMethodGet    = "GET"
	HttpMethodPost   = "POST"
	HttpMethodPut    = "PUT"
	HttpMethodDelete = "DELETE"
)

var (
	DefaultReplacer = []string{"int", "\\d+", "string", "[\\w\\-]+", "any", "[^/]+"}
)

type Route struct {
	pattern     string
	regexp      *regexp.Regexp
	handler     http.Handler
	name        string
	methods     []string
	strictSlash bool
	middleware  []http.Handler
}

type MatchedRoute struct {
	route   *Route
	handler http.Handler
	params  url.Values
}

func NewRoute(pattern string, handler http.Handler) *Route {
	route := &Route{pattern: pattern, handler: handler}
	route.parsePattern()
	return route
}

func (r *Route) Name(name string) *Route {
	r.name = name
	return r
}

func (r *Route) Methods(v ...string) *Route {
	for _, method := range v {
		r.methods = append(r.methods, strings.ToUpper(method))
	}
	return r
}

func (r *Route) StrictSlash(strictSlash bool) *Route {
	r.strictSlash = strictSlash
	return r
}

func (r *Route) Middleware(middleware ...http.Handler) *Route {
	r.middleware = append(r.middleware, middleware...)
	return r
}

func (r *Route) parsePattern() {
	if r.regexp != nil {
		return
	}

	pattern := r.pattern
	buf := make([]byte, 0, len(pattern)+3)
	buf = append(buf, '^')

	if strings.ContainsRune(pattern, '<') && strings.ContainsRune(pattern, '>') {
		re := regexp.MustCompile("<[^>]+>")
		reString := re.ReplaceAllStringFunc(pattern, func(in string) string {
			converter := DefaultConverter
			var name string
			if segments := strings.SplitN(in, ":", 2); len(segments) == 2 {
				converter, name = segments[0][1:], segments[1][:len(segments[1])-1]
			} else {
				name = segments[0][1 : len(segments[0])-1]
			}
			return fmt.Sprintf("(?P<%s>%s)", name, strings.NewReplacer(DefaultReplacer...).Replace(converter))
		})
		buf = append(buf, reString...)
	} else {
		buf = append(buf, pattern...)
	}

	if strings.HasSuffix(pattern, "/") {
		buf = append(buf, "?$"...)
	} else {
		buf = append(buf, "/?$"...)
	}
	r.regexp = regexp.MustCompile(string(buf))
}

func (r *Route) supportsHttpMethod(httpMethod string) bool {
	httpMethod = strings.ToUpper(httpMethod)
	if len(r.methods) == 0 {
		if httpMethod == HttpMethodGet {
			return true
		}
	} else {
		for _, method := range r.methods {
			if httpMethod == method {
				return true
			}
		}
	}
	return false
}

func (r *Route) matches(resourceUri string) (MatchedRoute, bool) {
	matched := MatchedRoute{params: url.Values{}, route: r}

	if out := r.regexp.FindAllStringSubmatch(resourceUri, -1); out != nil {
		if r.strictSlash {
			p1 := strings.HasSuffix(r.pattern, "/")
			p2 := strings.HasSuffix(resourceUri, "/")
			if p1 != p2 {
				if p1 {
					resourceUri += "/"
				} else {
					resourceUri = resourceUri[:len(resourceUri)-1]
				}
				matched.handler = http.RedirectHandler(resourceUri, http.StatusMovedPermanently)
				return matched, true
			}
		}

		names := r.regexp.SubexpNames()
		for k, v := range out[0] {
			if k > 0 {
				matched.params.Add((names[k]), v)
			}
		}
		matched.handler = r.handler
		return matched, true
	}
	return matched, false
}
<|endoftext|>


<commit_before>package udnssdk

import (
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// RRSetsService provides access to RRSet resources
type RRSetsService struct {
	client *Client
}

// Here is the big 'Profile' mess that should get refactored to a more managable place

// StringProfile wraps a Profile string
type StringProfile struct {
	Profile string `json:"profile,omitempty"`
}

// Metaprofile is a helper struct for extracting a Context from a StringProfile
type Metaprofile struct {
	Context ProfileSchema `json:"@context"`
}

// ProfileSchema are the schema URIs for RRSet Profiles
type ProfileSchema string

const (
	// DirPoolSchema is the schema URI for a Directional pool profile
	DirPoolSchema ProfileSchema = "http://schemas.ultradns.com/DirPool.jsonschema"
	// RDPoolSchema is the schema URI for a Resource Distribution pool profile
	RDPoolSchema = "http://schemas.ultradns.com/RDPool.jsonschema"
	// SBPoolSchema is the schema URI for a SiteBacker pool profile
	SBPoolSchema = "http://schemas.ultradns.com/SBPool.jsonschema"
	// TCPoolSchema is the schema URI for a Traffic Controller pool profile
	TCPoolSchema = "http://schemas.ultradns.com/TCPool.jsonschema"
)

// DirPoolProfile wraps a Profile for a Directional Pool
type DirPoolProfile struct {
	Context         ProfileSchema `json:"@context"`
	Description     string        `json:"description"`
	ConflictResolve string        `json:"conflictResolve,omitempty"`
	RDataInfo       []DPRDataInfo `json:"rdataInfo"`
	NoResponse      DPRDataInfo   `json:"noResponse"`
}

// DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response
type DPRDataInfo struct {
	AllNonConfigured bool    `json:"allNonConfigured,omitempty"`
	IPInfo           IPInfo  `json:"ipInfo,omitempty"`
	GeoInfo          GeoInfo `json:"geoInfo,omitempty"`
}

// IPInfo wraps the ipInfo object of a DPRDataInfo
type IPInfo struct {
	Name           string      `json:"name"`
	IsAccountLevel bool        `json:"isAccountLevel,omitempty"`
	Ips            []IPAddrDTO `json:"ips"`
}

// GeoInfo wraps the geoInfo object of a DPRDataInfo
type GeoInfo struct {
	Name           string   `json:"name"`
	IsAccountLevel bool     `json:"isAccountLevel,omitempty"`
	Codes          []string `json:"codes"`
}

// RDPoolProfile wraps a Profile for a Resource Distribution pool
type RDPoolProfile struct {
	Context     ProfileSchema `json:"@context"`
	Order       string        `json:"order"`
	Description string        `json:"description"`
}

// SBPoolProfile wraps a Profile for a SiteBacker pool
type SBPoolProfile struct {
	Context       ProfileSchema  `json:"@context"`
	Description   string         `json:"description"`
	RunProbes     bool           `json:"runProbes,omitempty"`
	ActOnProbes   bool           `json:"actOnProbes,omitempty"`
	Order         string         `json:"order,omitempty"`
	MaxActive     int            `json:"maxActive,omitempty"`
	MaxServed     int            `json:"maxServed,omitempty"`
	RDataInfo     []SBRDataInfo  `json:"rdataInfo"`
	BackupRecords []BackupRecord `json:"backupRecords"`
}

// SBRDataInfo wraps the rdataInfo object of a SBPoolProfile
type SBRDataInfo struct {
	State         string `json:"state"`
	RunProbes     bool   `json:"runProbes,omitempty"`
	Priority      int    `json:"priority"`
	FailoverDelay int    `json:"failoverDelay,omitempty"`
	Threshold     int    `json:"threshold"`
	Weight        int    `json:"weight"`
}

// BackupRecord wraps the backupRecord objects of an SBPoolProfile response
type BackupRecord struct {
	RData         string `json:"rdata"`
	FailoverDelay int    `json:"failoverDelay,omitempty"`
}

// TCPoolProfile wraps a Profile for a Traffic Controller pool
type TCPoolProfile struct {
	Context      ProfileSchema `json:"@context"`
	Description  string        `json:"description"`
	RunProbes    bool          `json:"runProbes,omitempty"`
	ActOnProbes  bool          `json:"actOnProbes,omitempty"`
	MaxToLB      int           `json:"maxToLB,omitempty"`
	RDataInfo    []SBRDataInfo `json:"rdataInfo"`
	BackupRecord BackupRecord  `json:"backupRecord"`
}

// UnmarshalJSON does what it says on the tin
func (sp *StringProfile) UnmarshalJSON(b []byte) (err error) {
	sp.Profile = string(b)
	return nil
}

// MarshalJSON does what it says on the tin
func (sp *StringProfile) MarshalJSON() ([]byte, error) {
	if sp.Profile != "" {
		return []byte(sp.Profile), nil
	}
	return json.Marshal(nil)
}

// GetType extracts the schema context from a StringProfile
func (sp *StringProfile) GetType() string {
	return string(sp.Context())
}

// Metaprofile converts a StringProfile to a Metaprofile to extract the context
func (sp *StringProfile) Metaprofile() (Metaprofile, error) {
	var mp Metaprofile
	if sp.Profile == "" {
		return mp, fmt.Errorf("Empty Profile cannot be converted to a Metaprofile")
	}
	err := json.Unmarshal([]byte(sp.Profile), &mp)
	if err != nil {
		return mp, fmt.Errorf("Error getting profile type: %+v\n", err)
	}
	return mp, nil
}

// Context extracts the schema context from a StringProfile
func (sp *StringProfile) Context() ProfileSchema {
	mp, err := sp.Metaprofile()
	if err != nil {
		log.Printf("[ERROR] %+s\n", err)
		return ""
	}
	return mp.Context
}

// GoString returns the StringProfile's Profile.
func (sp *StringProfile) GoString() string {
	return sp.Profile
}

// String returns the StringProfile's Profile.
func (sp *StringProfile) String() string {
	return sp.Profile
}

// GetProfileObject extracts the full Profile by its schema type
func (sp *StringProfile) GetProfileObject() interface{} {
	c := sp.Context()
	switch c {
	case DirPoolSchema:
		var dpp DirPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &dpp)
		if err != nil {
			log.Printf("Could not Unmarshal the DirPoolProfile.\n")
			return nil
		}
		return dpp
	case RDPoolSchema:
		var rdp RDPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &rdp)
		if err != nil {
			log.Printf("Could not Unmarshal the RDPoolProfile.\n")
			return nil
		}
		return rdp
	case SBPoolSchema:
		var sbp SBPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &sbp)
		if err != nil {
			log.Printf("Could not Unmarshal the SBPoolProfile.\n")
			return nil
		}
		return sbp
	case TCPoolSchema:
		var tcp TCPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &tcp)
		if err != nil {
			log.Printf("Could not Unmarshal the TCPoolProfile.\n")
			return nil
		}
		return tcp
	default:
		log.Printf("ERROR - Fall through on GetProfileObject - %s.\n", c)
		return fmt.Errorf("Fallthrough on GetProfileObject type %s\n", c)
	}
}

// RRSet wraps an RRSet resource
type RRSet struct {
	OwnerName string         `json:"ownerName"`
	RRType    string         `json:"rrtype"`
	TTL       int            `json:"ttl"`
	RData     []string       `json:"rdata"`
	Profile   *StringProfile `json:"profile,omitempty"`
}

// RRSetListDTO wraps a list of RRSet resources
type RRSetListDTO struct {
	ZoneName   string     `json:"zoneName"`
	Rrsets     []RRSet    `json:"rrsets"`
	Queryinfo  QueryInfo  `json:"queryInfo"`
	Resultinfo ResultInfo `json:"resultInfo"`
}

// rrsetPath generates the resource path for given rrset that belongs to a zone.
func rrsetPath(zone string, rrtype interface{}, rrset interface{}) string {
	r := RRSetKey{
		Zone: zone,
		Type: rrtype.(string),
		Name: rrset.(string),
	}
	return r.URI()
}

func rrsetQueryPath(zone, rrsetName, rrsetType string, offset int) string {
	r := RRSetKey{
		Zone: zone,
		Type: rrsetType,
		Name: rrsetType,
	}
	return r.QueryURI(offset)
}

// ListAllRRSets will list the zone rrsets, paginating through all available results
func (s *RRSetsService) ListAllRRSets(zone string, rrsetName, rrsetType string) ([]RRSet, error) {
	r := RRSetKey{
		Zone: zone,
		Type: rrsetType,
		Name: rrsetType,
	}
	return s.Select(r)
}

// ListRRSets requests zone rrsets by zone, rrsetName, rrsetType & optional offset
func (s *RRSetsService) ListRRSets(zone, rrsetName, rrsetType string, offset int) ([]RRSet, ResultInfo, *Response, error) {
	r := RRSetKey{
		Zone: zone,
		Type: rrsetType,
		Name: rrsetType,
	}
	return s.SelectWithOffset(r, offset)
}

// CreateRRSet creates a zone rrset.
func (s *RRSetsService) CreateRRSet(zone string, rrsetAttributes RRSet) (*Response, error) {
	r := RRSetKey{
		Zone: zone,
		Type: rrsetAttributes.RRType,
		Name: rrsetAttributes.OwnerName,
	}
	return s.Create(r, rrsetAttributes)
}

// UpdateRRSet updates a zone rrset.
func (s *RRSetsService) UpdateRRSet(zone string, rrsetAttributes RRSet) (*Response, error) {
	r := RRSetKey{
		Zone: zone,
		Type: rrsetAttributes.RRType,
		Name: rrsetAttributes.OwnerName,
	}
	return s.Update(r, rrsetAttributes)
}

// DeleteRRSet deletes a zone rrset.
func (s *RRSetsService) DeleteRRSet(zone string, rrsetAttributes RRSet) (*Response, error) {
	r := RRSetKey{
		Zone: zone,
		Type: rrsetAttributes.RRType,
		Name: rrsetAttributes.OwnerName,
	}
	return s.Delete(r)
}

// ===== //

// RRSetKey collects the identifiers of a Zone
type RRSetKey struct {
	Zone string
	Type string
	Name string
}

// URI generates the URI for an RRSet
func (r *RRSetKey) URI() string {
	uri := fmt.Sprintf("zones/%s/rrsets", r.Zone)
	if r.Type != "" {
		uri += fmt.Sprintf("/%v", r.Type)
		if r.Name != "" {
			uri += fmt.Sprintf("/%v", r.Name)
		}
	}
	return uri
}

// QueryURI generates the query URI for an RRSet and offset
func (r *RRSetKey) QueryURI(offset int) string {
	// TODO: find a more appropriate place to set "" to "ANY"
	if r.Type == "" {
		r.Type = "ANY"
	}
	return fmt.Sprintf("%s?offset=%d", r.URI(), offset)
}

// EventsURI generates the URI for an RRSet
func (r *RRSetKey) EventsURI() string {
	return fmt.Sprintf("%s/events", r.URI())
}

// EventsQueryURI generates the events query URI for an RRSet with query
func (r *RRSetKey) EventsQueryURI(query string, offset int) string {
	uri := r.EventsURI()
	if query != "" {
		return fmt.Sprintf("%s?sort=NAME&query=%s&offset=%d", uri, query, offset)
	}
	if offset != 0 {
		return fmt.Sprintf("%s?offset=%d", uri, offset)
	}
	return uri
}

// Select will list the zone rrsets, paginating through all available results
func (s *RRSetsService) Select(r RRSetKey) ([]RRSet, error) {
	// TODO: Sane Configuration for timeouts / retries
	maxerrs := 5
	waittime := 5 * time.Second

	rrsets := []RRSet{}
	errcnt := 0
	offset := 0

	for {
		reqRrsets, ri, res, err := s.SelectWithOffset(r, offset)
		if err != nil {
			if res.StatusCode >= 500 {
				errcnt = errcnt + 1
				if errcnt < maxerrs {
					time.Sleep(waittime)
					continue
				}
			}
			return rrsets, err
		}

		log.Printf("ResultInfo: %+v\n", ri)
		for _, rrset := range reqRrsets {
			rrsets = append(rrsets, rrset)
		}
		if ri.ReturnedCount+ri.Offset >= ri.TotalCount {
			return rrsets, nil
		}
		offset = ri.ReturnedCount + ri.Offset
		continue
	}
}

// SelectWithOffset requests zone rrsets by RRSetKey & optional offset
func (s *RRSetsService) SelectWithOffset(r RRSetKey, offset int) ([]RRSet, ResultInfo, *Response, error) {
	var rrsld RRSetListDTO

	uri := r.QueryURI(offset)
	res, err := s.client.get(uri, &rrsld)

	rrsets := []RRSet{}
	for _, rrset := range rrsld.Rrsets {
		rrsets = append(rrsets, rrset)
	}
	return rrsets, rrsld.Resultinfo, res, err
}

// Create creates an rrset with val
func (s *RRSetsService) Create(r RRSetKey, rrset RRSet) (*Response, error) {
	var ignored interface{}
	return s.client.post(r.URI(), rrset, &ignored)
}

// Update updates a RRSet with the provided val
func (s *RRSetsService) Update(r RRSetKey, val RRSet) (*Response, error) {
	var ignored interface{}
	return s.client.put(r.URI(), val, &ignored)
}

// Delete deletes an RRSet
func (s *RRSetsService) Delete(r RRSetKey) (*Response, error) {
	return s.client.delete(r.URI(), nil)
}
<commit_msg>rrset.go: remove old interface<commit_after>package udnssdk

import (
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// RRSetsService provides access to RRSet resources
type RRSetsService struct {
	client *Client
}

// Here is the big 'Profile' mess that should get refactored to a more managable place

// StringProfile wraps a Profile string
type StringProfile struct {
	Profile string `json:"profile,omitempty"`
}

// Metaprofile is a helper struct for extracting a Context from a StringProfile
type Metaprofile struct {
	Context ProfileSchema `json:"@context"`
}

// ProfileSchema are the schema URIs for RRSet Profiles
type ProfileSchema string

const (
	// DirPoolSchema is the schema URI for a Directional pool profile
	DirPoolSchema ProfileSchema = "http://schemas.ultradns.com/DirPool.jsonschema"
	// RDPoolSchema is the schema URI for a Resource Distribution pool profile
	RDPoolSchema = "http://schemas.ultradns.com/RDPool.jsonschema"
	// SBPoolSchema is the schema URI for a SiteBacker pool profile
	SBPoolSchema = "http://schemas.ultradns.com/SBPool.jsonschema"
	// TCPoolSchema is the schema URI for a Traffic Controller pool profile
	TCPoolSchema = "http://schemas.ultradns.com/TCPool.jsonschema"
)

// DirPoolProfile wraps a Profile for a Directional Pool
type DirPoolProfile struct {
	Context         ProfileSchema `json:"@context"`
	Description     string        `json:"description"`
	ConflictResolve string        `json:"conflictResolve,omitempty"`
	RDataInfo       []DPRDataInfo `json:"rdataInfo"`
	NoResponse      DPRDataInfo   `json:"noResponse"`
}

// DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response
type DPRDataInfo struct {
	AllNonConfigured bool    `json:"allNonConfigured,omitempty"`
	IPInfo           IPInfo  `json:"ipInfo,omitempty"`
	GeoInfo          GeoInfo `json:"geoInfo,omitempty"`
}

// IPInfo wraps the ipInfo object of a DPRDataInfo
type IPInfo struct {
	Name           string      `json:"name"`
	IsAccountLevel bool        `json:"isAccountLevel,omitempty"`
	Ips            []IPAddrDTO `json:"ips"`
}

// GeoInfo wraps the geoInfo object of a DPRDataInfo
type GeoInfo struct {
	Name           string   `json:"name"`
	IsAccountLevel bool     `json:"isAccountLevel,omitempty"`
	Codes          []string `json:"codes"`
}

// RDPoolProfile wraps a Profile for a Resource Distribution pool
type RDPoolProfile struct {
	Context     ProfileSchema `json:"@context"`
	Order       string        `json:"order"`
	Description string        `json:"description"`
}

// SBPoolProfile wraps a Profile for a SiteBacker pool
type SBPoolProfile struct {
	Context       ProfileSchema  `json:"@context"`
	Description   string         `json:"description"`
	RunProbes     bool           `json:"runProbes,omitempty"`
	ActOnProbes   bool           `json:"actOnProbes,omitempty"`
	Order         string         `json:"order,omitempty"`
	MaxActive     int            `json:"maxActive,omitempty"`
	MaxServed     int            `json:"maxServed,omitempty"`
	RDataInfo     []SBRDataInfo  `json:"rdataInfo"`
	BackupRecords []BackupRecord `json:"backupRecords"`
}

// SBRDataInfo wraps the rdataInfo object of a SBPoolProfile
type SBRDataInfo struct {
	State         string `json:"state"`
	RunProbes     bool   `json:"runProbes,omitempty"`
	Priority      int    `json:"priority"`
	FailoverDelay int    `json:"failoverDelay,omitempty"`
	Threshold     int    `json:"threshold"`
	Weight        int    `json:"weight"`
}

// BackupRecord wraps the backupRecord objects of an SBPoolProfile response
type BackupRecord struct {
	RData         string `json:"rdata"`
	FailoverDelay int    `json:"failoverDelay,omitempty"`
}

// TCPoolProfile wraps a Profile for a Traffic Controller pool
type TCPoolProfile struct {
	Context      ProfileSchema `json:"@context"`
	Description  string        `json:"description"`
	RunProbes    bool          `json:"runProbes,omitempty"`
	ActOnProbes  bool          `json:"actOnProbes,omitempty"`
	MaxToLB      int           `json:"maxToLB,omitempty"`
	RDataInfo    []SBRDataInfo `json:"rdataInfo"`
	BackupRecord BackupRecord  `json:"backupRecord"`
}

// UnmarshalJSON does what it says on the tin
func (sp *StringProfile) UnmarshalJSON(b []byte) (err error) {
	sp.Profile = string(b)
	return nil
}

// MarshalJSON does what it says on the tin
func (sp *StringProfile) MarshalJSON() ([]byte, error) {
	if sp.Profile != "" {
		return []byte(sp.Profile), nil
	}
	return json.Marshal(nil)
}

// Metaprofile converts a StringProfile to a Metaprofile to extract the context
func (sp *StringProfile) Metaprofile() (Metaprofile, error) {
	var mp Metaprofile
	if sp.Profile == "" {
		return mp, fmt.Errorf("Empty Profile cannot be converted to a Metaprofile")
	}
	err := json.Unmarshal([]byte(sp.Profile), &mp)
	if err != nil {
		return mp, fmt.Errorf("Error getting profile type: %+v\n", err)
	}
	return mp, nil
}

// Context extracts the schema context from a StringProfile
func (sp *StringProfile) Context() ProfileSchema {
	mp, err := sp.Metaprofile()
	if err != nil {
		log.Printf("[ERROR] %+s\n", err)
		return ""
	}
	return mp.Context
}

// GoString returns the StringProfile's Profile.
func (sp *StringProfile) GoString() string {
	return sp.Profile
}

// String returns the StringProfile's Profile.
func (sp *StringProfile) String() string {
	return sp.Profile
}

// GetProfileObject extracts the full Profile by its schema type
func (sp *StringProfile) GetProfileObject() interface{} {
	c := sp.Context()
	switch c {
	case DirPoolSchema:
		var dpp DirPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &dpp)
		if err != nil {
			log.Printf("Could not Unmarshal the DirPoolProfile.\n")
			return nil
		}
		return dpp
	case RDPoolSchema:
		var rdp RDPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &rdp)
		if err != nil {
			log.Printf("Could not Unmarshal the RDPoolProfile.\n")
			return nil
		}
		return rdp
	case SBPoolSchema:
		var sbp SBPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &sbp)
		if err != nil {
			log.Printf("Could not Unmarshal the SBPoolProfile.\n")
			return nil
		}
		return sbp
	case TCPoolSchema:
		var tcp TCPoolProfile
		err := json.Unmarshal([]byte(sp.Profile), &tcp)
		if err != nil {
			log.Printf("Could not Unmarshal the TCPoolProfile.\n")
			return nil
		}
		return tcp
	default:
		log.Printf("ERROR - Fall through on GetProfileObject - %s.\n", c)
		return fmt.Errorf("Fallthrough on GetProfileObject type %s\n", c)
	}
}

// RRSet wraps an RRSet resource
type RRSet struct {
	OwnerName string         `json:"ownerName"`
	RRType    string         `json:"rrtype"`
	TTL       int            `json:"ttl"`
	RData     []string       `json:"rdata"`
	Profile   *StringProfile `json:"profile,omitempty"`
}

// RRSetListDTO wraps a list of RRSet resources
type RRSetListDTO struct {
	ZoneName   string     `json:"zoneName"`
	Rrsets     []RRSet    `json:"rrsets"`
	Queryinfo  QueryInfo  `json:"queryInfo"`
	Resultinfo ResultInfo `json:"resultInfo"`
}

// RRSetKey collects the identifiers of a Zone
type RRSetKey struct {
	Zone string
	Type string
	Name string
}

// URI generates the URI for an RRSet
func (r *RRSetKey) URI() string {
	uri := fmt.Sprintf("zones/%s/rrsets", r.Zone)
	if r.Type != "" {
		uri += fmt.Sprintf("/%v", r.Type)
		if r.Name != "" {
			uri += fmt.Sprintf("/%v", r.Name)
		}
	}
	return uri
}

// QueryURI generates the query URI for an RRSet and offset
func (r *RRSetKey) QueryURI(offset int) string {
	// TODO: find a more appropriate place to set "" to "ANY"
	if r.Type == "" {
		r.Type = "ANY"
	}
	return fmt.Sprintf("%s?offset=%d", r.URI(), offset)
}

// EventsURI generates the URI for an RRSet
func (r *RRSetKey) EventsURI() string {
	return fmt.Sprintf("%s/events", r.URI())
}

// EventsQueryURI generates the events query URI for an RRSet with query
func (r *RRSetKey) EventsQueryURI(query string, offset int) string {
	uri := r.EventsURI()
	if query != "" {
		return fmt.Sprintf("%s?sort=NAME&query=%s&offset=%d", uri, query, offset)
	}
	if offset != 0 {
		return fmt.Sprintf("%s?offset=%d", uri, offset)
	}
	return uri
}

// Select will list the zone rrsets, paginating through all available results
func (s *RRSetsService) Select(r RRSetKey) ([]RRSet, error) {
	// TODO: Sane Configuration for timeouts / retries
	maxerrs := 5
	waittime := 5 * time.Second

	rrsets := []RRSet{}
	errcnt := 0
	offset := 0

	for {
		reqRrsets, ri, res, err := s.SelectWithOffset(r, offset)
		if err != nil {
			if res.StatusCode >= 500 {
				errcnt = errcnt + 1
				if errcnt < maxerrs {
					time.Sleep(waittime)
					continue
				}
			}
			return rrsets, err
		}

		log.Printf("ResultInfo: %+v\n", ri)
		for _, rrset := range reqRrsets {
			rrsets = append(rrsets, rrset)
		}
		if ri.ReturnedCount+ri.Offset >= ri.TotalCount {
			return rrsets, nil
		}
		offset = ri.ReturnedCount + ri.Offset
		continue
	}
}

// SelectWithOffset requests zone rrsets by RRSetKey & optional offset
func (s *RRSetsService) SelectWithOffset(r RRSetKey, offset int) ([]RRSet, ResultInfo, *Response, error) {
	var rrsld RRSetListDTO

	uri := r.QueryURI(offset)
	res, err := s.client.get(uri, &rrsld)

	rrsets := []RRSet{}
	for _, rrset := range rrsld.Rrsets {
		rrsets = append(rrsets, rrset)
	}
	return rrsets, rrsld.Resultinfo, res, err
}

// Create creates an rrset with val
func (s *RRSetsService) Create(r RRSetKey, rrset RRSet) (*Response, error) {
	var ignored interface{}
	return s.client.post(r.URI(), rrset, &ignored)
}

// Update updates a RRSet with the provided val
func (s *RRSetsService) Update(r RRSetKey, val RRSet) (*Response, error) {
	var ignored interface{}
	return s.client.put(r.URI(), val, &ignored)
}

// Delete deletes an RRSet
func (s *RRSetsService) Delete(r RRSetKey) (*Response, error) {
	return s.client.delete(r.URI(), nil)
}
<|endoftext|>


<commit_before>package imaging

import (
	"image"
	"image/color"
	"math"
)

// Grayscale produces a grayscale version of the image.
func Grayscale(img image.Image) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+3 : i+3]
				r := d[0]
				g := d[1]
				b := d[2]
				f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
				y := uint8(f + 0.5)
				d[0] = y
				d[1] = y
				d[2] = y
				i += 4
			}
		}
	})
	return dst
}

// Invert produces an inverted (negated) version of the image.
func Invert(img image.Image) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+3 : i+3]
				d[0] = 255 - d[0]
				d[1] = 255 - d[1]
				d[2] = 255 - d[2]
				i += 4
			}
		}
	})
	return dst
}

// AdjustSaturation changes the saturation of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in the range (-100, 100).
// The percentage = 0 gives the original image.
// The percentage = 100 gives the image with the saturation value doubled for each pixel.
// The percentage = -100 gives the image with the saturation value zeroed for each pixel (grayscale).
//
// Examples:
//  dstImage = imaging.AdjustSaturation(srcImage, 25) // Increase image saturation by 25%.
//  dstImage = imaging.AdjustSaturation(srcImage, -10) // Decrease image saturation by 10%.
//
func AdjustSaturation(img image.Image, percentage float64) *image.NRGBA {
	if percentage == 0 {
		return Clone(img)
	}

	percentage = math.Min(math.Max(percentage, -100), 100)
	multiplier := 1 + percentage/100

	return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
		h, s, l := rgbToHSL(c.R, c.G, c.B)
		s *= multiplier
		if s > 1 {
			s = 1
		}
		r, g, b := hslToRGB(h, s, l)
		return color.NRGBA{r, g, b, c.A}
	})
}

// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
// The percentage = -100 gives solid gray image.
//
// Examples:
//
//	dstImage = imaging.AdjustContrast(srcImage, -10) // Decrease image contrast by 10%.
//	dstImage = imaging.AdjustContrast(srcImage, 20) // Increase image contrast by 20%.
//
func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
	if percentage == 0 {
		return Clone(img)
	}

	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
	lut := make([]uint8, 256)

	v := (100.0 + percentage) / 100.0
	for i := 0; i < 256; i++ {
		switch {
		case 0 <= v && v <= 1:
			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
		case 1 < v && v < 2:
			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
		default:
			lut[i] = uint8(float64(i)/255.0+0.5) * 255
		}
	}

	return adjustLUT(img, lut)
}

// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
//
// Examples:
//
//	dstImage = imaging.AdjustBrightness(srcImage, -15) // Decrease image brightness by 15%.
//	dstImage = imaging.AdjustBrightness(srcImage, 10) // Increase image brightness by 10%.
//
func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
	if percentage == 0 {
		return Clone(img)
	}

	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
	lut := make([]uint8, 256)

	shift := 255.0 * percentage / 100.0
	for i := 0; i < 256; i++ {
		lut[i] = clamp(float64(i) + shift)
	}

	return adjustLUT(img, lut)
}

// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
//
// Example:
//
//	dstImage = imaging.AdjustGamma(srcImage, 0.7)
//
func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
	if gamma == 1 {
		return Clone(img)
	}

	e := 1.0 / math.Max(gamma, 0.0001)
	lut := make([]uint8, 256)

	for i := 0; i < 256; i++ {
		lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
	}

	return adjustLUT(img, lut)
}

// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
//
// Examples:
//
//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // Increase the contrast.
//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // Decrease the contrast.
//
func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
	if factor == 0 {
		return Clone(img)
	}

	lut := make([]uint8, 256)
	a := math.Min(math.Max(midpoint, 0.0), 1.0)
	b := math.Abs(factor)
	sig0 := sigmoid(a, b, 0)
	sig1 := sigmoid(a, b, 1)
	e := 1.0e-6

	if factor > 0 {
		for i := 0; i < 256; i++ {
			x := float64(i) / 255.0
			sigX := sigmoid(a, b, x)
			f := (sigX - sig0) / (sig1 - sig0)
			lut[i] = clamp(f * 255.0)
		}
	} else {
		for i := 0; i < 256; i++ {
			x := float64(i) / 255.0
			arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
			f := a - math.Log(1.0/arg-1.0)/b
			lut[i] = clamp(f * 255.0)
		}
	}

	return adjustLUT(img, lut)
}

func sigmoid(a, b, x float64) float64 {
	return 1 / (1 + math.Exp(b*(a-x)))
}

// adjustLUT applies the given lookup table to the colors of the image.
func adjustLUT(img image.Image, lut []uint8) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	lut = lut[0:256]
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+3 : i+3]
				d[0] = lut[d[0]]
				d[1] = lut[d[1]]
				d[2] = lut[d[2]]
				i += 4
			}
		}
	})
	return dst
}

// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
//
// Example:
//
//	dstImage = imaging.AdjustFunc(
//		srcImage,
//		func(c color.NRGBA) color.NRGBA {
//			// Shift the red channel by 16.
//			r := int(c.R) + 16
//			if r > 255 {
//				r = 255
//			}
//			return color.NRGBA{uint8(r), c.G, c.B, c.A}
//		}
//	)
//
func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+4 : i+4]
				r := d[0]
				g := d[1]
				b := d[2]
				a := d[3]
				c := fn(color.NRGBA{r, g, b, a})
				d[0] = c.R
				d[1] = c.G
				d[2] = c.B
				d[3] = c.A
				i += 4
			}
		}
	})
	return dst
}
<commit_msg>Add 'AdjustHue' function<commit_after>package imaging

import (
	"image"
	"image/color"
	"math"
)

// Grayscale produces a grayscale version of the image.
func Grayscale(img image.Image) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+3 : i+3]
				r := d[0]
				g := d[1]
				b := d[2]
				f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
				y := uint8(f + 0.5)
				d[0] = y
				d[1] = y
				d[2] = y
				i += 4
			}
		}
	})
	return dst
}

// Invert produces an inverted (negated) version of the image.
func Invert(img image.Image) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+3 : i+3]
				d[0] = 255 - d[0]
				d[1] = 255 - d[1]
				d[2] = 255 - d[2]
				i += 4
			}
		}
	})
	return dst
}

// AdjustSaturation changes the saturation of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in the range (-100, 100).
// The percentage = 0 gives the original image.
// The percentage = 100 gives the image with the saturation value doubled for each pixel.
// The percentage = -100 gives the image with the saturation value zeroed for each pixel (grayscale).
//
// Examples:
//  dstImage = imaging.AdjustSaturation(srcImage, 25) // Increase image saturation by 25%.
//  dstImage = imaging.AdjustSaturation(srcImage, -10) // Decrease image saturation by 10%.
//
func AdjustSaturation(img image.Image, percentage float64) *image.NRGBA {
	if percentage == 0 {
		return Clone(img)
	}

	percentage = math.Min(math.Max(percentage, -100), 100)
	multiplier := 1 + percentage/100

	return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
		h, s, l := rgbToHSL(c.R, c.G, c.B)
		s *= multiplier
		if s > 1 {
			s = 1
		}
		r, g, b := hslToRGB(h, s, l)
		return color.NRGBA{r, g, b, c.A}
	})
}

// AdjustHue changes the hue of the image using the shift parameter (measued in degrees) and returns the adjusted image.
// The shift must be in the range (-180, 180).
// The shift = 0 gives the original image.
// The shift = 180 (or -180) corresponds to a 180° degree rotation of the color wheel and thus gives the image with its hue inverted for each pixel.
//
// Examples:
//  dstImage = imaging.AdjustHue(srcImage, 90) // Shift Hue by 90°.
//  dstImage = imaging.AdjustHue(srcImage, -30) // Shift Hue by -30°.
//
func AdjustHue(img image.Image, shift float64) *image.NRGBA {
	if shift == 0 {
		return Clone(img)
	}

	shift = math.Min(math.Max(shift, -180), 180)
	shift = shift / 180

	return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
		h, s, l := rgbToHSL(c.R, c.G, c.B)
		h = math.Mod(h+shift, 1.0)
		r, g, b := hslToRGB(h, s, l)
		return color.NRGBA{r, g, b, c.A}
	})
}

// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
// The percentage = -100 gives solid gray image.
//
// Examples:
//
//	dstImage = imaging.AdjustContrast(srcImage, -10) // Decrease image contrast by 10%.
//	dstImage = imaging.AdjustContrast(srcImage, 20) // Increase image contrast by 20%.
//
func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
	if percentage == 0 {
		return Clone(img)
	}

	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
	lut := make([]uint8, 256)

	v := (100.0 + percentage) / 100.0
	for i := 0; i < 256; i++ {
		switch {
		case 0 <= v && v <= 1:
			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
		case 1 < v && v < 2:
			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
		default:
			lut[i] = uint8(float64(i)/255.0+0.5) * 255
		}
	}

	return adjustLUT(img, lut)
}

// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
//
// Examples:
//
//	dstImage = imaging.AdjustBrightness(srcImage, -15) // Decrease image brightness by 15%.
//	dstImage = imaging.AdjustBrightness(srcImage, 10) // Increase image brightness by 10%.
//
func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
	if percentage == 0 {
		return Clone(img)
	}

	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
	lut := make([]uint8, 256)

	shift := 255.0 * percentage / 100.0
	for i := 0; i < 256; i++ {
		lut[i] = clamp(float64(i) + shift)
	}

	return adjustLUT(img, lut)
}

// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
//
// Example:
//
//	dstImage = imaging.AdjustGamma(srcImage, 0.7)
//
func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
	if gamma == 1 {
		return Clone(img)
	}

	e := 1.0 / math.Max(gamma, 0.0001)
	lut := make([]uint8, 256)

	for i := 0; i < 256; i++ {
		lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
	}

	return adjustLUT(img, lut)
}

// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
//
// Examples:
//
//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // Increase the contrast.
//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // Decrease the contrast.
//
func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
	if factor == 0 {
		return Clone(img)
	}

	lut := make([]uint8, 256)
	a := math.Min(math.Max(midpoint, 0.0), 1.0)
	b := math.Abs(factor)
	sig0 := sigmoid(a, b, 0)
	sig1 := sigmoid(a, b, 1)
	e := 1.0e-6

	if factor > 0 {
		for i := 0; i < 256; i++ {
			x := float64(i) / 255.0
			sigX := sigmoid(a, b, x)
			f := (sigX - sig0) / (sig1 - sig0)
			lut[i] = clamp(f * 255.0)
		}
	} else {
		for i := 0; i < 256; i++ {
			x := float64(i) / 255.0
			arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
			f := a - math.Log(1.0/arg-1.0)/b
			lut[i] = clamp(f * 255.0)
		}
	}

	return adjustLUT(img, lut)
}

func sigmoid(a, b, x float64) float64 {
	return 1 / (1 + math.Exp(b*(a-x)))
}

// adjustLUT applies the given lookup table to the colors of the image.
func adjustLUT(img image.Image, lut []uint8) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	lut = lut[0:256]
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+3 : i+3]
				d[0] = lut[d[0]]
				d[1] = lut[d[1]]
				d[2] = lut[d[2]]
				i += 4
			}
		}
	})
	return dst
}

// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
//
// Example:
//
//	dstImage = imaging.AdjustFunc(
//		srcImage,
//		func(c color.NRGBA) color.NRGBA {
//			// Shift the red channel by 16.
//			r := int(c.R) + 16
//			if r > 255 {
//				r = 255
//			}
//			return color.NRGBA{uint8(r), c.G, c.B, c.A}
//		}
//	)
//
func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
	src := newScanner(img)
	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
	parallel(0, src.h, func(ys <-chan int) {
		for y := range ys {
			i := y * dst.Stride
			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
			for x := 0; x < src.w; x++ {
				d := dst.Pix[i : i+4 : i+4]
				r := d[0]
				g := d[1]
				b := d[2]
				a := d[3]
				c := fn(color.NRGBA{r, g, b, a})
				d[0] = c.R
				d[1] = c.G
				d[2] = c.B
				d[3] = c.A
				i += 4
			}
		}
	})
	return dst
}
<|endoftext|>


<commit_before>package uax29

import (
	"bytes"
	"unicode"
)

var is = unicode.Is

// Runes is a slice of runes, for use as a buffer
type Runes []rune

// Pos is a cursor for a Runes buffer
type Pos int

// SeekPreviousIndex works backward until it hits a rune in the `seek` category,
// ignoring runes in the `ignore` category, and returns the index of the rune in the buffer.
// It returns -1 if `seek` rune is not found.
func (buffer Runes) SeekPreviousIndex(pos Pos, ignore, seek *unicode.RangeTable) Pos {
	// Start at the end of the buffer and move backwards
	for i := pos - 1; i >= 0; i-- {
		r := buffer[i]

		if is(ignore, r) {
			continue
		}

		if is(seek, r) {
			return i
		}

		// If we get this far, it's not there
		break
	}

	return -1
}

// SeekPrevious works backward in the buffer until it hits a rune in the `seek` category,
// ignoring runes in the `ignore` category. It returns true if `seek` is found.
func (buffer Runes) SeekPrevious(pos Pos, ignore, seek *unicode.RangeTable) bool {
	return buffer.SeekPreviousIndex(pos, ignore, seek) >= 0
}

// SeekForward looks ahead in the buffer until it hits a rune in the `seek` category,
// ignoring runes in the `ignore` category. It returns true if `seek` is found.
func (buffer Runes) SeekForward(pos Pos, ignore, seek *unicode.RangeTable) bool {
	for i := int(pos) + 1; i < len(buffer); i++ {
		r := buffer[i]

		if is(ignore, r) {
			continue
		}

		if is(seek, r) {
			return true
		}

		// If we get this far, it's not there
		break
	}

	return false
}

// Bytes returns a byte slice of the runes in the buffer
func (buffer Runes) Bytes() []byte {
	var bb bytes.Buffer
	for _, r := range buffer {
		bb.WriteRune(r)
	}
	return bb.Bytes()
}
<commit_msg>Encode runes to bytes without buffer<commit_after>package uax29

import (
	"unicode"
	"unicode/utf8"
)

var is = unicode.Is

// Runes is a slice of runes, for use as a buffer
type Runes []rune

// Pos is a cursor for a Runes buffer
type Pos int

// SeekPreviousIndex works backward until it hits a rune in the `seek` category,
// ignoring runes in the `ignore` category, and returns the index of the rune in the buffer.
// It returns -1 if `seek` rune is not found.
func (buffer Runes) SeekPreviousIndex(pos Pos, ignore, seek *unicode.RangeTable) Pos {
	// Start at the end of the buffer and move backwards
	for i := pos - 1; i >= 0; i-- {
		r := buffer[i]

		if is(ignore, r) {
			continue
		}

		if is(seek, r) {
			return i
		}

		// If we get this far, it's not there
		break
	}

	return -1
}

// SeekPrevious works backward in the buffer until it hits a rune in the `seek` category,
// ignoring runes in the `ignore` category. It returns true if `seek` is found.
func (buffer Runes) SeekPrevious(pos Pos, ignore, seek *unicode.RangeTable) bool {
	return buffer.SeekPreviousIndex(pos, ignore, seek) >= 0
}

// SeekForward looks ahead in the buffer until it hits a rune in the `seek` category,
// ignoring runes in the `ignore` category. It returns true if `seek` is found.
func (buffer Runes) SeekForward(pos Pos, ignore, seek *unicode.RangeTable) bool {
	for i := int(pos) + 1; i < len(buffer); i++ {
		r := buffer[i]

		if is(ignore, r) {
			continue
		}

		if is(seek, r) {
			return true
		}

		// If we get this far, it's not there
		break
	}

	return false
}

// Bytes returns a byte slice of the runes in the buffer
func (buffer Runes) Bytes() []byte {
	len := 0
	for _, r := range buffer {
		len += utf8.RuneLen(r)
	}

	b := make([]byte, len, len)

	i := 0
	for _, r := range buffer {
		w := utf8.EncodeRune(b[i:], r)
		i += w
	}

	return b
}
<|endoftext|>


<commit_before>package satan

import (
	"fmt"
	"log"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"time"
)

// Satan is the master daemon.
type Satan struct {
	SubscribeFunc SubscribeFunc
	Publisher     Publisher
	Statistics    Statistics

	daemons []Daemon
	queue   chan *task

	wgWorkers       sync.WaitGroup
	wgSystem        sync.WaitGroup
	shutdownWorkers chan struct{}
	shutdownSystem  chan struct{}
}

// Actor is a function that could be executed by daemon workers.
type Actor func()

// SubscribeFunc is a function that is used by daemons to subscribe to messages.
type SubscribeFunc func(consumer, topic string) Streamer

// Streamer is the interface that wraps message consumers. Error handling
// should be provided by the implementation. Feel free to panic.
type Streamer interface {
	Messages() <-chan []byte
	Close()
}

// Publisher is the interface that wraps message publishers. Error handling
// should be provided by the implementation. Feel free to panic.
type Publisher interface {
	Publish(msg []byte)
	Close()
}

type Statistics interface {
	Add(name string, dur time.Duration)
	Error(name string)
}

type task struct {
	daemon    Daemon
	actor     Actor
	createdAt time.Time
	system    bool
	name      string
}

const (
	defaultNumWorkers = 10
)

var (
	workerIndex uint64
)

// Summon creates a new instance of Satan.
func Summon() *Satan {
	return &Satan{
		queue:           make(chan *task),
		shutdownWorkers: make(chan struct{}),
		shutdownSystem:  make(chan struct{}),
	}
}

// AddDaemon adds a new daemon.
func (s *Satan) AddDaemon(d Daemon) {
	base := d.base()
	base.self = d
	base.subscribeFunc = s.SubscribeFunc
	base.publisher = s.Publisher
	base.queue = s.queue
	base.shutdown = s.shutdownSystem

	go d.Startup()
	s.daemons = append(s.daemons, d)
}

// StartDaemons starts all registered daemons.
func (s *Satan) StartDaemons() {
	s.addWorkers(defaultNumWorkers)
}

// StopDaemons stops all running daemons.
func (s *Satan) StopDaemons() {
	close(s.shutdownSystem)
	for _, d := range s.daemons {
		d.Shutdown()
	}

	s.wgSystem.Wait()
	close(s.shutdownWorkers)
	s.wgWorkers.Wait()
	close(s.queue)
}

func (s *Satan) addWorkers(num int) {
	for i := 0; i < num; i++ {
		go s.runWorker()
	}
}

func (s *Satan) stopWorkers(num int) {
	for i := 0; i < num; i++ {
		s.shutdownWorkers <- struct{}{}
	}
}

func (s *Satan) runWorker() {
	s.wgWorkers.Add(1)
	defer s.wgWorkers.Done()

	i := atomic.AddUint64(&workerIndex, 1)
	log.Printf("Starting worker #%d", i)
	defer log.Printf("Worker #%d has stopped", i)

	for {
		select {
		case t := <-s.queue:
			s.processTask(t)
		case <-s.shutdownWorkers:
			return
		}
	}
}

func (s *Satan) processTask(t *task) {
	dur := time.Now().UnixNano() - t.createdAt.UnixNano()
	s.Statistics.Add("Latency", time.Duration(dur))

	if t.system {
		s.processSystemTask(t)
	} else {
		s.processGeneralTask(t)
	}
}

func (s *Satan) processSystemTask(t *task) {
	s.wgSystem.Add(1)
	defer s.wgSystem.Done()
	defer func() {
		if err := recover(); err != nil {
			log.Printf("System task %s recovered from a panic\nError: %v\n", t, err)
			debug.PrintStack()

			t.createdAt = time.Now()
			s.queue <- t // Restarting task
		} else {
			log.Printf("System task %s has stopped\n", t)
		}
	}()

	log.Printf("Starting system task %s\n", t)
	t.actor() // <--- ACTION STARTS HERE
}

func (s *Satan) processGeneralTask(t *task) {
	defer func() {
		if err := recover(); err != nil {
			if s.Statistics != nil {
				s.Statistics.Error(t.daemon.base().String())
			}
			t.daemon.base().handlePanic(err)
			log.Printf("Daemon %s recovered from a panic\nError: %v\n", t.daemon.base(), err)
			debug.PrintStack()
		}
	}()
	if s.Statistics != nil {
		defer func(start time.Time) {
			dur := time.Now().UnixNano() - start.UnixNano()
			s.Statistics.Add(t.daemon.base().String(), time.Duration(dur))
		}(time.Now())
	}

	t.actor() // <--- ACTION STARTS HERE
}

func (t *task) String() string {
	if t.name == "" {
		return fmt.Sprintf("[unnamed %s process]", t.daemon)
	}

	return fmt.Sprintf("%s[%s]", t.daemon, t.name)
}
<commit_msg>Collect task wait statistics<commit_after>package satan

import (
	"fmt"
	"log"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"time"
)

// Satan is the master daemon.
type Satan struct {
	SubscribeFunc SubscribeFunc
	Publisher     Publisher
	Statistics    Statistics

	daemons []Daemon
	queue   chan *task

	wgWorkers       sync.WaitGroup
	wgSystem        sync.WaitGroup
	shutdownWorkers chan struct{}
	shutdownSystem  chan struct{}
}

// Actor is a function that could be executed by daemon workers.
type Actor func()

// SubscribeFunc is a function that is used by daemons to subscribe to messages.
type SubscribeFunc func(consumer, topic string) Streamer

// Streamer is the interface that wraps message consumers. Error handling
// should be provided by the implementation. Feel free to panic.
type Streamer interface {
	Messages() <-chan []byte
	Close()
}

// Publisher is the interface that wraps message publishers. Error handling
// should be provided by the implementation. Feel free to panic.
type Publisher interface {
	Publish(msg []byte)
	Close()
}

type Statistics interface {
	Add(name string, dur time.Duration)
	Error(name string)
}

type task struct {
	daemon    Daemon
	actor     Actor
	createdAt time.Time
	system    bool
	name      string
}

const (
	defaultNumWorkers = 10
)

var (
	workerIndex uint64
)

// Summon creates a new instance of Satan.
func Summon() *Satan {
	return &Satan{
		queue:           make(chan *task),
		shutdownWorkers: make(chan struct{}),
		shutdownSystem:  make(chan struct{}),
	}
}

// AddDaemon adds a new daemon.
func (s *Satan) AddDaemon(d Daemon) {
	base := d.base()
	base.self = d
	base.subscribeFunc = s.SubscribeFunc
	base.publisher = s.Publisher
	base.queue = s.queue
	base.shutdown = s.shutdownSystem

	go d.Startup()
	s.daemons = append(s.daemons, d)
}

// StartDaemons starts all registered daemons.
func (s *Satan) StartDaemons() {
	s.addWorkers(defaultNumWorkers)
}

// StopDaemons stops all running daemons.
func (s *Satan) StopDaemons() {
	close(s.shutdownSystem)
	for _, d := range s.daemons {
		d.Shutdown()
	}

	s.wgSystem.Wait()
	close(s.shutdownWorkers)
	s.wgWorkers.Wait()
	close(s.queue)
}

func (s *Satan) addWorkers(num int) {
	for i := 0; i < num; i++ {
		go s.runWorker()
	}
}

func (s *Satan) stopWorkers(num int) {
	for i := 0; i < num; i++ {
		s.shutdownWorkers <- struct{}{}
	}
}

func (s *Satan) runWorker() {
	s.wgWorkers.Add(1)
	defer s.wgWorkers.Done()

	i := atomic.AddUint64(&workerIndex, 1)
	log.Printf("Starting worker #%d", i)
	defer log.Printf("Worker #%d has stopped", i)

	for {
		start := time.Now()
		select {
		case t := <-s.queue:
			dur := time.Now().UnixNano() - start.UnixNano()
			s.Statistics.Add("TaskWait", time.Duration(dur))
			s.processTask(t)
		case <-s.shutdownWorkers:
			return
		}
	}
}

func (s *Satan) processTask(t *task) {
	dur := time.Now().UnixNano() - t.createdAt.UnixNano()
	s.Statistics.Add("Latency", time.Duration(dur))

	if t.system {
		s.processSystemTask(t)
	} else {
		s.processGeneralTask(t)
	}
}

func (s *Satan) processSystemTask(t *task) {
	s.wgSystem.Add(1)
	defer s.wgSystem.Done()
	defer func() {
		if err := recover(); err != nil {
			log.Printf("System task %s recovered from a panic\nError: %v\n", t, err)
			debug.PrintStack()

			t.createdAt = time.Now()
			s.queue <- t // Restarting task
		} else {
			log.Printf("System task %s has stopped\n", t)
		}
	}()

	log.Printf("Starting system task %s\n", t)
	t.actor() // <--- ACTION STARTS HERE
}

func (s *Satan) processGeneralTask(t *task) {
	defer func() {
		if err := recover(); err != nil {
			if s.Statistics != nil {
				s.Statistics.Error(t.daemon.base().String())
			}
			t.daemon.base().handlePanic(err)
			log.Printf("Daemon %s recovered from a panic\nError: %v\n", t.daemon.base(), err)
			debug.PrintStack()
		}
	}()
	if s.Statistics != nil {
		defer func(start time.Time) {
			dur := time.Now().UnixNano() - start.UnixNano()
			s.Statistics.Add(t.daemon.base().String(), time.Duration(dur))
		}(time.Now())
	}

	t.actor() // <--- ACTION STARTS HERE
}

func (t *task) String() string {
	if t.name == "" {
		return fmt.Sprintf("[unnamed %s process]", t.daemon)
	}

	return fmt.Sprintf("%s[%s]", t.daemon, t.name)
}
<|endoftext|>