<commit_before>// Package dataloader is an implimentation of facebook's dataloader in go.
// See https://github.com/facebook/dataloader for more information
package dataloader

import (
	"context"
	"fmt"
	"log"
	"runtime"
	"sync"
	"time"
)

// Interface is a `DataLoader` Interface which defines a public API for loading data from a particular
// data back-end with unique keys such as the `id` column of a SQL table or
// document name in a MongoDB database, given a batch loading function.
//
// Each `DataLoader` instance should contain a unique memoized cache. Use caution when
// used in long-lived applications or those which serve many users with
// different access permissions and consider creating a new instance per
// web request.
type Interface interface {
	Load(context.Context, interface{}) Thunk
	LoadMany(context.Context, []interface{}) ThunkMany
	Clear(context.Context, string) Interface
	ClearAll() Interface
	Prime(ctx context.Context, key string, value interface{}) Interface
}

// BatchFunc is a function, which when given a slice of keys (string), returns an slice of `results`.
// It's important that the length of the input keys matches the length of the output results.
//
// The keys passed to this function are guaranteed to be unique
type BatchFunc func(context.Context, []interface{}) []*Result

// Result is the data structure that a BatchFunc returns.
// It contains the resolved data, and any errors that may have occurred while fetching the data.
type Result struct {
	Data  interface{}
	Error error
}

// ResultMany is used by the LoadMany method.
// It contains a list of resolved data and a list of errors.
// The lengths of the data list and error list will match, and elements at each index correspond to each other.
type ResultMany struct {
	Data  []interface{}
	Error []error
}

// Loader implements the dataloader.Interface.
type Loader struct {
	// the batch function to be used by this loader
	batchFn BatchFunc

	// the maximum batch size. Set to 0 if you want it to be unbounded.
	batchCap int

	// the internal cache. This packages contains a basic cache implementation but any custom cache
	// implementation could be used as long as it implements the `Cache` interface.
	cacheLock sync.Mutex
	cache     Cache
	// should we clear the cache on each batch?
	// this would allow batching but no long term caching
	clearCacheOnBatch bool

	// count of queued up items
	count int

	// the maximum input queue size. Set to 0 if you want it to be unbounded.
	inputCap int

	// the amount of time to wait before triggering a batch
	wait time.Duration

	// lock to protect the batching operations
	batchLock sync.Mutex

	// current batcher
	curBatcher *batcher

	// used to close the sleeper of the current batcher
	endSleeper chan bool

	// used by tests to prevent logs
	silent bool

	// can be set to trace calls to dataloader
	tracer Tracer
}

// Thunk is a function that will block until the value (*Result) it contins is resolved.
// After the value it contains is resolved, this function will return the result.
// This function can be called many times, much like a Promise is other languages.
// The value will only need to be resolved once so subsequent calls will return immediately.
type Thunk func() (interface{}, error)

// ThunkMany is much like the Thunk func type but it contains a list of results.
type ThunkMany func() ([]interface{}, []error)

// type used to on input channel
type batchRequest struct {
	key     interface{}
	channel chan *Result
}

// Option allows for configuration of Loader fields.
type Option func(*Loader)

// WithCache sets the BatchedLoader cache. Defaults to InMemoryCache if a Cache is not set.
func WithCache(c Cache) Option {
	return func(l *Loader) {
		l.cache = c
	}
}

// WithBatchCapacity sets the batch capacity. Default is 0 (unbounded).
func WithBatchCapacity(c int) Option {
	return func(l *Loader) {
		l.batchCap = c
	}
}

// WithInputCapacity sets the input capacity. Default is 1000.
func WithInputCapacity(c int) Option {
	return func(l *Loader) {
		l.inputCap = c
	}
}

// WithWait sets the amount of time to wait before triggering a batch.
// Default duration is 16 milliseconds.
func WithWait(d time.Duration) Option {
	return func(l *Loader) {
		l.wait = d
	}
}

// WithClearCacheOnBatch allows batching of items but no long term caching.
// It accomplishes this by clearing the cache after each batch operation.
func WithClearCacheOnBatch() Option {
	return func(l *Loader) {
		l.cacheLock.Lock()
		l.clearCacheOnBatch = true
		l.cacheLock.Unlock()
	}
}

// withSilentLogger turns of log messages. It's used by the tests
func withSilentLogger() Option {
	return func(l *Loader) {
		l.silent = true
	}
}

// WithTracer allows tracing of calls to Load and LoadMany
func WithTracer(tracer Tracer) Option {
	return func(l *Loader) {
		l.tracer = tracer
	}
}

// WithOpenTracingTracer allows tracing of calls to Load and LoadMany
func WithOpenTracingTracer() Option {
	return WithTracer(&OpenTracingTracer{})
}

// NewBatchedLoader constructs a new Loader with given options.
func NewBatchedLoader(batchFn BatchFunc, opts ...Option) *Loader {
	loader := &Loader{
		batchFn:  batchFn,
		inputCap: 1000,
		wait:     16 * time.Millisecond,
	}

	// Apply options
	for _, apply := range opts {
		apply(loader)
	}

	// Set defaults
	if loader.cache == nil {
		loader.cache = NewCache()
	}

	if loader.tracer == nil {
		loader.tracer = &NoopTracer{}
	}

	return loader
}

// Load load/resolves the given key, returning a channel that will contain the value and error
func (l *Loader) Load(originalContext context.Context, key interface{}) Thunk {
	ctx, finish := l.tracer.TraceLoad(originalContext, key)

	c := make(chan *Result, 1)
	var result struct {
		mu    sync.RWMutex
		value *Result
	}

	// lock to prevent duplicate keys coming in before item has been added to cache.
	l.cacheLock.Lock()
	if v, ok := l.cache.Get(ctx, key); ok {
		defer finish(v)
		defer l.cacheLock.Unlock()
		return v
	}

	thunk := func() (interface{}, error) {
		result.mu.RLock()
		resultNotSet := result.value == nil
		result.mu.RUnlock()

		if resultNotSet {
			result.mu.Lock()
			if v, ok := <-c; ok {
				result.value = v
			}
			result.mu.Unlock()
		}
		result.mu.RLock()
		defer result.mu.RUnlock()
		return result.value.Data, result.value.Error
	}
	defer finish(thunk)

	l.cache.Set(ctx, key, thunk)
	l.cacheLock.Unlock()

	// this is sent to batch fn. It contains the key and the channel to return the
	// the result on
	req := &batchRequest{key, c}

	l.batchLock.Lock()
	// start the batch window if it hasn't already started.
	if l.curBatcher == nil {
		l.curBatcher = l.newBatcher(l.silent, l.tracer)
		// start the current batcher batch function
		go l.curBatcher.batch(originalContext)
		// start a sleeper for the current batcher
		l.endSleeper = make(chan bool)
		go l.sleeper(l.curBatcher, l.endSleeper)
	}

	l.curBatcher.input <- req

	// if we need to keep track of the count (max batch), then do so.
	if l.batchCap > 0 {
		l.count++
		// if we hit our limit, force the batch to start
		if l.count == l.batchCap {
			// end the batcher synchronously here because another call to Load
			// may concurrently happen and needs to go to a new batcher.
			l.curBatcher.end()
			// end the sleeper for the current batcher.
			// this is to stop the goroutine without waiting for the
			// sleeper timeout.
			close(l.endSleeper)
			l.reset()
		}
	}
	l.batchLock.Unlock()

	return thunk
}

// LoadMany loads mulitiple keys, returning a thunk (type: ThunkMany) that will resolve the keys passed in.
func (l *Loader) LoadMany(originalContext context.Context, keys []interface{}) ThunkMany {
	ctx, finish := l.tracer.TraceLoadMany(originalContext, keys)

	var (
		length = len(keys)
		data   = make([]interface{}, length)
		errors = make([]error, length)
		c      = make(chan *ResultMany, 1)
		wg     sync.WaitGroup
	)

	wg.Add(length)
	for i := range keys {
		go func(ctx context.Context, i int) {
			defer wg.Done()
			thunk := l.Load(ctx, keys[i])
			result, err := thunk()
			data[i] = result
			errors[i] = err
		}(ctx, i)
	}

	go func() {
		wg.Wait()

		// errs is nil unless there exists a non-nil error.
		// This prevents dataloader from returning a slice of all-nil errors.
		var errs []error
		for _, e := range errors {
			if e != nil {
				errs = errors
				break
			}
		}

		c <- &ResultMany{Data: data, Error: errs}
		close(c)
	}()

	var result struct {
		mu    sync.RWMutex
		value *ResultMany
	}

	thunkMany := func() ([]interface{}, []error) {
		result.mu.RLock()
		resultNotSet := result.value == nil
		result.mu.RUnlock()

		if resultNotSet {
			result.mu.Lock()
			if v, ok := <-c; ok {
				result.value = v
			}
			result.mu.Unlock()
		}
		result.mu.RLock()
		defer result.mu.RUnlock()
		return result.value.Data, result.value.Error
	}

	defer finish(thunkMany)
	return thunkMany
}

// Clear clears the value at `key` from the cache, it it exsits. Returs self for method chaining
func (l *Loader) Clear(ctx context.Context, key string) Interface {
	l.cacheLock.Lock()
	l.cache.Delete(ctx, key)
	l.cacheLock.Unlock()
	return l
}

// ClearAll clears the entire cache. To be used when some event results in unknown invalidations.
// Returns self for method chaining.
func (l *Loader) ClearAll() Interface {
	l.cacheLock.Lock()
	l.cache.Clear()
	l.cacheLock.Unlock()
	return l
}

// Prime adds the provided key and value to the cache. If the key already exists, no change is made.
// Returns self for method chaining
func (l *Loader) Prime(ctx context.Context, key string, value interface{}) Interface {
	if _, ok := l.cache.Get(ctx, key); !ok {
		thunk := func() (interface{}, error) {
			return value, nil
		}
		l.cache.Set(ctx, key, thunk)
	}
	return l
}

func (l *Loader) reset() {
	l.count = 0
	l.curBatcher = nil

	if l.clearCacheOnBatch {
		l.cache.Clear()
	}
}

type batcher struct {
	input    chan *batchRequest
	batchFn  BatchFunc
	finished bool
	silent   bool
	tracer   Tracer
}

// newBatcher returns a batcher for the current requests
// all the batcher methods must be protected by a global batchLock
func (l *Loader) newBatcher(silent bool, tracer Tracer) *batcher {
	return &batcher{
		input:   make(chan *batchRequest, l.inputCap),
		batchFn: l.batchFn,
		silent:  silent,
		tracer:  tracer,
	}
}

// stop receiving input and process batch function
func (b *batcher) end() {
	if !b.finished {
		close(b.input)
		b.finished = true
	}
}

// execute the batch of all items in queue
func (b *batcher) batch(originalContext context.Context) {
	var keys []interface{}
	var reqs []*batchRequest
	var items []*Result
	var panicErr interface{}

	for item := range b.input {
		keys = append(keys, item.key)
		reqs = append(reqs, item)
	}

	ctx, finish := b.tracer.TraceBatch(originalContext, keys)
	defer finish(items)

	func() {
		defer func() {
			if r := recover(); r != nil {
				panicErr = r
				if b.silent {
					return
				}
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				log.Printf("Dataloader: Panic received in batch function:: %v\n%s", panicErr, buf)
			}
		}()
		items = b.batchFn(ctx, keys)
	}()

	if panicErr != nil {
		for _, req := range reqs {
			req.channel <- &Result{Error: fmt.Errorf("Panic received in batch function: %v", panicErr)}
			close(req.channel)
		}
		return
	}

	if len(items) != len(keys) {
		err := &Result{Error: fmt.Errorf(`
			The batch function supplied did not return an array of responses
			the same length as the array of keys.

			Keys:
			%v

			Values:
			%v
		`, keys, items)}

		for _, req := range reqs {
			req.channel <- err
			close(req.channel)
		}

		return
	}

	for i, req := range reqs {
		req.channel <- items[i]
		close(req.channel)
	}
}

// wait the appropriate amount of time for the provided batcher
func (l *Loader) sleeper(b *batcher, close chan bool) {
	select {
	// used by batch to close early. usually triggered by max batch size
	case <-close:
		return
	// this will move this goroutine to the back of the callstack?
	case <-time.After(l.wait):
	}

	// reset
	// this is protected by the batchLock to avoid closing the batcher input
	// channel while Load is inserting a request
	l.batchLock.Lock()
	b.end()

	// We can end here also if the batcher has already been closed and a
	// new one has been created. So reset the loader state only if the batcher
	// is the current one
	if l.curBatcher == b {
		l.reset()
	}
	l.batchLock.Unlock()
}
<commit_msg>add interface to prime interface<commit_after>// Package dataloader is an implimentation of facebook's dataloader in go.
// See https://github.com/facebook/dataloader for more information
package dataloader

import (
	"context"
	"fmt"
	"log"
	"runtime"
	"sync"
	"time"
)

// Interface is a `DataLoader` Interface which defines a public API for loading data from a particular
// data back-end with unique keys such as the `id` column of a SQL table or
// document name in a MongoDB database, given a batch loading function.
//
// Each `DataLoader` instance should contain a unique memoized cache. Use caution when
// used in long-lived applications or those which serve many users with
// different access permissions and consider creating a new instance per
// web request.
type Interface interface {
	Load(context.Context, interface{}) Thunk
	LoadMany(context.Context, []interface{}) ThunkMany
	Clear(context.Context, string) Interface
	ClearAll() Interface
	Prime(ctx context.Context, key interface{}, value interface{}) Interface
}

// BatchFunc is a function, which when given a slice of keys (string), returns an slice of `results`.
// It's important that the length of the input keys matches the length of the output results.
//
// The keys passed to this function are guaranteed to be unique
type BatchFunc func(context.Context, []interface{}) []*Result

// Result is the data structure that a BatchFunc returns.
// It contains the resolved data, and any errors that may have occurred while fetching the data.
type Result struct {
	Data  interface{}
	Error error
}

// ResultMany is used by the LoadMany method.
// It contains a list of resolved data and a list of errors.
// The lengths of the data list and error list will match, and elements at each index correspond to each other.
type ResultMany struct {
	Data  []interface{}
	Error []error
}

// Loader implements the dataloader.Interface.
type Loader struct {
	// the batch function to be used by this loader
	batchFn BatchFunc

	// the maximum batch size. Set to 0 if you want it to be unbounded.
	batchCap int

	// the internal cache. This packages contains a basic cache implementation but any custom cache
	// implementation could be used as long as it implements the `Cache` interface.
	cacheLock sync.Mutex
	cache     Cache
	// should we clear the cache on each batch?
	// this would allow batching but no long term caching
	clearCacheOnBatch bool

	// count of queued up items
	count int

	// the maximum input queue size. Set to 0 if you want it to be unbounded.
	inputCap int

	// the amount of time to wait before triggering a batch
	wait time.Duration

	// lock to protect the batching operations
	batchLock sync.Mutex

	// current batcher
	curBatcher *batcher

	// used to close the sleeper of the current batcher
	endSleeper chan bool

	// used by tests to prevent logs
	silent bool

	// can be set to trace calls to dataloader
	tracer Tracer
}

// Thunk is a function that will block until the value (*Result) it contins is resolved.
// After the value it contains is resolved, this function will return the result.
// This function can be called many times, much like a Promise is other languages.
// The value will only need to be resolved once so subsequent calls will return immediately.
type Thunk func() (interface{}, error)

// ThunkMany is much like the Thunk func type but it contains a list of results.
type ThunkMany func() ([]interface{}, []error)

// type used to on input channel
type batchRequest struct {
	key     interface{}
	channel chan *Result
}

// Option allows for configuration of Loader fields.
type Option func(*Loader)

// WithCache sets the BatchedLoader cache. Defaults to InMemoryCache if a Cache is not set.
func WithCache(c Cache) Option {
	return func(l *Loader) {
		l.cache = c
	}
}

// WithBatchCapacity sets the batch capacity. Default is 0 (unbounded).
func WithBatchCapacity(c int) Option {
	return func(l *Loader) {
		l.batchCap = c
	}
}

// WithInputCapacity sets the input capacity. Default is 1000.
func WithInputCapacity(c int) Option {
	return func(l *Loader) {
		l.inputCap = c
	}
}

// WithWait sets the amount of time to wait before triggering a batch.
// Default duration is 16 milliseconds.
func WithWait(d time.Duration) Option {
	return func(l *Loader) {
		l.wait = d
	}
}

// WithClearCacheOnBatch allows batching of items but no long term caching.
// It accomplishes this by clearing the cache after each batch operation.
func WithClearCacheOnBatch() Option {
	return func(l *Loader) {
		l.cacheLock.Lock()
		l.clearCacheOnBatch = true
		l.cacheLock.Unlock()
	}
}

// withSilentLogger turns of log messages. It's used by the tests
func withSilentLogger() Option {
	return func(l *Loader) {
		l.silent = true
	}
}

// WithTracer allows tracing of calls to Load and LoadMany
func WithTracer(tracer Tracer) Option {
	return func(l *Loader) {
		l.tracer = tracer
	}
}

// WithOpenTracingTracer allows tracing of calls to Load and LoadMany
func WithOpenTracingTracer() Option {
	return WithTracer(&OpenTracingTracer{})
}

// NewBatchedLoader constructs a new Loader with given options.
func NewBatchedLoader(batchFn BatchFunc, opts ...Option) *Loader {
	loader := &Loader{
		batchFn:  batchFn,
		inputCap: 1000,
		wait:     16 * time.Millisecond,
	}

	// Apply options
	for _, apply := range opts {
		apply(loader)
	}

	// Set defaults
	if loader.cache == nil {
		loader.cache = NewCache()
	}

	if loader.tracer == nil {
		loader.tracer = &NoopTracer{}
	}

	return loader
}

// Load load/resolves the given key, returning a channel that will contain the value and error
func (l *Loader) Load(originalContext context.Context, key interface{}) Thunk {
	ctx, finish := l.tracer.TraceLoad(originalContext, key)

	c := make(chan *Result, 1)
	var result struct {
		mu    sync.RWMutex
		value *Result
	}

	// lock to prevent duplicate keys coming in before item has been added to cache.
	l.cacheLock.Lock()
	if v, ok := l.cache.Get(ctx, key); ok {
		defer finish(v)
		defer l.cacheLock.Unlock()
		return v
	}

	thunk := func() (interface{}, error) {
		result.mu.RLock()
		resultNotSet := result.value == nil
		result.mu.RUnlock()

		if resultNotSet {
			result.mu.Lock()
			if v, ok := <-c; ok {
				result.value = v
			}
			result.mu.Unlock()
		}
		result.mu.RLock()
		defer result.mu.RUnlock()
		return result.value.Data, result.value.Error
	}
	defer finish(thunk)

	l.cache.Set(ctx, key, thunk)
	l.cacheLock.Unlock()

	// this is sent to batch fn. It contains the key and the channel to return the
	// the result on
	req := &batchRequest{key, c}

	l.batchLock.Lock()
	// start the batch window if it hasn't already started.
	if l.curBatcher == nil {
		l.curBatcher = l.newBatcher(l.silent, l.tracer)
		// start the current batcher batch function
		go l.curBatcher.batch(originalContext)
		// start a sleeper for the current batcher
		l.endSleeper = make(chan bool)
		go l.sleeper(l.curBatcher, l.endSleeper)
	}

	l.curBatcher.input <- req

	// if we need to keep track of the count (max batch), then do so.
	if l.batchCap > 0 {
		l.count++
		// if we hit our limit, force the batch to start
		if l.count == l.batchCap {
			// end the batcher synchronously here because another call to Load
			// may concurrently happen and needs to go to a new batcher.
			l.curBatcher.end()
			// end the sleeper for the current batcher.
			// this is to stop the goroutine without waiting for the
			// sleeper timeout.
			close(l.endSleeper)
			l.reset()
		}
	}
	l.batchLock.Unlock()

	return thunk
}

// LoadMany loads mulitiple keys, returning a thunk (type: ThunkMany) that will resolve the keys passed in.
func (l *Loader) LoadMany(originalContext context.Context, keys []interface{}) ThunkMany {
	ctx, finish := l.tracer.TraceLoadMany(originalContext, keys)

	var (
		length = len(keys)
		data   = make([]interface{}, length)
		errors = make([]error, length)
		c      = make(chan *ResultMany, 1)
		wg     sync.WaitGroup
	)

	wg.Add(length)
	for i := range keys {
		go func(ctx context.Context, i int) {
			defer wg.Done()
			thunk := l.Load(ctx, keys[i])
			result, err := thunk()
			data[i] = result
			errors[i] = err
		}(ctx, i)
	}

	go func() {
		wg.Wait()

		// errs is nil unless there exists a non-nil error.
		// This prevents dataloader from returning a slice of all-nil errors.
		var errs []error
		for _, e := range errors {
			if e != nil {
				errs = errors
				break
			}
		}

		c <- &ResultMany{Data: data, Error: errs}
		close(c)
	}()

	var result struct {
		mu    sync.RWMutex
		value *ResultMany
	}

	thunkMany := func() ([]interface{}, []error) {
		result.mu.RLock()
		resultNotSet := result.value == nil
		result.mu.RUnlock()

		if resultNotSet {
			result.mu.Lock()
			if v, ok := <-c; ok {
				result.value = v
			}
			result.mu.Unlock()
		}
		result.mu.RLock()
		defer result.mu.RUnlock()
		return result.value.Data, result.value.Error
	}

	defer finish(thunkMany)
	return thunkMany
}

// Clear clears the value at `key` from the cache, it it exsits. Returs self for method chaining
func (l *Loader) Clear(ctx context.Context, key string) Interface {
	l.cacheLock.Lock()
	l.cache.Delete(ctx, key)
	l.cacheLock.Unlock()
	return l
}

// ClearAll clears the entire cache. To be used when some event results in unknown invalidations.
// Returns self for method chaining.
func (l *Loader) ClearAll() Interface {
	l.cacheLock.Lock()
	l.cache.Clear()
	l.cacheLock.Unlock()
	return l
}

// Prime adds the provided key and value to the cache. If the key already exists, no change is made.
// Returns self for method chaining
func (l *Loader) Prime(ctx context.Context, key interface{}, value interface{}) Interface {
	if _, ok := l.cache.Get(ctx, key); !ok {
		thunk := func() (interface{}, error) {
			return value, nil
		}
		l.cache.Set(ctx, key, thunk)
	}
	return l
}

func (l *Loader) reset() {
	l.count = 0
	l.curBatcher = nil

	if l.clearCacheOnBatch {
		l.cache.Clear()
	}
}

type batcher struct {
	input    chan *batchRequest
	batchFn  BatchFunc
	finished bool
	silent   bool
	tracer   Tracer
}

// newBatcher returns a batcher for the current requests
// all the batcher methods must be protected by a global batchLock
func (l *Loader) newBatcher(silent bool, tracer Tracer) *batcher {
	return &batcher{
		input:   make(chan *batchRequest, l.inputCap),
		batchFn: l.batchFn,
		silent:  silent,
		tracer:  tracer,
	}
}

// stop receiving input and process batch function
func (b *batcher) end() {
	if !b.finished {
		close(b.input)
		b.finished = true
	}
}

// execute the batch of all items in queue
func (b *batcher) batch(originalContext context.Context) {
	var keys []interface{}
	var reqs []*batchRequest
	var items []*Result
	var panicErr interface{}

	for item := range b.input {
		keys = append(keys, item.key)
		reqs = append(reqs, item)
	}

	ctx, finish := b.tracer.TraceBatch(originalContext, keys)
	defer finish(items)

	func() {
		defer func() {
			if r := recover(); r != nil {
				panicErr = r
				if b.silent {
					return
				}
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				log.Printf("Dataloader: Panic received in batch function:: %v\n%s", panicErr, buf)
			}
		}()
		items = b.batchFn(ctx, keys)
	}()

	if panicErr != nil {
		for _, req := range reqs {
			req.channel <- &Result{Error: fmt.Errorf("Panic received in batch function: %v", panicErr)}
			close(req.channel)
		}
		return
	}

	if len(items) != len(keys) {
		err := &Result{Error: fmt.Errorf(`
			The batch function supplied did not return an array of responses
			the same length as the array of keys.

			Keys:
			%v

			Values:
			%v
		`, keys, items)}

		for _, req := range reqs {
			req.channel <- err
			close(req.channel)
		}

		return
	}

	for i, req := range reqs {
		req.channel <- items[i]
		close(req.channel)
	}
}

// wait the appropriate amount of time for the provided batcher
func (l *Loader) sleeper(b *batcher, close chan bool) {
	select {
	// used by batch to close early. usually triggered by max batch size
	case <-close:
		return
	// this will move this goroutine to the back of the callstack?
	case <-time.After(l.wait):
	}

	// reset
	// this is protected by the batchLock to avoid closing the batcher input
	// channel while Load is inserting a request
	l.batchLock.Lock()
	b.end()

	// We can end here also if the batcher has already been closed and a
	// new one has been created. So reset the loader state only if the batcher
	// is the current one
	if l.curBatcher == b {
		l.reset()
	}
	l.batchLock.Unlock()
}
<|endoftext|>


<commit_before>package main

import (
//	"net"
	"net/http"
	"log"
//	"io/ioutil"
	"flag"
	"os"
	"path/filepath"
	"regexp"
	"fmt"
//	"reflect"
	"strconv"
)

var (
	rootdir string
	port string
	address string
	verbose bool
)

var (
	d Query
)

func init() {
	flag.StringVar(&rootdir, "root", "current directory", "webroot directory")
	flag.StringVar(&rootdir, "d", "current directory", "webroot directory" + " (shorthand)")
	flag.StringVar(&port,"port", "8080", "listen port")
	flag.StringVar(&port,"p", "8080", "listen port" + " (shorthand)")
	flag.StringVar(&address, "address", "*", "listen address")
	flag.StringVar(&address, "l", "*", "listen address" + " (shorthand)")
	flag.BoolVar(&verbose, "verbose", false, "be verbose")
	flag.BoolVar(&verbose, "v", false, "be verbose" + " (shorthand)")

	if rootdir == "current directory" {
		rootdir, err :=  filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			log.Fatal(err)
		}
		log.Print("Webroot set to \"" + rootdir + "\".")
	}
	if address == "*" {
		address = ""
	}
}

func debug(s string) {
	if verbose {
		log.Print(s)
	}
}

func debugln(v ...interface{}) {
	if verbose {
		log.Println(v ...)
	}
}

var validLog = regexp.MustCompile("^/log/([a-zA-Z0-9-]+)/([0-9]+[.]{0,1}[0-9]*)/?$")

func logHandler(w http.ResponseWriter, r *http.Request) {
	m := validLog.FindStringSubmatch(r.URL.Path)
	if len(m) == 0 {
		http.Error(w, "Sensor not found", http.StatusNotFound)
		return
	}
	debug("Sensor " + m[1] + " sent value " + m[2])
	if ! d.Exists(m[1]) {
		d.Add(m[1])
	}
	v, _ := strconv.ParseFloat(m[2], 64)
	d.Store(m[1], v)
	debugln("Sensor " + m[1] + " now contains:", d.Load(m[1]))
	fmt.Fprintf(w, "ok")
}

func main() {
	flag.Parse()
	t := Database{ make(map[string] sensorlog) }
	d = t
	http.HandleFunc("/log/", logHandler)
	http.Handle("/", http.FileServer(http.Dir(rootdir)))

	log.Print("Starting webserver. Listening on " + address + ":" + port)
	err := http.ListenAndServe(address + ":" + port, nil)
	if err != nil {
		log.Fatal("Couldn't start server. ListenAndServe: ", err)
	}
}
<commit_msg>now server can parse time or seconds since from GET URL and store as timestamp<commit_after>package main

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

var (
	rootdir string
	port string
	address string
	verbose bool
)

var (
	d Query
)

func init() {
	flag.StringVar(&rootdir, "root", "current directory", "webroot directory")
	flag.StringVar(&rootdir, "d", "current directory", "webroot directory" + " (shorthand)")
	flag.StringVar(&port,"port", "8080", "listen port")
	flag.StringVar(&port,"p", "8080", "listen port" + " (shorthand)")
	flag.StringVar(&address, "address", "*", "listen address")
	flag.StringVar(&address, "l", "*", "listen address" + " (shorthand)")
	flag.BoolVar(&verbose, "verbose", false, "be verbose")
	flag.BoolVar(&verbose, "v", false, "be verbose" + " (shorthand)")

	if rootdir == "current directory" {
		rootdir, err :=  filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			log.Fatal(err)
		}
		log.Print("Webroot set to \"" + rootdir + "\".")
	}
	if address == "*" {
		address = ""
	}
}

func debug(s string) {
	if verbose {
		log.Print(s)
	}
}

func debugln(v ...interface{}) {
	if verbose {
		log.Println(v ...)
	}
}

var validLog = regexp.MustCompile("^/log/([a-zA-Z0-9-]+)/([0-9]+[.]{0,1}[0-9]*)(/([ts])/([0-9]+))?/?$")

func logHandler(w http.ResponseWriter, r *http.Request) {
	m := validLog.FindStringSubmatch(r.URL.Path)
	if len(m) == 0 {
		http.Error(w, "Sensor not found", http.StatusNotFound)
		return
	}
	debug("Sensor " + m[1] + " sent value " + m[2])
	//if ! d.Exists(m[1]) {
	//	d.Add(m[1])
	//}
	v, _ := strconv.ParseFloat(m[2], 64)
	if m[4] != "" {
		t, _ := strconv.ParseInt(m[5], 10, 64)
		if m[4] == "t" {
			d.StoreT(m[1], v, time.Unix(t, 0))
		} else {
			d.StoreT(m[1], v, time.Unix(time.Now().Unix() - t, 0))
		}
	} else {
		d.Store(m[1], v)
	}
	debugln("Sensor " + m[1] + " now contains:", d.Load(m[1]))
	fmt.Fprintf(w, "ok")

}

func main() {
	flag.Parse()
	t := Database{ make(map[string] sensorlog) }
	d = t
	http.HandleFunc("/log/", logHandler)
	http.Handle("/", http.FileServer(http.Dir(rootdir)))

	log.Print("Starting webserver. Listening on " + address + ":" + port)
	err := http.ListenAndServe(address + ":" + port, nil)
	if err != nil {
		log.Fatal("Couldn't start server. ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>// The macaroon package implements macaroons as described in
// the paper "Macaroons: Cookies with Contextual Caveats for
// Decentralized Authorization in the Cloud"
// (http://theory.stanford.edu/~ataly/Papers/macaroons.pdf)
package macaroon

import (
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"io"
)

// Macaroon holds a macaroon.
// See Fig. 7 of http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
// for a description of the data contained within.
// Macaroons are mutable objects - use Clone as appropriate
// to avoid unwanted mutation.
type Macaroon struct {
	// data holds the binary-marshalled form
	// of the macaroon data.
	data []byte

	location packet
	id       packet
	caveats  []caveat
	sig      []byte
}

// caveat holds a first person or third party caveat.
type caveat struct {
	location       packet
	caveatId       packet
	verificationId packet
}

type Caveat struct {
	Id       string
	Location string
}

// isThirdParty reports whether the caveat must be satisfied
// by some third party (if not, it's a first person caveat).
func (cav *caveat) isThirdParty() bool {
	return cav.verificationId.len() > 0
}

// New returns a new macaroon with the given root key,
// identifier and location.
func New(rootKey []byte, id, loc string) (*Macaroon, error) {
	var m Macaroon
	if err := m.init(id, loc); err != nil {
		return nil, err
	}
	m.sig = keyedHash(rootKey, m.dataBytes(m.id))
	return &m, nil
}

func (m *Macaroon) init(id, loc string) error {
	var ok bool
	m.location, ok = m.appendPacket(fieldLocation, []byte(loc))
	if !ok {
		return fmt.Errorf("macaroon location too big")
	}
	m.id, ok = m.appendPacket(fieldIdentifier, []byte(id))
	if !ok {
		return fmt.Errorf("macaroon identifier too big")
	}
	return nil
}

// Clone returns a copy of the receiving macaroon.
func (m *Macaroon) Clone() *Macaroon {
	m1 := *m
	// Ensure that if any data is appended to the new
	// macaroon, it will copy data and caveats.
	m1.data = m1.data[0:len(m1.data):len(m1.data)]
	m1.caveats = m1.caveats[0:len(m1.caveats):len(m1.caveats)]
	m1.sig = append([]byte(nil), m.sig...)
	return &m1
}

// Location returns the macaroon's location hint. This is
// not verified as part of the macaroon.
func (m *Macaroon) Location() string {
	return m.dataStr(m.location)
}

// Id returns the id of the macaroon. This can hold
// arbitrary information.
func (m *Macaroon) Id() string {
	return m.dataStr(m.id)
}

// Signature returns the macaroon's signature.
func (m *Macaroon) Signature() []byte {
	return append([]byte(nil), m.sig...)
}

// Caveats returns the macaroon's caveats.
// This method will probably change, and it's important not to change the returned caveat.
func (m *Macaroon) Caveats() []Caveat {
	caveats := make([]Caveat, len(m.caveats))
	for i, cav := range m.caveats {
		caveats[i] = Caveat{
			Id:       m.dataStr(cav.caveatId),
			Location: m.dataStr(cav.location),
		}
	}
	return caveats
}

// appendCaveat appends a caveat without modifying the macaroon's signature.
func (m *Macaroon) appendCaveat(caveatId string, verificationId []byte, loc string) (*caveat, error) {
	var cav caveat
	var ok bool
	if caveatId != "" {
		cav.caveatId, ok = m.appendPacket(fieldCaveatId, []byte(caveatId))
		if !ok {
			return nil, fmt.Errorf("caveat identifier too big")
		}
	}
	if len(verificationId) > 0 {
		cav.verificationId, ok = m.appendPacket(fieldVerificationId, verificationId)
		if !ok {
			return nil, fmt.Errorf("caveat verification id too big")
		}
	}
	if loc != "" {
		cav.location, ok = m.appendPacket(fieldCaveatLocation, []byte(loc))
		if !ok {
			return nil, fmt.Errorf("caveat location too big")
		}
	}
	m.caveats = append(m.caveats, cav)
	return &m.caveats[len(m.caveats)-1], nil
}

func (m *Macaroon) addCaveat(caveatId string, verificationId []byte, loc string) error {
	cav, err := m.appendCaveat(caveatId, verificationId, loc)
	if err != nil {
		return err
	}
	sig := keyedHasher(m.sig)
	sig.Write(m.dataBytes(cav.verificationId))
	sig.Write(m.dataBytes(cav.caveatId))
	m.sig = sig.Sum(m.sig[:0])
	return nil
}

// Bind prepares the macaroon for being used to discharge the
// macaroon with the given rootSig. This must be
// used before it is used in the discharges argument to Verify.
func (m *Macaroon) Bind(rootSig []byte) {
	m.sig = bindForRequest(rootSig, m.sig)
}

// AddFirstPartyCaveat adds a caveat that will be verified
// by the target service.
func (m *Macaroon) AddFirstPartyCaveat(caveatId string) error {
	return m.addCaveat(caveatId, nil, "")
}

// AddThirdPartyCaveat adds a third-party caveat to the macaroon,
// using the given shared root key, caveat id and location hint.
// The caveat id should encode the root key in some
// way, either by encrypting it with a key known to the third party
// or by holding a reference to it stored in the third party's
// storage.
func (m *Macaroon) AddThirdPartyCaveat(rootKey []byte, caveatId string, loc string) error {
	return m.addThirdPartyCaveatWithRand(rootKey, caveatId, loc, rand.Reader)
}

func (m *Macaroon) addThirdPartyCaveatWithRand(rootKey []byte, caveatId string, loc string, r io.Reader) error {
	verificationId, err := encrypt(m.sig, rootKey, r)
	if err != nil {
		return err
	}
	return m.addCaveat(caveatId, verificationId, loc)
}

// bndForRequest binds the given macaroon
// to the given signature of its parent macaroon.
func bindForRequest(rootSig, dischargeSig []byte) []byte {
	if bytes.Equal(rootSig, dischargeSig) {
		return rootSig
	}
	sig := sha256.New()
	sig.Write(rootSig)
	sig.Write(dischargeSig)
	return sig.Sum(nil)
}

// Verify verifies that the receiving macaroon is valid.
// The root key must be the same that the macaroon was originally
// minted with. The check function is called to verify each
// first-party caveat - it should return an error if the
// condition is not met.
//
// The discharge macaroons should be provided in discharges.
//
// Verify returns true if the verification succeeds; if returns
// (false, nil) if the verification fails, and (false, err) if
// the verification cannot be asserted (but may not be false).
//
// TODO(rog) is there a possible DOS attack that can cause this
// function to infinitely recurse?
func (m *Macaroon) Verify(rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error {
	// TODO(rog) consider distinguishing between classes of
	// check error - some errors may be resolved by minting
	// a new macaroon; others may not.
	return m.verify(m.sig, rootKey, check, discharges)
}

func (m *Macaroon) verify(rootSig []byte, rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error {
	if len(rootSig) == 0 {
		rootSig = m.sig
	}
	caveatSig := keyedHash(rootKey, m.dataBytes(m.id))
	for i, cav := range m.caveats {
		if cav.isThirdParty() {
			cavKey, err := decrypt(caveatSig, m.dataBytes(cav.verificationId))
			if err != nil {
				return fmt.Errorf("failed to decrypt caveat %d signature: %v", i, err)
			}
			// We choose an arbitrary error from one of the
			// possible discharge macaroon verifications
			// if there's more than one discharge macaroon
			// with the required id.
			var verifyErr error
			found := false
			for _, dm := range discharges {
				if !bytes.Equal(dm.dataBytes(dm.id), m.dataBytes(cav.caveatId)) {
					continue
				}
				found = true
				verifyErr = dm.verify(rootSig, cavKey, check, discharges)
				if verifyErr == nil {
					break
				}
			}
			if !found {
				return fmt.Errorf("cannot find discharge macaroon for caveat %q", m.dataBytes(cav.caveatId))
			}
			if verifyErr != nil {
				return verifyErr
			}
		} else {
			if err := check(string(m.dataBytes(cav.caveatId))); err != nil {
				return err
			}
		}
		sig := keyedHasher(caveatSig)
		sig.Write(m.dataBytes(cav.verificationId))
		sig.Write(m.dataBytes(cav.caveatId))
		caveatSig = sig.Sum(caveatSig[:0])
	}
	// TODO perhaps we should actually do this check before doing
	// all the potentially expensive caveat checks.
	boundSig := bindForRequest(rootSig, caveatSig)
	if !hmac.Equal(boundSig, m.sig) {
		return fmt.Errorf("signature mismatch after caveat verification")
	}
	return nil
}

type Verifier interface {
	Verify(m *Macaroon, rootKey []byte) (bool, error)
}
<commit_msg>clarify usage of Macaroon.Bind<commit_after>// The macaroon package implements macaroons as described in
// the paper "Macaroons: Cookies with Contextual Caveats for
// Decentralized Authorization in the Cloud"
// (http://theory.stanford.edu/~ataly/Papers/macaroons.pdf)
package macaroon

import (
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"io"
)

// Macaroon holds a macaroon.
// See Fig. 7 of http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
// for a description of the data contained within.
// Macaroons are mutable objects - use Clone as appropriate
// to avoid unwanted mutation.
type Macaroon struct {
	// data holds the binary-marshalled form
	// of the macaroon data.
	data []byte

	location packet
	id       packet
	caveats  []caveat
	sig      []byte
}

// caveat holds a first person or third party caveat.
type caveat struct {
	location       packet
	caveatId       packet
	verificationId packet
}

type Caveat struct {
	Id       string
	Location string
}

// isThirdParty reports whether the caveat must be satisfied
// by some third party (if not, it's a first person caveat).
func (cav *caveat) isThirdParty() bool {
	return cav.verificationId.len() > 0
}

// New returns a new macaroon with the given root key,
// identifier and location.
func New(rootKey []byte, id, loc string) (*Macaroon, error) {
	var m Macaroon
	if err := m.init(id, loc); err != nil {
		return nil, err
	}
	m.sig = keyedHash(rootKey, m.dataBytes(m.id))
	return &m, nil
}

func (m *Macaroon) init(id, loc string) error {
	var ok bool
	m.location, ok = m.appendPacket(fieldLocation, []byte(loc))
	if !ok {
		return fmt.Errorf("macaroon location too big")
	}
	m.id, ok = m.appendPacket(fieldIdentifier, []byte(id))
	if !ok {
		return fmt.Errorf("macaroon identifier too big")
	}
	return nil
}

// Clone returns a copy of the receiving macaroon.
func (m *Macaroon) Clone() *Macaroon {
	m1 := *m
	// Ensure that if any data is appended to the new
	// macaroon, it will copy data and caveats.
	m1.data = m1.data[0:len(m1.data):len(m1.data)]
	m1.caveats = m1.caveats[0:len(m1.caveats):len(m1.caveats)]
	m1.sig = append([]byte(nil), m.sig...)
	return &m1
}

// Location returns the macaroon's location hint. This is
// not verified as part of the macaroon.
func (m *Macaroon) Location() string {
	return m.dataStr(m.location)
}

// Id returns the id of the macaroon. This can hold
// arbitrary information.
func (m *Macaroon) Id() string {
	return m.dataStr(m.id)
}

// Signature returns the macaroon's signature.
func (m *Macaroon) Signature() []byte {
	return append([]byte(nil), m.sig...)
}

// Caveats returns the macaroon's caveats.
// This method will probably change, and it's important not to change the returned caveat.
func (m *Macaroon) Caveats() []Caveat {
	caveats := make([]Caveat, len(m.caveats))
	for i, cav := range m.caveats {
		caveats[i] = Caveat{
			Id:       m.dataStr(cav.caveatId),
			Location: m.dataStr(cav.location),
		}
	}
	return caveats
}

// appendCaveat appends a caveat without modifying the macaroon's signature.
func (m *Macaroon) appendCaveat(caveatId string, verificationId []byte, loc string) (*caveat, error) {
	var cav caveat
	var ok bool
	if caveatId != "" {
		cav.caveatId, ok = m.appendPacket(fieldCaveatId, []byte(caveatId))
		if !ok {
			return nil, fmt.Errorf("caveat identifier too big")
		}
	}
	if len(verificationId) > 0 {
		cav.verificationId, ok = m.appendPacket(fieldVerificationId, verificationId)
		if !ok {
			return nil, fmt.Errorf("caveat verification id too big")
		}
	}
	if loc != "" {
		cav.location, ok = m.appendPacket(fieldCaveatLocation, []byte(loc))
		if !ok {
			return nil, fmt.Errorf("caveat location too big")
		}
	}
	m.caveats = append(m.caveats, cav)
	return &m.caveats[len(m.caveats)-1], nil
}

func (m *Macaroon) addCaveat(caveatId string, verificationId []byte, loc string) error {
	cav, err := m.appendCaveat(caveatId, verificationId, loc)
	if err != nil {
		return err
	}
	sig := keyedHasher(m.sig)
	sig.Write(m.dataBytes(cav.verificationId))
	sig.Write(m.dataBytes(cav.caveatId))
	m.sig = sig.Sum(m.sig[:0])
	return nil
}

// Bind prepares the macaroon for being used to discharge the
// macaroon with the given signature sig. This must be
// used before it is used in the discharges argument to Verify.
func (m *Macaroon) Bind(sig []byte) {
	m.sig = bindForRequest(sig, m.sig)
}

// AddFirstPartyCaveat adds a caveat that will be verified
// by the target service.
func (m *Macaroon) AddFirstPartyCaveat(caveatId string) error {
	return m.addCaveat(caveatId, nil, "")
}

// AddThirdPartyCaveat adds a third-party caveat to the macaroon,
// using the given shared root key, caveat id and location hint.
// The caveat id should encode the root key in some
// way, either by encrypting it with a key known to the third party
// or by holding a reference to it stored in the third party's
// storage.
func (m *Macaroon) AddThirdPartyCaveat(rootKey []byte, caveatId string, loc string) error {
	return m.addThirdPartyCaveatWithRand(rootKey, caveatId, loc, rand.Reader)
}

func (m *Macaroon) addThirdPartyCaveatWithRand(rootKey []byte, caveatId string, loc string, r io.Reader) error {
	verificationId, err := encrypt(m.sig, rootKey, r)
	if err != nil {
		return err
	}
	return m.addCaveat(caveatId, verificationId, loc)
}

// bndForRequest binds the given macaroon
// to the given signature of its parent macaroon.
func bindForRequest(rootSig, dischargeSig []byte) []byte {
	if bytes.Equal(rootSig, dischargeSig) {
		return rootSig
	}
	sig := sha256.New()
	sig.Write(rootSig)
	sig.Write(dischargeSig)
	return sig.Sum(nil)
}

// Verify verifies that the receiving macaroon is valid.
// The root key must be the same that the macaroon was originally
// minted with. The check function is called to verify each
// first-party caveat - it should return an error if the
// condition is not met.
//
// The discharge macaroons should be provided in discharges.
//
// Verify returns true if the verification succeeds; if returns
// (false, nil) if the verification fails, and (false, err) if
// the verification cannot be asserted (but may not be false).
//
// TODO(rog) is there a possible DOS attack that can cause this
// function to infinitely recurse?
func (m *Macaroon) Verify(rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error {
	// TODO(rog) consider distinguishing between classes of
	// check error - some errors may be resolved by minting
	// a new macaroon; others may not.
	return m.verify(m.sig, rootKey, check, discharges)
}

func (m *Macaroon) verify(rootSig []byte, rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error {
	if len(rootSig) == 0 {
		rootSig = m.sig
	}
	caveatSig := keyedHash(rootKey, m.dataBytes(m.id))
	for i, cav := range m.caveats {
		if cav.isThirdParty() {
			cavKey, err := decrypt(caveatSig, m.dataBytes(cav.verificationId))
			if err != nil {
				return fmt.Errorf("failed to decrypt caveat %d signature: %v", i, err)
			}
			// We choose an arbitrary error from one of the
			// possible discharge macaroon verifications
			// if there's more than one discharge macaroon
			// with the required id.
			var verifyErr error
			found := false
			for _, dm := range discharges {
				if !bytes.Equal(dm.dataBytes(dm.id), m.dataBytes(cav.caveatId)) {
					continue
				}
				found = true
				verifyErr = dm.verify(rootSig, cavKey, check, discharges)
				if verifyErr == nil {
					break
				}
			}
			if !found {
				return fmt.Errorf("cannot find discharge macaroon for caveat %q", m.dataBytes(cav.caveatId))
			}
			if verifyErr != nil {
				return verifyErr
			}
		} else {
			if err := check(string(m.dataBytes(cav.caveatId))); err != nil {
				return err
			}
		}
		sig := keyedHasher(caveatSig)
		sig.Write(m.dataBytes(cav.verificationId))
		sig.Write(m.dataBytes(cav.caveatId))
		caveatSig = sig.Sum(caveatSig[:0])
	}
	// TODO perhaps we should actually do this check before doing
	// all the potentially expensive caveat checks.
	boundSig := bindForRequest(rootSig, caveatSig)
	if !hmac.Equal(boundSig, m.sig) {
		return fmt.Errorf("signature mismatch after caveat verification")
	}
	return nil
}

type Verifier interface {
	Verify(m *Macaroon, rootKey []byte) (bool, error)
}
<|endoftext|>


<commit_before>// +build mage

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps
	"github.com/magefile/mage/sh" // mg contains helpful utility functions, like Deps
)

const WORK_DIR = "./sparta"

var header = strings.Repeat("-", 80)

var ignoreSubdirectoryPaths = []string{
	".vendor",
	".sparta",
	".vscode",
	"/resources/describe",
}

// Default target to run when none is specified
// If not set, running mage will list available targets
// var Default = Build

func mageScript(commands [][]string) error {
	for _, eachCommand := range commands {
		var commandErr error
		if len(eachCommand) <= 1 {
			commandErr = sh.Run(eachCommand[0])
		} else {
			commandErr = sh.Run(eachCommand[0], eachCommand[1:]...)
		}
		if commandErr != nil {
			return commandErr
		}
	}
	return nil
}
func mageLog(formatSpecifier string, args ...interface{}) {
	if mg.Verbose() {
		if len(args) != 0 {
			log.Printf(formatSpecifier, args...)
		} else {
			log.Printf(formatSpecifier)
		}
	}
}

func sourceFilesOfType(extension string) ([]string, error) {
	testExtension := strings.TrimPrefix(extension, ".")
	testExtension = fmt.Sprintf(".%s", testExtension)

	files := make([]string, 0)
	walker := func(path string, info os.FileInfo, err error) error {
		contains := false
		for _, eachComponent := range ignoreSubdirectoryPaths {
			contains = strings.Contains(path, eachComponent)
			if contains {
				break
			}
		}
		if !contains && (filepath.Ext(path) == testExtension) {
			files = append(files, path)
		}
		return nil
	}
	goSourceFilesErr := filepath.Walk(".", walker)
	return files, goSourceFilesErr
}

func applyToSource(fileExtension string, commandParts ...string) error {
	if len(commandParts) <= 0 {
		return errors.New("applyToSource requires a command to apply to source files")
	}
	eligibleSourceFiles, eligibleSourceFilesErr := sourceFilesOfType(fileExtension)
	if eligibleSourceFilesErr != nil {
		return eligibleSourceFilesErr
	}

	mageLog(header)
	mageLog("Applying `%s` to %d *.%s` source files", commandParts[0], len(eligibleSourceFiles), fileExtension)
	mageLog(header)

	commandArgs := []string{}
	if len(commandParts) > 1 {
		for _, eachPart := range commandParts[1:] {
			commandArgs = append(commandArgs, eachPart)
		}
	}
	for _, eachFile := range eligibleSourceFiles {
		applyArgs := append(commandArgs, eachFile)
		applyErr := sh.Run(commandParts[0], applyArgs...)
		if applyErr != nil {
			return applyErr
		}
	}
	return nil
}

func markdownSourceApply(commandParts ...string) error {
	return applyToSource("md", commandParts...)
}
func goSourceApply(commandParts ...string) error {
	return applyToSource("go", commandParts...)
}

// GenerateBuildInfo creates the automatic buildinfo.go file so that we can
// stamp the SHA into the binaries we build...
func GenerateBuildInfo() error {
	// The first thing we need is the `git` SHA
	cmd := exec.Command("git", "rev-parse", "HEAD")
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		return err
	}
	stdOutResult := strings.TrimSpace(string(stdout.Bytes()))

	// Super = update the buildinfo data
	buildInfoTemplate := `package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: %s

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "%s"
`
	updatedInfo := fmt.Sprintf(buildInfoTemplate, time.Now().UTC(), stdOutResult)
	// Write it to the output location...
	return ioutil.WriteFile("./buildinfo.go", []byte(updatedInfo), os.ModePerm)
}

// GenerateConstants runs the set of commands that update the embedded CONSTANTS
// for both local and AWS Lambda execution
func GenerateConstants() error {
	generateCommands := [][]string{
		// Create the embedded version
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS.go", "-private", "-pkg", "sparta", "./resources"},
		//Create a secondary CONSTANTS_AWSBINARY.go file with empty content.
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS_AWSBINARY.go", "-private", "-pkg", "sparta", "./resources/awsbinary/README.md"},
		//The next step will insert the
		// build tags at the head of each file so that they are mutually exclusive
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS", "!lambdabinary"},
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS_AWSBINARY", "lambdabinary"},
		{"git", "commit", "-a", "-m", "Autogenerated constants"},
	}
	return mageScript(generateCommands)
}

// InstallBuildRequirements installs or updates the dependent
// packages that aren't referenced by the source, but are needed
// to build the Sparta source
func InstallBuildRequirements() error {
	mageLog("`go get` update flags (env.GO_GET_FLAG): %s", os.Getenv("GO_GET_FLAG"))

	requirements := []string{
		"github.com/golang/dep/...",
		"honnef.co/go/tools/cmd/megacheck",
		"honnef.co/go/tools/cmd/gosimple",
		"honnef.co/go/tools/cmd/unused",
		"honnef.co/go/tools/cmd/staticcheck",
		"golang.org/x/tools/cmd/goimports",
		"github.com/fzipp/gocyclo",
		"github.com/golang/lint/golint",
		"github.com/mjibson/esc",
		"github.com/securego/gosec/cmd/gosec/...",
		"github.com/client9/misspell/cmd/misspell",
	}
	for _, eachDep := range requirements {
		cmdErr := sh.Run("go",
			"get",
			os.Getenv("GO_GET_FLAG"),
			eachDep)

		// cmdErr := exec.Command(.Run()
		if cmdErr != nil {
			return cmdErr
		}
	}
	return nil
}

// EnsureSpelling ensures that there are no misspellings in the source
func EnsureSpelling() error {
	goSpelling := func() error {
		return goSourceApply("misspell", "-error")
	}
	mdSpelling := func() error {
		return markdownSourceApply("misspell", "-error")
	}
	mg.SerialDeps(
		goSpelling,
		mdSpelling)
	return nil
}

// EnsureVet ensures that the source has been `go vet`ted
func EnsureVet() error {
	return goSourceApply("go", "tool", "vet")
}

// EnsureLint ensures that the source is `golint`ed
func EnsureLint() error {
	return goSourceApply("golint")
}

// EnsureFormatted ensures that the source code is formatted with goimports
func EnsureFormatted() error {
	return goSourceApply("goimports", "-d")
}

// EnsureStaticChecks ensures that the source code passes static code checks
func EnsureStaticChecks() error {
	// Megacheck
	megacheckErr := sh.Run("megacheck",
		"-ignore",
		"github.com/mweagle/Sparta/CONSTANTS.go:*")
	if megacheckErr != nil {
		return megacheckErr
	}
	// Gosec
	return sh.Run("gosec",
		"-exclude=G204,G505,G401",
		"./...")
}

// EnsureAllPreconditions ensures that the source passes *ALL* static `ensure*`
// precondition steps
func EnsureAllPreconditions() error {
	mg.SerialDeps(
		InstallBuildRequirements,
		EnsureVet,
		EnsureLint,
		EnsureFormatted,
		EnsureStaticChecks,
		EnsureSpelling,
	)
	return nil
}

// EnsureTravisBuildEnvironment is the command that sets up the Travis
// environment to run the build.
func EnsureTravisBuildEnvironment() error {
	mg.SerialDeps(InstallBuildRequirements)

	// Super run some commands
	travisComands := [][]string{
		[]string{"dep", "version"},
		[]string{"dep", "ensure"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(travisComands)
}

// Build the application
func Build() error {
	mg.Deps(EnsureAllPreconditions)
	return sh.Run("go", "build", ".")
}

// Clean the working directory
func Clean() error {
	cleanCommands := [][]string{
		[]string{"go", "clean", "."},
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(cleanCommands)
}

// Describe runs the `TestDescribe` test to generate a describe HTML output
// file at graph.html
func Describe() error {
	describeCommands := [][]string{
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"go", "test", "-v", "-run", "TestDescribe"},
	}
	return mageScript(describeCommands)
}

// Publish the latest source
func Publish() error {
	mg.SerialDeps(GenerateBuildInfo)

	describeCommands := [][]string{
		[]string{"echo", "Checking `git` tree status"},
		[]string{"git", "diff", "--exit-code"},
		// TODO - migrate to Go
		[]string{"./buildinfo.sh"},
		[]string{"git", "commit", "-a", "-m", "Tagging Sparta commit"},
		[]string{"git", "push", "origin"},
	}
	return mageScript(describeCommands)
}

// Test runs the Sparta tests
func Test() {
	testCommand := func() error {
		return sh.Run("go",
			"test",
			"-cover",
			"-race",
			"./...")
	}
	mg.SerialDeps(
		EnsureAllPreconditions,
		testCommand,
	)
}

// TestCover runs the test and opens up the resulting report
func TestCover() error {
	// mg.SerialDeps(
	// 	EnsureAllPreconditions,
	// )
	coverageReport := fmt.Sprintf("%s/cover.out", WORK_DIR)
	testCoverCommands := [][]string{
		[]string{"go", "test", fmt.Sprintf("-coverprofile=%s", coverageReport), "."},
		[]string{"go", "tool", "cover", fmt.Sprintf("-html=%s", coverageReport)},
		[]string{"rm", coverageReport},
		[]string{"open", fmt.Sprintf("%s/cover.html", WORK_DIR)},
	}
	return mageScript(testCoverCommands)
}

// TravisBuild is the task to build in the context of a Travis CI pipeline
func TravisBuild() error {
	mg.SerialDeps(EnsureTravisBuildEnvironment,
		Build,
		Test)
	return nil
}
<commit_msg>Fix typo in apply error<commit_after>// +build mage

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps
	"github.com/magefile/mage/sh" // mg contains helpful utility functions, like Deps
)

const WORK_DIR = "./sparta"

var header = strings.Repeat("-", 80)

var ignoreSubdirectoryPaths = []string{
	".vendor",
	".sparta",
	".vscode",
	"/resources/describe",
}

// Default target to run when none is specified
// If not set, running mage will list available targets
// var Default = Build

func mageScript(commands [][]string) error {
	for _, eachCommand := range commands {
		var commandErr error
		if len(eachCommand) <= 1 {
			commandErr = sh.Run(eachCommand[0])
		} else {
			commandErr = sh.Run(eachCommand[0], eachCommand[1:]...)
		}
		if commandErr != nil {
			return commandErr
		}
	}
	return nil
}
func mageLog(formatSpecifier string, args ...interface{}) {
	if mg.Verbose() {
		if len(args) != 0 {
			log.Printf(formatSpecifier, args...)
		} else {
			log.Printf(formatSpecifier)
		}
	}
}

func sourceFilesOfType(extension string) ([]string, error) {
	testExtension := strings.TrimPrefix(extension, ".")
	testExtension = fmt.Sprintf(".%s", testExtension)

	files := make([]string, 0)
	walker := func(path string, info os.FileInfo, err error) error {
		contains := false
		for _, eachComponent := range ignoreSubdirectoryPaths {
			contains = strings.Contains(path, eachComponent)
			if contains {
				break
			}
		}
		if !contains && (filepath.Ext(path) == testExtension) {
			files = append(files, path)
		}
		return nil
	}
	goSourceFilesErr := filepath.Walk(".", walker)
	return files, goSourceFilesErr
}

func applyToSource(fileExtension string, commandParts ...string) error {
	if len(commandParts) <= 0 {
		return errors.New("applyToSource requires a command to apply to source files")
	}
	eligibleSourceFiles, eligibleSourceFilesErr := sourceFilesOfType(fileExtension)
	if eligibleSourceFilesErr != nil {
		return eligibleSourceFilesErr
	}

	mageLog(header)
	mageLog("Applying `%s` to %d `*.%s` source files", commandParts[0], len(eligibleSourceFiles), fileExtension)
	mageLog(header)

	commandArgs := []string{}
	if len(commandParts) > 1 {
		for _, eachPart := range commandParts[1:] {
			commandArgs = append(commandArgs, eachPart)
		}
	}
	for _, eachFile := range eligibleSourceFiles {
		applyArgs := append(commandArgs, eachFile)
		applyErr := sh.Run(commandParts[0], applyArgs...)
		if applyErr != nil {
			return applyErr
		}
	}
	return nil
}

func markdownSourceApply(commandParts ...string) error {
	return applyToSource("md", commandParts...)
}
func goSourceApply(commandParts ...string) error {
	return applyToSource("go", commandParts...)
}

// GenerateBuildInfo creates the automatic buildinfo.go file so that we can
// stamp the SHA into the binaries we build...
func GenerateBuildInfo() error {
	// The first thing we need is the `git` SHA
	cmd := exec.Command("git", "rev-parse", "HEAD")
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		return err
	}
	stdOutResult := strings.TrimSpace(string(stdout.Bytes()))

	// Super = update the buildinfo data
	buildInfoTemplate := `package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: %s

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "%s"
`
	updatedInfo := fmt.Sprintf(buildInfoTemplate, time.Now().UTC(), stdOutResult)
	// Write it to the output location...
	return ioutil.WriteFile("./buildinfo.go", []byte(updatedInfo), os.ModePerm)
}

// GenerateConstants runs the set of commands that update the embedded CONSTANTS
// for both local and AWS Lambda execution
func GenerateConstants() error {
	generateCommands := [][]string{
		// Create the embedded version
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS.go", "-private", "-pkg", "sparta", "./resources"},
		//Create a secondary CONSTANTS_AWSBINARY.go file with empty content.
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS_AWSBINARY.go", "-private", "-pkg", "sparta", "./resources/awsbinary/README.md"},
		//The next step will insert the
		// build tags at the head of each file so that they are mutually exclusive
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS", "!lambdabinary"},
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS_AWSBINARY", "lambdabinary"},
		{"git", "commit", "-a", "-m", "Autogenerated constants"},
	}
	return mageScript(generateCommands)
}

// InstallBuildRequirements installs or updates the dependent
// packages that aren't referenced by the source, but are needed
// to build the Sparta source
func InstallBuildRequirements() error {
	mageLog("`go get` update flags (env.GO_GET_FLAG): %s", os.Getenv("GO_GET_FLAG"))

	requirements := []string{
		"github.com/golang/dep/...",
		"honnef.co/go/tools/cmd/megacheck",
		"honnef.co/go/tools/cmd/gosimple",
		"honnef.co/go/tools/cmd/unused",
		"honnef.co/go/tools/cmd/staticcheck",
		"golang.org/x/tools/cmd/goimports",
		"github.com/fzipp/gocyclo",
		"github.com/golang/lint/golint",
		"github.com/mjibson/esc",
		"github.com/securego/gosec/cmd/gosec/...",
		"github.com/client9/misspell/cmd/misspell",
	}
	for _, eachDep := range requirements {
		cmdErr := sh.Run("go",
			"get",
			os.Getenv("GO_GET_FLAG"),
			eachDep)

		// cmdErr := exec.Command(.Run()
		if cmdErr != nil {
			return cmdErr
		}
	}
	return nil
}

// EnsureSpelling ensures that there are no misspellings in the source
func EnsureSpelling() error {
	goSpelling := func() error {
		return goSourceApply("misspell", "-error")
	}
	mdSpelling := func() error {
		return markdownSourceApply("misspell", "-error")
	}
	mg.SerialDeps(
		goSpelling,
		mdSpelling)
	return nil
}

// EnsureVet ensures that the source has been `go vet`ted
func EnsureVet() error {
	return goSourceApply("go", "tool", "vet")
}

// EnsureLint ensures that the source is `golint`ed
func EnsureLint() error {
	return goSourceApply("golint")
}

// EnsureFormatted ensures that the source code is formatted with goimports
func EnsureFormatted() error {
	return goSourceApply("goimports", "-d")
}

// EnsureStaticChecks ensures that the source code passes static code checks
func EnsureStaticChecks() error {
	// Megacheck
	megacheckErr := sh.Run("megacheck",
		"-ignore",
		"github.com/mweagle/Sparta/CONSTANTS.go:*")
	if megacheckErr != nil {
		return megacheckErr
	}
	// Gosec
	return sh.Run("gosec",
		"-exclude=G204,G505,G401",
		"./...")
}

// EnsureAllPreconditions ensures that the source passes *ALL* static `ensure*`
// precondition steps
func EnsureAllPreconditions() error {
	mg.SerialDeps(
		InstallBuildRequirements,
		EnsureVet,
		EnsureLint,
		EnsureFormatted,
		EnsureStaticChecks,
		EnsureSpelling,
	)
	return nil
}

// EnsureTravisBuildEnvironment is the command that sets up the Travis
// environment to run the build.
func EnsureTravisBuildEnvironment() error {
	mg.SerialDeps(InstallBuildRequirements)

	// Super run some commands
	travisComands := [][]string{
		[]string{"dep", "version"},
		[]string{"dep", "ensure"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(travisComands)
}

// Build the application
func Build() error {
	mg.Deps(EnsureAllPreconditions)
	return sh.Run("go", "build", ".")
}

// Clean the working directory
func Clean() error {
	cleanCommands := [][]string{
		[]string{"go", "clean", "."},
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(cleanCommands)
}

// Describe runs the `TestDescribe` test to generate a describe HTML output
// file at graph.html
func Describe() error {
	describeCommands := [][]string{
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"go", "test", "-v", "-run", "TestDescribe"},
	}
	return mageScript(describeCommands)
}

// Publish the latest source
func Publish() error {
	mg.SerialDeps(GenerateBuildInfo)

	describeCommands := [][]string{
		[]string{"echo", "Checking `git` tree status"},
		[]string{"git", "diff", "--exit-code"},
		// TODO - migrate to Go
		[]string{"./buildinfo.sh"},
		[]string{"git", "commit", "-a", "-m", "Tagging Sparta commit"},
		[]string{"git", "push", "origin"},
	}
	return mageScript(describeCommands)
}

// Test runs the Sparta tests
func Test() {
	testCommand := func() error {
		return sh.Run("go",
			"test",
			"-cover",
			"-race",
			"./...")
	}
	mg.SerialDeps(
		EnsureAllPreconditions,
		testCommand,
	)
}

// TestCover runs the test and opens up the resulting report
func TestCover() error {
	// mg.SerialDeps(
	// 	EnsureAllPreconditions,
	// )
	coverageReport := fmt.Sprintf("%s/cover.out", WORK_DIR)
	testCoverCommands := [][]string{
		[]string{"go", "test", fmt.Sprintf("-coverprofile=%s", coverageReport), "."},
		[]string{"go", "tool", "cover", fmt.Sprintf("-html=%s", coverageReport)},
		[]string{"rm", coverageReport},
		[]string{"open", fmt.Sprintf("%s/cover.html", WORK_DIR)},
	}
	return mageScript(testCoverCommands)
}

// TravisBuild is the task to build in the context of a Travis CI pipeline
func TravisBuild() error {
	mg.SerialDeps(EnsureTravisBuildEnvironment,
		Build,
		Test)
	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"bytes"
	"code.google.com/p/go.tools/go/types"
	"fmt"
	"gnd.la/gen/genutil"
	"gnd.la/loaders"
	"gnd.la/log"
	"io/ioutil"
	"launchpad.net/goyaml"
	"path/filepath"
	"regexp"
	"strings"
)

const appFilename = "app.yaml"

type Templates struct {
	Path  string            `yaml:"path"`
	Hooks map[string]string `yaml:"hooks"`
}

type App struct {
	Dir       string
	Name      string            `yaml:"name"`
	Handlers  map[string]string `yaml:"handlers"`
	Vars      map[string]string `yaml:"vars"`
	Templates *Templates        `yaml:"templates"`
	Assets    string            `yaml:"assets"`
}

func (app *App) writeLoader(buf *bytes.Buffer, dir string, release bool) error {
	if release {
		return loaders.Bake(buf, dir, nil, loaders.CompressTgz)
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		return err
	}
	fmt.Fprintf(buf, "loaders.MemLoader(loaders.FSLoader(%q))\n", abs)
	return nil
}

func (app *App) Gen(release bool) error {
	pkg, err := genutil.NewPackage(app.Dir)
	if err != nil {
		return err
	}
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "package %s\n\n", pkg.Name())
	buf.WriteString(genutil.AutogenString())
	buf.WriteString("import (\n\"gnd.la/app\"\n\"gnd.la/loaders\"\n\"gnd.la/template\"\n\"gnd.la/template/assets\"\n)\n")
	buf.WriteString("var _ = loaders.FSLoader\n")
	buf.WriteString("var _ = template.New\n")
	buf.WriteString("var _ = assets.NewManager\n")
	fmt.Fprintf(&buf, "var (\n App *app.App\n)\n")
	buf.WriteString("func init() {\n")
	buf.WriteString("App = app.New()\n")
	fmt.Fprintf(&buf, "App.SetName(%q)\n", app.Name)
	buf.WriteString("var manager *assets.Manager\n")
	if app.Assets != "" {
		buf.WriteString("assetsLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Assets), release); err != nil {
			return err
		}
		buf.WriteString("const prefix = \"/assets/\"\n")
		buf.WriteString("manager = assets.NewManager(assetsLoader, prefix)\n")
		buf.WriteString("App.SetAssetsManager(manager)\n")
		buf.WriteString("assetsHandler := assets.Handler(manager)\n")
		buf.WriteString("App.Handle(\"^\"+prefix, func(ctx *app.Context) { assetsHandler(ctx, ctx.R) })\n")
	}
	scope := pkg.Scope()
	if len(app.Vars) > 0 {
		buf.WriteString("App.AddTemplateVars(map[string]interface{}{\n")
		for k, v := range app.Vars {
			ident := k
			name := v
			if name == "" {
				name = ident
			}
			obj := scope.Lookup(ident)
			if obj == nil {
				return fmt.Errorf("could not find identifier named %q", ident)
			}
			rhs := ident
			if va, ok := obj.(*types.Var); ok {
				tn := va.Type().String()
				if strings.Contains(tn, ".") {
					tn = "interface{}"
				}
				rhs = fmt.Sprintf("func() %s { return %s }", tn, ident)
			}
			fmt.Fprintf(&buf, "%q: %s,\n", name, rhs)
		}
		buf.WriteString("})\n")
	}
	for k, v := range app.Handlers {
		obj := scope.Lookup(k)
		if obj == nil {
			return fmt.Errorf("could not find handler named %q", k)
		}
		if _, err := regexp.Compile(v); err != nil {
			return fmt.Errorf("invalid pattern %q: %s", v, err)
		}
		switch obj.Type().String() {
		case "*gnd.la/app.HandlerInfo", "gnd.la/app.HandlerInfo":
			fmt.Fprintf(&buf, "App.HandleOptions(%q, %s.Handler, %s.Options)\n", v, obj.Name(), obj.Name())
		default:
			return fmt.Errorf("invalid handler type %s", obj.Type())
		}
	}
	if app.Templates != nil && app.Templates.Path != "" {
		buf.WriteString("templatesLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Templates.Path), release); err != nil {
			return err
		}
		buf.WriteString("App.SetTemplatesLoader(templatesLoader)\n")
		re := regexp.MustCompile("\\W")
		for k, v := range app.Templates.Hooks {
			var pos string
			switch strings.ToLower(v) {
			case "top":
				pos = "assets.Top"
			case "bottom":
				pos = "assets.Bottom"
			case "none":
				pos = "assets.None"
			default:
				return fmt.Errorf("invalid hook position %q", v)
			}
			suffix := re.ReplaceAllString(k, "_")
			name := fmt.Sprintf("tmpl_%s", suffix)
			fmt.Fprintf(&buf, "%s := template.New(templatesLoader, manager)\n", name)
			fmt.Fprintf(&buf, "if err := %s.Parse(%q); err != nil {\npanic(err)\n}\n", name, k)
			fmt.Fprintf(&buf, "App.AddHook(&template.Hook{Template: %s, Position: %s})\n", name, pos)
		}
	}
	buf.WriteString("}\n")
	out := filepath.Join(pkg.Dir(), "gondola_app.go")
	log.Debugf("Writing Gondola app to %s", out)
	return genutil.WriteAutogen(out, buf.Bytes())
}

func Parse(dir string) (*App, error) {
	appFile := filepath.Join(dir, appFilename)
	data, err := ioutil.ReadFile(appFile)
	if err != nil {
		return nil, fmt.Errorf("error reading %s: %s", appFilename, err)
	}
	var app *App
	if err := goyaml.Unmarshal(data, &app); err != nil {
		return nil, err
	}
	app.Dir = dir
	return app, nil
}
<commit_msg>Parse the translation context from app.yaml<commit_after>package app

import (
	"bytes"
	"code.google.com/p/go.tools/go/types"
	"fmt"
	"gnd.la/gen/genutil"
	"gnd.la/loaders"
	"gnd.la/log"
	"io/ioutil"
	"launchpad.net/goyaml"
	"path/filepath"
	"regexp"
	"strings"
)

const appFilename = "app.yaml"

type Templates struct {
	Path  string            `yaml:"path"`
	Hooks map[string]string `yaml:"hooks"`
}

type Translations struct {
	Context string `yaml:"context"`
}

type App struct {
	Dir          string
	Name         string            `yaml:"name"`
	Handlers     map[string]string `yaml:"handlers"`
	Vars         map[string]string `yaml:"vars"`
	Templates    *Templates        `yaml:"templates"`
	Translations *Translations     `yaml:"translations"`
	Assets       string            `yaml:"assets"`
}

func (app *App) writeLoader(buf *bytes.Buffer, dir string, release bool) error {
	if release {
		return loaders.Bake(buf, dir, nil, loaders.CompressTgz)
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		return err
	}
	fmt.Fprintf(buf, "loaders.MemLoader(loaders.FSLoader(%q))\n", abs)
	return nil
}

func (app *App) Gen(release bool) error {
	pkg, err := genutil.NewPackage(app.Dir)
	if err != nil {
		return err
	}
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "package %s\n\n", pkg.Name())
	buf.WriteString(genutil.AutogenString())
	buf.WriteString("import (\n\"gnd.la/app\"\n\"gnd.la/loaders\"\n\"gnd.la/template\"\n\"gnd.la/template/assets\"\n)\n")
	buf.WriteString("var _ = loaders.FSLoader\n")
	buf.WriteString("var _ = template.New\n")
	buf.WriteString("var _ = assets.NewManager\n")
	fmt.Fprintf(&buf, "var (\n App *app.App\n)\n")
	buf.WriteString("func init() {\n")
	buf.WriteString("App = app.New()\n")
	// TODO: Enable this when we have a solution for
	// executing templates from differnt apps from the
	// same *app.Context, which would need different
	// default translation contexts.
	//
	/*if app.Translations != nil && app.Translations.Context != "" {
		fmt.Fprintf(&buf, "App.TranslationContext = %q\n", app.Translations.Context)
	}*/
	fmt.Fprintf(&buf, "App.SetName(%q)\n", app.Name)
	buf.WriteString("var manager *assets.Manager\n")
	if app.Assets != "" {
		buf.WriteString("assetsLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Assets), release); err != nil {
			return err
		}
		buf.WriteString("const prefix = \"/assets/\"\n")
		buf.WriteString("manager = assets.NewManager(assetsLoader, prefix)\n")
		buf.WriteString("App.SetAssetsManager(manager)\n")
		buf.WriteString("assetsHandler := assets.Handler(manager)\n")
		buf.WriteString("App.Handle(\"^\"+prefix, func(ctx *app.Context) { assetsHandler(ctx, ctx.R) })\n")
	}
	scope := pkg.Scope()
	if len(app.Vars) > 0 {
		buf.WriteString("App.AddTemplateVars(map[string]interface{}{\n")
		for k, v := range app.Vars {
			ident := k
			name := v
			if name == "" {
				name = ident
			}
			obj := scope.Lookup(ident)
			if obj == nil {
				return fmt.Errorf("could not find identifier named %q", ident)
			}
			rhs := ident
			if va, ok := obj.(*types.Var); ok {
				tn := va.Type().String()
				if strings.Contains(tn, ".") {
					tn = "interface{}"
				}
				rhs = fmt.Sprintf("func() %s { return %s }", tn, ident)
			}
			fmt.Fprintf(&buf, "%q: %s,\n", name, rhs)
		}
		buf.WriteString("})\n")
	}
	for k, v := range app.Handlers {
		obj := scope.Lookup(k)
		if obj == nil {
			return fmt.Errorf("could not find handler named %q", k)
		}
		if _, err := regexp.Compile(v); err != nil {
			return fmt.Errorf("invalid pattern %q: %s", v, err)
		}
		switch obj.Type().String() {
		case "*gnd.la/app.HandlerInfo", "gnd.la/app.HandlerInfo":
			fmt.Fprintf(&buf, "App.HandleOptions(%q, %s.Handler, %s.Options)\n", v, obj.Name(), obj.Name())
		default:
			return fmt.Errorf("invalid handler type %s", obj.Type())
		}
	}
	if app.Templates != nil && app.Templates.Path != "" {
		buf.WriteString("templatesLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Templates.Path), release); err != nil {
			return err
		}
		buf.WriteString("App.SetTemplatesLoader(templatesLoader)\n")
		re := regexp.MustCompile("\\W")
		for k, v := range app.Templates.Hooks {
			var pos string
			switch strings.ToLower(v) {
			case "top":
				pos = "assets.Top"
			case "bottom":
				pos = "assets.Bottom"
			case "none":
				pos = "assets.None"
			default:
				return fmt.Errorf("invalid hook position %q", v)
			}
			suffix := re.ReplaceAllString(k, "_")
			name := fmt.Sprintf("tmpl_%s", suffix)
			fmt.Fprintf(&buf, "%s := template.New(templatesLoader, manager)\n", name)
			fmt.Fprintf(&buf, "if err := %s.Parse(%q); err != nil {\npanic(err)\n}\n", name, k)
			fmt.Fprintf(&buf, "App.AddHook(&template.Hook{Template: %s, Position: %s})\n", name, pos)
		}
	}
	buf.WriteString("}\n")
	out := filepath.Join(pkg.Dir(), "gondola_app.go")
	log.Debugf("Writing Gondola app to %s", out)
	return genutil.WriteAutogen(out, buf.Bytes())
}

func Parse(dir string) (*App, error) {
	appFile := filepath.Join(dir, appFilename)
	data, err := ioutil.ReadFile(appFile)
	if err != nil {
		return nil, fmt.Errorf("error reading %s: %s", appFilename, err)
	}
	var app *App
	if err := goyaml.Unmarshal(data, &app); err != nil {
		return nil, err
	}
	app.Dir = dir
	return app, nil
}
<|endoftext|>


<commit_before>package slackboard

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync/atomic"
)

type SlackPayload struct {
	Channel   string `json:"channel"`
	Username  string `json:"username,omitempty"`
	IconEmoji string `json:"icon_emoji,omitempty"`
	Text      string `json:"text"`
	Parse     string `json:"parse,omitempty"`
}

type SlackboardPayload struct {
	Tag  string `json:"tag"`
	Host string `json:"host,omitempty"`
	Text string `json:"text"`
}

func sendNotification2Slack(payload *SlackPayload) error {
	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	client := &http.Client{}

	resp, err := client.Post(
		ConfSlackboard.Core.SlackURL,
		"application/json",
		strings.NewReader(string(body)))
	if err != nil {
		return err
	}

	defer resp.Body.Close()

	return nil
}

func NotifyHandler(w http.ResponseWriter, r *http.Request) {
	LogError.Debug("notify-request is Accepted")

	LogError.Debug("parse request body")
	var req SlackboardPayload
	reqBody, _ := ioutil.ReadAll(r.Body)
	err := json.Unmarshal(reqBody, &req)
	if err != nil {
		LogAcceptedRequest("/notify", r.Method, r.Proto, r.ContentLength, "")
		sendResponse(w, "Request-body is malformed", http.StatusBadRequest)
		return
	}

	LogAcceptedRequest("/notify", r.Method, r.Proto, r.ContentLength, req.Tag)

	LogError.Debug("method check")
	if r.Method != "POST" {
		sendResponse(w, "invalid method", http.StatusBadRequest)
		return
	}

	LogError.Debug("find tag")
	sent := false
	for i, tag := range ConfSlackboard.Tags {
		if tag.Tag == req.Tag {
			atomic.AddUint64(&Topics[i].Count, 1)
			payload := &SlackPayload{
				Channel:   tag.Channel,
				Username:  tag.Username,
				IconEmoji: tag.IconEmoji,
				Text:      req.Text,
				Parse:     tag.Parse,
			}
			err := sendNotification2Slack(payload)
			if err != nil {
				sendResponse(w, "failed to post message to slack", http.StatusBadGateway)
				return
			}
			sent = true
		}

	}

	LogError.Debug("response to client")
	if sent {
		sendResponse(w, "ok", http.StatusOK)
	} else {
		msg := fmt.Sprintf("tag:%s is not found", req.Tag)
		sendResponse(w, msg, http.StatusBadRequest)
	}

}
<commit_msg>slackboard(-cli)?: asynchronous notification implementation. (refs #2)<commit_after>package slackboard

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync/atomic"
	"errors"
)

type SlackPayload struct {
	Channel   string `json:"channel"`
	Username  string `json:"username,omitempty"`
	IconEmoji string `json:"icon_emoji,omitempty"`
	Text      string `json:"text"`
	Parse     string `json:"parse,omitempty"`
}

type SlackboardPayload struct {
	Tag  string `json:"tag"`
	Host string `json:"host,omitempty"`
	Text string `json:"text"`
	Sync bool   `json:"sync,omitempty"`
}

func sendNotification2Slack(payload *SlackPayload) error {
	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	client := &http.Client{}

	resp, err := client.Post(
		ConfSlackboard.Core.SlackURL,
		"application/json",
		strings.NewReader(string(body)))

	if resp.Status != "200" {
		return errors.New(fmt.Sprintf("Slack is not available:%s", resp.Status))
	}

	if err != nil {
		return err
	}

	defer resp.Body.Close()

	return nil
}

func NotifyHandler(w http.ResponseWriter, r *http.Request) {
	LogError.Debug("notify-request is Accepted")

	LogError.Debug("parse request body")
	var req SlackboardPayload
	reqBody, _ := ioutil.ReadAll(r.Body)
	err := json.Unmarshal(reqBody, &req)
	if err != nil {
		LogAcceptedRequest("/notify", r.Method, r.Proto, r.ContentLength, "")
		sendResponse(w, "Request-body is malformed", http.StatusBadRequest)
		return
	}

	LogAcceptedRequest("/notify", r.Method, r.Proto, r.ContentLength, req.Tag)

	LogError.Debug("method check")
	if r.Method != "POST" {
		sendResponse(w, "invalid method", http.StatusBadRequest)
		return
	}

	LogError.Debug("find tag")
	sent := false
	for i, tag := range ConfSlackboard.Tags {
		if tag.Tag == req.Tag {
			atomic.AddUint64(&Topics[i].Count, 1)
			payload := &SlackPayload{
				Channel:   tag.Channel,
				Username:  tag.Username,
				IconEmoji: tag.IconEmoji,
				Text:      req.Text,
				Parse:     tag.Parse,
			}

			if req.Sync {
				err := sendNotification2Slack(payload)
				if err != nil {
					sendResponse(w, "failed to post message to slack", http.StatusBadGateway)
					return
				}
				sent = true
			} else {
				go func() {
					err := sendNotification2Slack(payload)
					if err != nil {
						LogError.Error(fmt.Sprintf("failed to post message to slack:%s", err.Error()))
					}
				}()
			}
		}

	}

	LogError.Debug("response to client")

	if req.Sync {
		if sent {
			sendResponse(w, "ok", http.StatusOK)
		} else {
			msg := fmt.Sprintf("tag:%s is not found", req.Tag)
			sendResponse(w, msg, http.StatusBadRequest)
		}

	} else {
		sendResponse(w, "ok", http.StatusOK)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
	"time"

	"github.com/tardisx/discord-auto-upload/config"
	"github.com/tardisx/discord-auto-upload/upload"
)

func TestWatchNewFiles(t *testing.T) {
	dir := createFileTree()
	defer os.RemoveAll(dir)
	time.Sleep(time.Second)

	w := watch{
		config:       config.Watcher{Path: dir},
		uploader:     upload.Uploader{},
		lastCheck:    time.Now(),
		newLastCheck: time.Now(),
	}
	files := w.ProcessNewFiles()
	if len(files) != 0 {
		t.Errorf("was not zero files (%d): %v", len(files), files)
	}
	// create a new file
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b.gif"))
	files = w.ProcessNewFiles()
	if len(files) != 1 {
		t.Errorf("was not one file - got: %v", files)
	}
	if files[0] != fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b.gif") {
		t.Error("wrong file")
	}
}

func TestExclsion(t *testing.T) {
	dir := createFileTree()
	defer os.RemoveAll(dir)
	time.Sleep(time.Second)

	w := watch{
		config:       config.Watcher{Path: dir, Exclude: []string{"thumb", "tiny"}},
		uploader:     upload.Uploader{},
		lastCheck:    time.Now(),
		newLastCheck: time.Now(),
	}
	files := w.ProcessNewFiles()
	if len(files) != 0 {
		t.Errorf("was not zero files (%d): %v", len(files), files)
	}
	// create a new file that would not hit exclusion, and two that would
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b.gif"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b_thumb.gif"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "tiny_b.jpg"))
	files = w.ProcessNewFiles()
	if len(files) != 1 {
		t.Error("was not one new file")
	}

}

func TestCheckPath(t *testing.T) {
	dir := createFileTree()
	defer os.RemoveAll(dir)

	w := watch{
		config:       config.Watcher{Path: dir},
		uploader:     upload.Uploader{},
		lastCheck:    time.Now(),
		newLastCheck: time.Now(),
	}
	if !w.checkPath() {
		t.Error("checkPath failed?")
	}
	os.RemoveAll(dir)
	if w.checkPath() {
		t.Error("checkPath succeeded when shouldn't?")
	}
}

func createFileTree() string {
	dir, err := ioutil.TempDir("", "dau-test")
	if err != nil {
		log.Fatal(err)
	}
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "a.gif"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "a.jpg"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "a.png"))

	return dir
}
<commit_msg>Just a stab in the dark<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
	"time"

	"github.com/tardisx/discord-auto-upload/config"
	"github.com/tardisx/discord-auto-upload/upload"
)

func TestWatchNewFiles(t *testing.T) {
	dir := createFileTree()
	defer os.RemoveAll(dir)
	time.Sleep(time.Second)

	w := watch{
		config:       config.Watcher{Path: dir},
		uploader:     upload.Uploader{},
		lastCheck:    time.Now(),
		newLastCheck: time.Now(),
	}
	files := w.ProcessNewFiles()
	if len(files) != 0 {
		t.Errorf("was not zero files (%d): %v", len(files), files)
	}

	// create a new file
	time.Sleep(time.Second)
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b.gif"))
	files = w.ProcessNewFiles()
	if len(files) != 1 {
		t.Errorf("was not one file - got: %v", files)
	}
	if files[0] != fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b.gif") {
		t.Error("wrong file")
	}
}

func TestExclsion(t *testing.T) {
	dir := createFileTree()
	defer os.RemoveAll(dir)
	time.Sleep(time.Second)

	w := watch{
		config:       config.Watcher{Path: dir, Exclude: []string{"thumb", "tiny"}},
		uploader:     upload.Uploader{},
		lastCheck:    time.Now(),
		newLastCheck: time.Now(),
	}
	files := w.ProcessNewFiles()
	if len(files) != 0 {
		t.Errorf("was not zero files (%d): %v", len(files), files)
	}
	// create a new file that would not hit exclusion, and two that would
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b.gif"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "b_thumb.gif"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "tiny_b.jpg"))
	files = w.ProcessNewFiles()
	if len(files) != 1 {
		t.Error("was not one new file")
	}

}

func TestCheckPath(t *testing.T) {
	dir := createFileTree()
	defer os.RemoveAll(dir)

	w := watch{
		config:       config.Watcher{Path: dir},
		uploader:     upload.Uploader{},
		lastCheck:    time.Now(),
		newLastCheck: time.Now(),
	}
	if !w.checkPath() {
		t.Error("checkPath failed?")
	}
	os.RemoveAll(dir)
	if w.checkPath() {
		t.Error("checkPath succeeded when shouldn't?")
	}
}

func createFileTree() string {
	dir, err := ioutil.TempDir("", "dau-test")
	if err != nil {
		log.Fatal(err)
	}
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "a.gif"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "a.jpg"))
	os.Create(fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "a.png"))

	return dir
}
<|endoftext|>


<commit_before>package cmd

import (
	"encoding/json"
	"errors"

	log "github.com/Sirupsen/logrus"
	"github.com/minchao/smsender/smsender"
	"github.com/minchao/smsender/smsender/model"
	"github.com/minchao/smsender/smsender/utils"
	"github.com/spf13/cobra"
	config "github.com/spf13/viper"
	"gopkg.in/go-playground/validator.v9"
)

var sendCmd = &cobra.Command{
	Use:   "send",
	Short: "Send message",
	Example: `  send --to +12345678900 --body "Hello, 世界"
  send --to +12345678900 --from smsender --body "Hello, 世界" --provider dummy`,
	RunE: sendCmdF,
}

func init() {
	sendCmd.Flags().StringP("to", "t", "", "The destination phone number (E.164 format)")
	sendCmd.Flags().StringP("from", "f", "", "Sender Id (phone number or alphanumeric)")
	sendCmd.Flags().StringP("body", "b", "", "The text of the message")
	sendCmd.Flags().StringP("provider", "p", "", "Provider name")
}

func sendCmdF(cmd *cobra.Command, args []string) error {
	if err := initEnv(cmd); err != nil {
		return err
	}

	to, err := cmd.Flags().GetString("to")
	if err != nil || to == "" {
		return errors.New("The to is required")
	}
	validate := validator.New()
	validate.RegisterValidation("phone", utils.IsPhoneNumber)
	if err := validate.Var(to, "phone"); err != nil {
		return errors.New("Invalid phone number")
	}
	from, _ := cmd.Flags().GetString("from")
	body, err := cmd.Flags().GetString("body")
	if err != nil || body == "" {
		return errors.New("The body is required")
	}
	provider, _ := cmd.Flags().GetString("provider")

	config.Set("worker.num", 1)

	sender := smsender.NewSender()
	sender.InitWorkers()

	job := model.NewMessageJob(to, from, body, false)
	if provider != "" {
		job.Provider = &provider
	}

	queue := sender.GetMessagesChannel()
	queue <- job

	result := <-job.Result
	resultJson, _ := json.Marshal(result)

	log.Infof("Result: %s", resultJson)

	return nil
}
<commit_msg>Formatting JSON in the console output<commit_after>package cmd

import (
	"encoding/json"
	"errors"

	log "github.com/Sirupsen/logrus"
	"github.com/minchao/smsender/smsender"
	"github.com/minchao/smsender/smsender/model"
	"github.com/minchao/smsender/smsender/utils"
	"github.com/spf13/cobra"
	config "github.com/spf13/viper"
	"gopkg.in/go-playground/validator.v9"
)

var sendCmd = &cobra.Command{
	Use:   "send",
	Short: "Send message",
	Example: `  send --to +12345678900 --body "Hello, 世界"
  send --to +12345678900 --from smsender --body "Hello, 世界" --provider dummy`,
	RunE: sendCmdF,
}

func init() {
	sendCmd.Flags().StringP("to", "t", "", "The destination phone number (E.164 format)")
	sendCmd.Flags().StringP("from", "f", "", "Sender Id (phone number or alphanumeric)")
	sendCmd.Flags().StringP("body", "b", "", "The text of the message")
	sendCmd.Flags().StringP("provider", "p", "", "Provider name")
}

func sendCmdF(cmd *cobra.Command, args []string) error {
	if err := initEnv(cmd); err != nil {
		return err
	}

	to, err := cmd.Flags().GetString("to")
	if err != nil || to == "" {
		return errors.New("The to is required")
	}
	validate := validator.New()
	validate.RegisterValidation("phone", utils.IsPhoneNumber)
	if err := validate.Var(to, "phone"); err != nil {
		return errors.New("Invalid phone number")
	}
	from, _ := cmd.Flags().GetString("from")
	body, err := cmd.Flags().GetString("body")
	if err != nil || body == "" {
		return errors.New("The body is required")
	}
	provider, _ := cmd.Flags().GetString("provider")

	config.Set("worker.num", 1)

	sender := smsender.NewSender()
	sender.InitWorkers()

	job := model.NewMessageJob(to, from, body, false)
	if provider != "" {
		job.Provider = &provider
	}

	queue := sender.GetMessagesChannel()
	queue <- job

	result := <-job.Result
	resultJson, _ := json.MarshalIndent(result, "", "  ")

	log.Infof("Result:\n%s", resultJson)

	return nil
}
<|endoftext|>


<commit_before>package event

import (
	"encoding/json"
	"errors"
	"strings"
	"sync"

	"github.com/Dataman-Cloud/swan/src/types"
	"github.com/Sirupsen/logrus"

	"github.com/Dataman-Cloud/swan-janitor/src/upstream"
)

type JanitorSubscriber struct {
	Key          string
	acceptors    map[string]types.JanitorAcceptor
	acceptorLock sync.RWMutex
}

func NewJanitorSubscriber() *JanitorSubscriber {
	janitorSubscriber := &JanitorSubscriber{
		Key:       "janitor",
		acceptors: make(map[string]types.JanitorAcceptor),
	}
	return janitorSubscriber
}

func (js *JanitorSubscriber) Subscribe(bus *EventBus) error {
	bus.Lock.Lock()
	defer bus.Lock.Unlock()

	bus.Subscribers[js.Key] = js
	return nil
}

func (js *JanitorSubscriber) Unsubscribe(bus *EventBus) error {
	bus.Lock.Lock()
	defer bus.Lock.Unlock()

	delete(bus.Subscribers, js.Key)
	return nil
}

func (js *JanitorSubscriber) AddAcceptor(acceptor types.JanitorAcceptor) {
	js.acceptorLock.Lock()
	js.acceptors[acceptor.ID] = acceptor
	js.acceptorLock.Unlock()
}

func (js *JanitorSubscriber) Write(e *Event) error {
	payload, ok := e.Payload.(*TaskInfoEvent)
	if !ok {
		return errors.New("payload type error")
	}

	rgevent := &upstream.TargetChangeEvent{}
	if e.Type == EventTypeTaskHealthy {
		rgevent.Change = "add"
	} else {
		rgevent.Change = "del"
	}

	rgevent.TargetIP = payload.Ip
	rgevent.TargetPort = payload.Port
	rgevent.TargetName = strings.ToLower(strings.Replace(payload.TaskId, "-", ".", -1))

	go js.pushJanitorEvent(rgevent)

	return nil
}

func (js *JanitorSubscriber) InterestIn(e *Event) bool {
	if e.AppMode != "replicates" {
		return false
	}

	if e.Type == EventTypeTaskHealthy {
		return true
	}

	if e.Type == EventTypeTaskUnhealthy {
		return true
	}

	return false
}

func (js *JanitorSubscriber) pushJanitorEvent(event *upstream.TargetChangeEvent) {
	data, err := json.Marshal(event)
	if err != nil {
		logrus.Infof("marshal janitor event got error: %s", err.Error())
		return
	}

	js.acceptorLock.RLock()
	for _, acceptor := range js.acceptors {
		logrus.Infof("write to xefwefe ||||||||||||||||||||||||||||| %s", acceptor.RemoteAddr)

		if err := sendEventByHttp(acceptor.RemoteAddr, "POST", data); err != nil {
			logrus.Infof("send janitor event by http to %s got error: %s", acceptor.RemoteAddr, err.Error())
		}
	}
	js.acceptorLock.RUnlock()
}
<commit_msg>remove unused log<commit_after>package event

import (
	"encoding/json"
	"errors"
	"strings"
	"sync"

	"github.com/Dataman-Cloud/swan/src/types"
	"github.com/Sirupsen/logrus"

	"github.com/Dataman-Cloud/swan-janitor/src/upstream"
)

type JanitorSubscriber struct {
	Key          string
	acceptors    map[string]types.JanitorAcceptor
	acceptorLock sync.RWMutex
}

func NewJanitorSubscriber() *JanitorSubscriber {
	janitorSubscriber := &JanitorSubscriber{
		Key:       "janitor",
		acceptors: make(map[string]types.JanitorAcceptor),
	}
	return janitorSubscriber
}

func (js *JanitorSubscriber) Subscribe(bus *EventBus) error {
	bus.Lock.Lock()
	defer bus.Lock.Unlock()

	bus.Subscribers[js.Key] = js
	return nil
}

func (js *JanitorSubscriber) Unsubscribe(bus *EventBus) error {
	bus.Lock.Lock()
	defer bus.Lock.Unlock()

	delete(bus.Subscribers, js.Key)
	return nil
}

func (js *JanitorSubscriber) AddAcceptor(acceptor types.JanitorAcceptor) {
	js.acceptorLock.Lock()
	js.acceptors[acceptor.ID] = acceptor
	js.acceptorLock.Unlock()
}

func (js *JanitorSubscriber) Write(e *Event) error {
	payload, ok := e.Payload.(*TaskInfoEvent)
	if !ok {
		return errors.New("payload type error")
	}

	rgevent := &upstream.TargetChangeEvent{}
	if e.Type == EventTypeTaskHealthy {
		rgevent.Change = "add"
	} else {
		rgevent.Change = "del"
	}

	rgevent.TargetIP = payload.Ip
	rgevent.TargetPort = payload.Port
	rgevent.TargetName = strings.ToLower(strings.Replace(payload.TaskId, "-", ".", -1))

	go js.pushJanitorEvent(rgevent)

	return nil
}

func (js *JanitorSubscriber) InterestIn(e *Event) bool {
	if e.AppMode != "replicates" {
		return false
	}

	if e.Type == EventTypeTaskHealthy {
		return true
	}

	if e.Type == EventTypeTaskUnhealthy {
		return true
	}

	return false
}

func (js *JanitorSubscriber) pushJanitorEvent(event *upstream.TargetChangeEvent) {
	data, err := json.Marshal(event)
	if err != nil {
		logrus.Infof("marshal janitor event got error: %s", err.Error())
		return
	}

	js.acceptorLock.RLock()
	for _, acceptor := range js.acceptors {
		if err := sendEventByHttp(acceptor.RemoteAddr, "POST", data); err != nil {
			logrus.Infof("send janitor event by http to %s got error: %s", acceptor.RemoteAddr, err.Error())
		}
	}
	js.acceptorLock.RUnlock()
}
<|endoftext|>