<commit_before>/*
Copyright 2014 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cache

import (
	"errors"
	"sync"

	"k8s.io/apimachinery/pkg/util/sets"
)

// PopProcessFunc is passed to Pop() method of Queue interface.
// It is supposed to process the accumulator popped from the queue.
type PopProcessFunc func(interface{}) error

// ErrRequeue may be returned by a PopProcessFunc to safely requeue
// the current item. The value of Err will be returned from Pop.
type ErrRequeue struct {
	// Err is returned by the Pop function
	Err error
}

// ErrFIFOClosed used when FIFO is closed
var ErrFIFOClosed = errors.New("DeltaFIFO: manipulating with closed queue")

func (e ErrRequeue) Error() string {
	if e.Err == nil {
		return "the popped item should be requeued without returning an error"
	}
	return e.Err.Error()
}

// Queue extends Store with a collection of Store keys to "process".
// Every Add, Update, or Delete may put the object's key in that collection.
// A Queue has a way to derive the corresponding key given an accumulator.
// A Queue can be accessed concurrently from multiple goroutines.
// A Queue can be "closed", after which Pop operations return an error.
type Queue interface {
	Store

	// Pop blocks until there is at least one key to process or the
	// Queue is closed.  In the latter case Pop returns with an error.
	// In the former case Pop atomically picks one key to process,
	// removes that (key, accumulator) association from the Store, and
	// processes the accumulator.  Pop returns the accumulator that
	// was processed and the result of processing.  The PopProcessFunc
	// may return an ErrRequeue{inner} and in this case Pop will (a)
	// return that (key, accumulator) association to the Queue as part
	// of the atomic processing and (b) return the inner error from
	// Pop.
	Pop(PopProcessFunc) (interface{}, error)

	// AddIfNotPresent puts the given accumulator into the Queue (in
	// association with the accumulator's key) if and only if that key
	// is not already associated with a non-empty accumulator.
	AddIfNotPresent(interface{}) error

	// HasSynced returns true if the first batch of keys have all been
	// popped.  The first batch of keys are those of the first Replace
	// operation if that happened before any Add, Update, or Delete;
	// otherwise the first batch is empty.
	HasSynced() bool

	// Close the queue
	Close()
}

// Pop is helper function for popping from Queue.
// WARNING: Do NOT use this function in non-test code to avoid races
// unless you really really really really know what you are doing.
func Pop(queue Queue) interface{} {
	var result interface{}
	queue.Pop(func(obj interface{}) error {
		result = obj
		return nil
	})
	return result
}

// FIFO is a Queue in which (a) each accumulator is simply the most
// recently provided object and (b) the collection of keys to process
// is a FIFO.  The accumulators all start out empty, and deleting an
// object from its accumulator empties the accumulator.  The Resync
// operation is a no-op.
//
// Thus: if multiple adds/updates of a single object happen while that
// object's key is in the queue before it has been processed then it
// will only be processed once, and when it is processed the most
// recent version will be processed. This can't be done with a channel
//
// FIFO solves this use case:
//  * You want to process every object (exactly) once.
//  * You want to process the most recent version of the object when you process it.
//  * You do not want to process deleted objects, they should be removed from the queue.
//  * You do not want to periodically reprocess objects.
// Compare with DeltaFIFO for other use cases.
type FIFO struct {
	lock sync.RWMutex
	cond sync.Cond
	// We depend on the property that every key in `items` is also in `queue`
	items map[string]interface{}
	queue []string

	// populated is true if the first batch of items inserted by Replace() has been populated
	// or Delete/Add/Update was called first.
	populated bool
	// initialPopulationCount is the number of items inserted by the first call of Replace()
	initialPopulationCount int

	// keyFunc is used to make the key used for queued item insertion and retrieval, and
	// should be deterministic.
	keyFunc KeyFunc

	// Indication the queue is closed.
	// Used to indicate a queue is closed so a control loop can exit when a queue is empty.
	// Currently, not used to gate any of CRED operations.
	closed     bool
	closedLock sync.Mutex
}

var (
	_ = Queue(&FIFO{}) // FIFO is a Queue
)

// Close the queue.
func (f *FIFO) Close() {
	f.closedLock.Lock()
	defer f.closedLock.Unlock()
	f.closed = true
	f.cond.Broadcast()
}

// HasSynced returns true if an Add/Update/Delete/AddIfNotPresent are called first,
// or an Update called first but the first batch of items inserted by Replace() has been popped
func (f *FIFO) HasSynced() bool {
	f.lock.Lock()
	defer f.lock.Unlock()
	return f.populated && f.initialPopulationCount == 0
}

// Add inserts an item, and puts it in the queue. The item is only enqueued
// if it doesn't already exist in the set.
func (f *FIFO) Add(obj interface{}) error {
	id, err := f.keyFunc(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.populated = true
	if _, exists := f.items[id]; !exists {
		f.queue = append(f.queue, id)
	}
	f.items[id] = obj
	f.cond.Broadcast()
	return nil
}

// AddIfNotPresent inserts an item, and puts it in the queue. If the item is already
// present in the set, it is neither enqueued nor added to the set.
//
// This is useful in a single producer/consumer scenario so that the consumer can
// safely retry items without contending with the producer and potentially enqueueing
// stale items.
func (f *FIFO) AddIfNotPresent(obj interface{}) error {
	id, err := f.keyFunc(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.addIfNotPresent(id, obj)
	return nil
}

// addIfNotPresent assumes the fifo lock is already held and adds the provided
// item to the queue under id if it does not already exist.
func (f *FIFO) addIfNotPresent(id string, obj interface{}) {
	f.populated = true
	if _, exists := f.items[id]; exists {
		return
	}

	f.queue = append(f.queue, id)
	f.items[id] = obj
	f.cond.Broadcast()
}

// Update is the same as Add in this implementation.
func (f *FIFO) Update(obj interface{}) error {
	return f.Add(obj)
}

// Delete removes an item. It doesn't add it to the queue, because
// this implementation assumes the consumer only cares about the objects,
// not the order in which they were created/added.
func (f *FIFO) Delete(obj interface{}) error {
	id, err := f.keyFunc(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.populated = true
	delete(f.items, id)
	return err
}

// List returns a list of all the items.
func (f *FIFO) List() []interface{} {
	f.lock.RLock()
	defer f.lock.RUnlock()
	list := make([]interface{}, 0, len(f.items))
	for _, item := range f.items {
		list = append(list, item)
	}
	return list
}

// ListKeys returns a list of all the keys of the objects currently
// in the FIFO.
func (f *FIFO) ListKeys() []string {
	f.lock.RLock()
	defer f.lock.RUnlock()
	list := make([]string, 0, len(f.items))
	for key := range f.items {
		list = append(list, key)
	}
	return list
}

// Get returns the requested item, or sets exists=false.
func (f *FIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
	key, err := f.keyFunc(obj)
	if err != nil {
		return nil, false, KeyError{obj, err}
	}
	return f.GetByKey(key)
}

// GetByKey returns the requested item, or sets exists=false.
func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
	f.lock.RLock()
	defer f.lock.RUnlock()
	item, exists = f.items[key]
	return item, exists, nil
}

// IsClosed checks if the queue is closed
func (f *FIFO) IsClosed() bool {
	f.closedLock.Lock()
	defer f.closedLock.Unlock()
	if f.closed {
		return true
	}
	return false
}

// Pop waits until an item is ready and processes it. If multiple items are
// ready, they are returned in the order in which they were added/updated.
// The item is removed from the queue (and the store) before it is processed,
// so if you don't successfully process it, it should be added back with
// AddIfNotPresent(). process function is called under lock, so it is safe
// update data structures in it that need to be in sync with the queue.
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) {
	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
			// When Close() is called, the f.closed is set and the condition is broadcasted.
			// Which causes this loop to continue and return from the Pop().
			if f.IsClosed() {
				return nil, ErrFIFOClosed
			}

			f.cond.Wait()
		}
		id := f.queue[0]
		f.queue = f.queue[1:]
		if f.initialPopulationCount > 0 {
			f.initialPopulationCount--
		}
		item, ok := f.items[id]
		if !ok {
			// Item may have been deleted subsequently.
			continue
		}
		delete(f.items, id)
		err := process(item)
		if e, ok := err.(ErrRequeue); ok {
			f.addIfNotPresent(id, item)
			err = e.Err
		}
		return item, err
	}
}

// Replace will delete the contents of 'f', using instead the given map.
// 'f' takes ownership of the map, you should not reference the map again
// after calling this function. f's queue is reset, too; upon return, it
// will contain the items in the map, in no particular order.
func (f *FIFO) Replace(list []interface{}, resourceVersion string) error {
	items := make(map[string]interface{}, len(list))
	for _, item := range list {
		key, err := f.keyFunc(item)
		if err != nil {
			return KeyError{item, err}
		}
		items[key] = item
	}

	f.lock.Lock()
	defer f.lock.Unlock()

	if !f.populated {
		f.populated = true
		f.initialPopulationCount = len(items)
	}

	f.items = items
	f.queue = f.queue[:0]
	for id := range items {
		f.queue = append(f.queue, id)
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
	return nil
}

// Resync will ensure that every object in the Store has its key in the queue.
// This should be a no-op, because that property is maintained by all operations.
func (f *FIFO) Resync() error {
	f.lock.Lock()
	defer f.lock.Unlock()

	inQueue := sets.NewString()
	for _, id := range f.queue {
		inQueue.Insert(id)
	}
	for id := range f.items {
		if !inQueue.Has(id) {
			f.queue = append(f.queue, id)
		}
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
	return nil
}

// NewFIFO returns a Store which can be used to queue up items to
// process.
func NewFIFO(keyFunc KeyFunc) *FIFO {
	f := &FIFO{
		items:   map[string]interface{}{},
		queue:   []string{},
		keyFunc: keyFunc,
	}
	f.cond.L = &f.lock
	return f
}
<commit_msg>Polished up HasSync comment<commit_after>/*
Copyright 2014 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cache

import (
	"errors"
	"sync"

	"k8s.io/apimachinery/pkg/util/sets"
)

// PopProcessFunc is passed to Pop() method of Queue interface.
// It is supposed to process the accumulator popped from the queue.
type PopProcessFunc func(interface{}) error

// ErrRequeue may be returned by a PopProcessFunc to safely requeue
// the current item. The value of Err will be returned from Pop.
type ErrRequeue struct {
	// Err is returned by the Pop function
	Err error
}

// ErrFIFOClosed used when FIFO is closed
var ErrFIFOClosed = errors.New("DeltaFIFO: manipulating with closed queue")

func (e ErrRequeue) Error() string {
	if e.Err == nil {
		return "the popped item should be requeued without returning an error"
	}
	return e.Err.Error()
}

// Queue extends Store with a collection of Store keys to "process".
// Every Add, Update, or Delete may put the object's key in that collection.
// A Queue has a way to derive the corresponding key given an accumulator.
// A Queue can be accessed concurrently from multiple goroutines.
// A Queue can be "closed", after which Pop operations return an error.
type Queue interface {
	Store

	// Pop blocks until there is at least one key to process or the
	// Queue is closed.  In the latter case Pop returns with an error.
	// In the former case Pop atomically picks one key to process,
	// removes that (key, accumulator) association from the Store, and
	// processes the accumulator.  Pop returns the accumulator that
	// was processed and the result of processing.  The PopProcessFunc
	// may return an ErrRequeue{inner} and in this case Pop will (a)
	// return that (key, accumulator) association to the Queue as part
	// of the atomic processing and (b) return the inner error from
	// Pop.
	Pop(PopProcessFunc) (interface{}, error)

	// AddIfNotPresent puts the given accumulator into the Queue (in
	// association with the accumulator's key) if and only if that key
	// is not already associated with a non-empty accumulator.
	AddIfNotPresent(interface{}) error

	// HasSynced returns true if the first batch of keys have all been
	// popped.  The first batch of keys are those of the first Replace
	// operation if that happened before any Add, AddIfNotPresent,
	// Update, or Delete; otherwise the first batch is empty.
	HasSynced() bool

	// Close the queue
	Close()
}

// Pop is helper function for popping from Queue.
// WARNING: Do NOT use this function in non-test code to avoid races
// unless you really really really really know what you are doing.
func Pop(queue Queue) interface{} {
	var result interface{}
	queue.Pop(func(obj interface{}) error {
		result = obj
		return nil
	})
	return result
}

// FIFO is a Queue in which (a) each accumulator is simply the most
// recently provided object and (b) the collection of keys to process
// is a FIFO.  The accumulators all start out empty, and deleting an
// object from its accumulator empties the accumulator.  The Resync
// operation is a no-op.
//
// Thus: if multiple adds/updates of a single object happen while that
// object's key is in the queue before it has been processed then it
// will only be processed once, and when it is processed the most
// recent version will be processed. This can't be done with a channel
//
// FIFO solves this use case:
//  * You want to process every object (exactly) once.
//  * You want to process the most recent version of the object when you process it.
//  * You do not want to process deleted objects, they should be removed from the queue.
//  * You do not want to periodically reprocess objects.
// Compare with DeltaFIFO for other use cases.
type FIFO struct {
	lock sync.RWMutex
	cond sync.Cond
	// We depend on the property that every key in `items` is also in `queue`
	items map[string]interface{}
	queue []string

	// populated is true if the first batch of items inserted by Replace() has been populated
	// or Delete/Add/Update was called first.
	populated bool
	// initialPopulationCount is the number of items inserted by the first call of Replace()
	initialPopulationCount int

	// keyFunc is used to make the key used for queued item insertion and retrieval, and
	// should be deterministic.
	keyFunc KeyFunc

	// Indication the queue is closed.
	// Used to indicate a queue is closed so a control loop can exit when a queue is empty.
	// Currently, not used to gate any of CRED operations.
	closed     bool
	closedLock sync.Mutex
}

var (
	_ = Queue(&FIFO{}) // FIFO is a Queue
)

// Close the queue.
func (f *FIFO) Close() {
	f.closedLock.Lock()
	defer f.closedLock.Unlock()
	f.closed = true
	f.cond.Broadcast()
}

// HasSynced returns true if an Add/Update/Delete/AddIfNotPresent are called first,
// or the first batch of items inserted by Replace() has been popped.
func (f *FIFO) HasSynced() bool {
	f.lock.Lock()
	defer f.lock.Unlock()
	return f.populated && f.initialPopulationCount == 0
}

// Add inserts an item, and puts it in the queue. The item is only enqueued
// if it doesn't already exist in the set.
func (f *FIFO) Add(obj interface{}) error {
	id, err := f.keyFunc(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.populated = true
	if _, exists := f.items[id]; !exists {
		f.queue = append(f.queue, id)
	}
	f.items[id] = obj
	f.cond.Broadcast()
	return nil
}

// AddIfNotPresent inserts an item, and puts it in the queue. If the item is already
// present in the set, it is neither enqueued nor added to the set.
//
// This is useful in a single producer/consumer scenario so that the consumer can
// safely retry items without contending with the producer and potentially enqueueing
// stale items.
func (f *FIFO) AddIfNotPresent(obj interface{}) error {
	id, err := f.keyFunc(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.addIfNotPresent(id, obj)
	return nil
}

// addIfNotPresent assumes the fifo lock is already held and adds the provided
// item to the queue under id if it does not already exist.
func (f *FIFO) addIfNotPresent(id string, obj interface{}) {
	f.populated = true
	if _, exists := f.items[id]; exists {
		return
	}

	f.queue = append(f.queue, id)
	f.items[id] = obj
	f.cond.Broadcast()
}

// Update is the same as Add in this implementation.
func (f *FIFO) Update(obj interface{}) error {
	return f.Add(obj)
}

// Delete removes an item. It doesn't add it to the queue, because
// this implementation assumes the consumer only cares about the objects,
// not the order in which they were created/added.
func (f *FIFO) Delete(obj interface{}) error {
	id, err := f.keyFunc(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.populated = true
	delete(f.items, id)
	return err
}

// List returns a list of all the items.
func (f *FIFO) List() []interface{} {
	f.lock.RLock()
	defer f.lock.RUnlock()
	list := make([]interface{}, 0, len(f.items))
	for _, item := range f.items {
		list = append(list, item)
	}
	return list
}

// ListKeys returns a list of all the keys of the objects currently
// in the FIFO.
func (f *FIFO) ListKeys() []string {
	f.lock.RLock()
	defer f.lock.RUnlock()
	list := make([]string, 0, len(f.items))
	for key := range f.items {
		list = append(list, key)
	}
	return list
}

// Get returns the requested item, or sets exists=false.
func (f *FIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
	key, err := f.keyFunc(obj)
	if err != nil {
		return nil, false, KeyError{obj, err}
	}
	return f.GetByKey(key)
}

// GetByKey returns the requested item, or sets exists=false.
func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
	f.lock.RLock()
	defer f.lock.RUnlock()
	item, exists = f.items[key]
	return item, exists, nil
}

// IsClosed checks if the queue is closed
func (f *FIFO) IsClosed() bool {
	f.closedLock.Lock()
	defer f.closedLock.Unlock()
	if f.closed {
		return true
	}
	return false
}

// Pop waits until an item is ready and processes it. If multiple items are
// ready, they are returned in the order in which they were added/updated.
// The item is removed from the queue (and the store) before it is processed,
// so if you don't successfully process it, it should be added back with
// AddIfNotPresent(). process function is called under lock, so it is safe
// update data structures in it that need to be in sync with the queue.
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) {
	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
			// When Close() is called, the f.closed is set and the condition is broadcasted.
			// Which causes this loop to continue and return from the Pop().
			if f.IsClosed() {
				return nil, ErrFIFOClosed
			}

			f.cond.Wait()
		}
		id := f.queue[0]
		f.queue = f.queue[1:]
		if f.initialPopulationCount > 0 {
			f.initialPopulationCount--
		}
		item, ok := f.items[id]
		if !ok {
			// Item may have been deleted subsequently.
			continue
		}
		delete(f.items, id)
		err := process(item)
		if e, ok := err.(ErrRequeue); ok {
			f.addIfNotPresent(id, item)
			err = e.Err
		}
		return item, err
	}
}

// Replace will delete the contents of 'f', using instead the given map.
// 'f' takes ownership of the map, you should not reference the map again
// after calling this function. f's queue is reset, too; upon return, it
// will contain the items in the map, in no particular order.
func (f *FIFO) Replace(list []interface{}, resourceVersion string) error {
	items := make(map[string]interface{}, len(list))
	for _, item := range list {
		key, err := f.keyFunc(item)
		if err != nil {
			return KeyError{item, err}
		}
		items[key] = item
	}

	f.lock.Lock()
	defer f.lock.Unlock()

	if !f.populated {
		f.populated = true
		f.initialPopulationCount = len(items)
	}

	f.items = items
	f.queue = f.queue[:0]
	for id := range items {
		f.queue = append(f.queue, id)
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
	return nil
}

// Resync will ensure that every object in the Store has its key in the queue.
// This should be a no-op, because that property is maintained by all operations.
func (f *FIFO) Resync() error {
	f.lock.Lock()
	defer f.lock.Unlock()

	inQueue := sets.NewString()
	for _, id := range f.queue {
		inQueue.Insert(id)
	}
	for id := range f.items {
		if !inQueue.Has(id) {
			f.queue = append(f.queue, id)
		}
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
	return nil
}

// NewFIFO returns a Store which can be used to queue up items to
// process.
func NewFIFO(keyFunc KeyFunc) *FIFO {
	f := &FIFO{
		items:   map[string]interface{}{},
		queue:   []string{},
		keyFunc: keyFunc,
	}
	f.cond.L = &f.lock
	return f
}
<|endoftext|>


<commit_before>package localforeman

import (
	"os"

	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/executor"
	"polydawn.net/repeatr/lib/guid"
	"polydawn.net/repeatr/model/cassandra"
	"polydawn.net/repeatr/model/catalog"
	"polydawn.net/repeatr/model/formula"
)

type Foreman struct {
	// configuration

	cassy    cassandra.Cassandra
	executor executor.Executor

	// work state

	chNewCatalog <-chan catalog.ID
	chOldCatalog <-chan catalog.ID
	currentPlans currentPlans
}

func (man *Foreman) work() {
	man.register()
	for {
		man.pump()
		man.evoke()
	}
}

// runs once upon start, rigging up our event feeds
func (man *Foreman) register() {
	// Register for catalog changes.
	chNewCatalog := make(chan catalog.ID, 100)
	man.cassy.ObserveCatalogs(chNewCatalog)
	man.chNewCatalog = chNewCatalog

	// Grab all current catalogs.  Give em one due consideration.
	// Dump em into a channel so we can select freely between these and fresh updates.
	// If an update careens in for one of these, and we react to that first, that's
	//  completely AOK: it'll end up nilled out when we reduce to stage2 formulas;
	//  the whole thing is an "at least once" situation.
	// We operate on CatalogIDs here instead of the full struct for two reasons:
	// - it's cheaper, if you didn't already have the whole thing loaded
	// - it means when you get the memo, you go get the latest -- and this
	//  absolves a race between old and updated catalogs in select.
	oldCats := man.cassy.ListCatalogs()
	oldCatalogChan := make(chan catalog.ID, len(oldCats))
	for _, cat := range oldCats {
		oldCatalogChan <- cat
	}
	man.chOldCatalog = oldCatalogChan

	// other misc init (can't be arsed to seperate since it's also an "exactly once, at start" thing)
	man.currentPlans.commissionIndex = make(map[formula.CommissionID]int)
}

// runs in a loop, accepting events, generating new formulas, and adding them to currentPlans.
func (man *Foreman) pump() {
	// Select a new and interesting catalog.
	var catID catalog.ID
	select {
	case catID = <-man.chNewCatalog: // Voom
	case catID = <-man.chOldCatalog: // Voom
	}

	// 'Mark' phase: See what we can do with it.
	markedSet := man.cassy.SelectCommissionsByInputCatalog(catID)

	// 'Fill' phase.
	formulas := make([]*formula.Stage2, 0)
	reasons := make(map[formula.CommissionID]*formula.Stage2)
	for _, commish := range markedSet {
		formula := (*formula.Stage2)(&commish.Formula) // FIXME need clone func and sane mem owner defn
		for iname, input := range formula.Inputs {
			cellID := catalog.ID(iname)                                  // this may not always be true / this is the same type haze around pre-pin inputs showing again
			input.Hash = string(man.cassy.Catalog(cellID).Latest().Hash) // this string cast is because def is currently Wrong
		}
		formulas = append(formulas, formula)
		reasons[commish.ID] = formula
	}

	// 'Seenit' filter.
	// TODO
	// Compute Stage2 identifiers and index by that.  If it's been seen before, forget it.

	// Commit phase: push the stage2 formula back to the knowledge base.
	// TODO

	// Planning phase: update our internal concept of what's up next.
	for reason, formula := range reasons {
		man.currentPlans.push(formula, reason)
	}
}

/*
	An atom capturing the foreman's current best idea of what formulas
	it wants to evaluate next.

	This is stateful because the foreman acknowledges info and produces
	new plans at a different pace than it can execute their evaluation,
	and it may also decide to cancel some plans in response to new info.
	(Also, it's a checkpoint for use in testing.)
*/
type currentPlans struct {
	// flat list of what formulas we want to run next, in order.
	queue []*formula.Stage2

	// map from cmid to queue index (so we can delete/replace things if they're now out of date).
	commissionIndex map[formula.CommissionID]int
}

func (p *currentPlans) push(f *formula.Stage2, reason formula.CommissionID) {
	if i, ok := p.commissionIndex[reason]; ok {
		p.queue[i] = f
	} else {
		i = len(p.queue)
		p.queue = append(p.queue, f)
		p.commissionIndex[reason] = i
	}
}

func (p *currentPlans) poll() *formula.Stage2 {
	l := len(p.queue)
	if l == 0 {
		return nil
	}
	v := p.queue[0]
	p.queue = p.queue[1:]
	return v
}

func (man *Foreman) evoke() {
	// Run.
	for _, formula := range man.currentPlans.queue {
		job := man.executor.Start(def.Formula(*formula), def.JobID(guid.New()), nil, os.Stderr)
		job.Wait()
	}
	// TODO all sorts of other housekeeping on the queue

	// Commit phase: push the stage3 formulas back to storage.
	// TODO
	// If someone wants to react to these new run records by publishing
	//  a new edition of a catalog, they can do that by asking
	//   cassy to observe new run records like this one as they come in.
}
<commit_msg>It's really just plans. "Current" is in your head.<commit_after>package localforeman

import (
	"os"

	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/executor"
	"polydawn.net/repeatr/lib/guid"
	"polydawn.net/repeatr/model/cassandra"
	"polydawn.net/repeatr/model/catalog"
	"polydawn.net/repeatr/model/formula"
)

type Foreman struct {
	// configuration

	cassy    cassandra.Cassandra
	executor executor.Executor

	// work state

	chNewCatalog <-chan catalog.ID
	chOldCatalog <-chan catalog.ID
	currentPlans plans
}

func (man *Foreman) work() {
	man.register()
	for {
		man.pump()
		man.evoke()
	}
}

// runs once upon start, rigging up our event feeds
func (man *Foreman) register() {
	// Register for catalog changes.
	chNewCatalog := make(chan catalog.ID, 100)
	man.cassy.ObserveCatalogs(chNewCatalog)
	man.chNewCatalog = chNewCatalog

	// Grab all current catalogs.  Give em one due consideration.
	// Dump em into a channel so we can select freely between these and fresh updates.
	// If an update careens in for one of these, and we react to that first, that's
	//  completely AOK: it'll end up nilled out when we reduce to stage2 formulas;
	//  the whole thing is an "at least once" situation.
	// We operate on CatalogIDs here instead of the full struct for two reasons:
	// - it's cheaper, if you didn't already have the whole thing loaded
	// - it means when you get the memo, you go get the latest -- and this
	//  absolves a race between old and updated catalogs in select.
	oldCats := man.cassy.ListCatalogs()
	oldCatalogChan := make(chan catalog.ID, len(oldCats))
	for _, cat := range oldCats {
		oldCatalogChan <- cat
	}
	man.chOldCatalog = oldCatalogChan

	// other misc init (can't be arsed to seperate since it's also an "exactly once, at start" thing)
	man.currentPlans.commissionIndex = make(map[formula.CommissionID]int)
}

// runs in a loop, accepting events, generating new formulas, and adding them to currentPlans.
func (man *Foreman) pump() {
	// Select a new and interesting catalog.
	var catID catalog.ID
	select {
	case catID = <-man.chNewCatalog: // Voom
	case catID = <-man.chOldCatalog: // Voom
	}

	// 'Mark' phase: See what we can do with it.
	markedSet := man.cassy.SelectCommissionsByInputCatalog(catID)

	// 'Fill' phase.
	formulas := make([]*formula.Stage2, 0)
	reasons := make(map[formula.CommissionID]*formula.Stage2)
	for _, commish := range markedSet {
		formula := (*formula.Stage2)(&commish.Formula) // FIXME need clone func and sane mem owner defn
		for iname, input := range formula.Inputs {
			cellID := catalog.ID(iname)                                  // this may not always be true / this is the same type haze around pre-pin inputs showing again
			input.Hash = string(man.cassy.Catalog(cellID).Latest().Hash) // this string cast is because def is currently Wrong
		}
		formulas = append(formulas, formula)
		reasons[commish.ID] = formula
	}

	// 'Seenit' filter.
	// TODO
	// Compute Stage2 identifiers and index by that.  If it's been seen before, forget it.

	// Commit phase: push the stage2 formula back to the knowledge base.
	// TODO

	// Planning phase: update our internal concept of what's up next.
	for reason, formula := range reasons {
		man.currentPlans.push(formula, reason)
	}
}

/*
	An atom capturing the foreman's current best idea of what formulas
	it wants to evaluate next.

	This is stateful because the foreman acknowledges info and produces
	new plans at a different pace than it can execute their evaluation,
	and it may also decide to cancel some plans in response to new info.
	(Also, it's a checkpoint for use in testing.)
*/
type plans struct {
	// flat list of what formulas we want to run next, in order.
	queue []*formula.Stage2

	// map from cmid to queue index (so we can delete/replace things if they're now out of date).
	commissionIndex map[formula.CommissionID]int
}

func (p *plans) push(f *formula.Stage2, reason formula.CommissionID) {
	if i, ok := p.commissionIndex[reason]; ok {
		p.queue[i] = f
	} else {
		i = len(p.queue)
		p.queue = append(p.queue, f)
		p.commissionIndex[reason] = i
	}
}

func (p *plans) poll() *formula.Stage2 {
	l := len(p.queue)
	if l == 0 {
		return nil
	}
	v := p.queue[0]
	p.queue = p.queue[1:]
	return v
}

func (man *Foreman) evoke() {
	// Run.
	for _, formula := range man.currentPlans.queue {
		job := man.executor.Start(def.Formula(*formula), def.JobID(guid.New()), nil, os.Stderr)
		job.Wait()
	}
	// TODO all sorts of other housekeeping on the queue

	// Commit phase: push the stage3 formulas back to storage.
	// TODO
	// If someone wants to react to these new run records by publishing
	//  a new edition of a catalog, they can do that by asking
	//   cassy to observe new run records like this one as they come in.
}
<|endoftext|>


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

/*
Present displays slide presentations and articles. It runs a web server that
presents slide and article files from the current directory.

It may be run as a stand-alone command or an App Engine app.
The stand-alone version permits the execution of programs from within a
presentation. The App Engine version does not provide this functionality.

Usage of present:
  -base="": base path for slide template and static resources
  -http="127.0.0.1:3999": host:port to listen on

You may use the app.yaml file provided in the root of the go.talks repository
to deploy present to App Engine:
	appcfg.py update -A your-app-id -V your-app-version /path/to/go.talks

Input files are named foo.extension, where "extension" defines the format of
the generated output. The supported formats are:
	.slide        // HTML5 slide presentation
	.article      // article format, such as a blog post

The present file format is documented by the present package:
http://godoc.org/code.google.com/p/go.tools/godoc/present
*/
package main
<commit_msg>go.talk/present: fix a link.<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Present displays slide presentations and articles. It runs a web server that
presents slide and article files from the current directory.

It may be run as a stand-alone command or an App Engine app.
The stand-alone version permits the execution of programs from within a
presentation. The App Engine version does not provide this functionality.

Usage of present:
  -base="": base path for slide template and static resources
  -http="127.0.0.1:3999": host:port to listen on

You may use the app.yaml file provided in the root of the go.talks repository
to deploy present to App Engine:
	appcfg.py update -A your-app-id -V your-app-version /path/to/go.talks

Input files are named foo.extension, where "extension" defines the format of
the generated output. The supported formats are:
	.slide        // HTML5 slide presentation
	.article      // article format, such as a blog post

The present file format is documented by the present package:
http://godoc.org/code.google.com/p/go.tools/present
*/
package main
<|endoftext|>


<commit_before>package chug_test

import (
	"fmt"
	"reflect"

	"code.cloudfoundry.org/lager/chug"
	"github.com/onsi/gomega/format"
	"github.com/onsi/gomega/types"
)

func MatchLogEntry(entry chug.LogEntry) types.GomegaMatcher {
	return &logEntryMatcher{entry}
}

type logEntryMatcher struct {
	entry chug.LogEntry
}

func (m *logEntryMatcher) Match(actual interface{}) (success bool, err error) {
	actualEntry, ok := actual.(chug.LogEntry)
	if !ok {
		return false, fmt.Errorf("MatchLogEntry must be passed a chug.LogEntry.  Got:\n%s", format.Object(actual, 1))
	}

	return m.entry.LogLevel == actualEntry.LogLevel &&
		m.entry.Source == actualEntry.Source &&
		m.entry.Message == actualEntry.Message &&
		m.entry.Session == actualEntry.Session &&
		reflect.DeepEqual(m.entry.Error, actualEntry.Error) &&
		m.entry.Trace == actualEntry.Trace &&
		reflect.DeepEqual(m.entry.Data, actualEntry.Data), nil
}

func (m *logEntryMatcher) FailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "to equal", m.entry)
}

func (m *logEntryMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "not to equal", m.entry)
}
<commit_msg>Fix code which breaks compiler in match_log_entry_test<commit_after>package chug_test

import (
	"fmt"
	"reflect"

	"code.cloudfoundry.org/lager/chug"
	"github.com/onsi/gomega/format"
	"github.com/onsi/gomega/types"
)

func MatchLogEntry(entry chug.LogEntry) types.GomegaMatcher {
	return &logEntryMatcher{entry}
}

type logEntryMatcher struct {
	entry chug.LogEntry
}

func (m *logEntryMatcher) Match(actual interface{}) (success bool, err error) {
	actualEntry, ok := actual.(chug.LogEntry)
	if !ok {
		return false, fmt.Errorf("MatchLogEntry must be passed a chug.LogEntry.  Got:\n%s", format.Object(actual, 1))
	}

	return reflect.DeepEqual(m.entry.Error, actualEntry.Error) &&
		m.entry.LogLevel == actualEntry.LogLevel &&
		m.entry.Source == actualEntry.Source &&
		m.entry.Message == actualEntry.Message &&
		m.entry.Session == actualEntry.Session &&
		m.entry.Trace == actualEntry.Trace &&
		reflect.DeepEqual(m.entry.Data, actualEntry.Data), nil
}

func (m *logEntryMatcher) FailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "to equal", m.entry)
}

func (m *logEntryMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "not to equal", m.entry)
}
<|endoftext|>


<commit_before>package localca

import (
	"encoding/pem"
	"errors"
	"io/ioutil"
	"os"
	"testing"

	"github.com/cloudflare/cfssl/csr"
	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/initca"
)

func tempName() (string, error) {
	tmpf, err := ioutil.TempFile("", "transport_cachedkp_")
	if err != nil {
		return "", err
	}

	name := tmpf.Name()
	tmpf.Close()
	return name, nil
}

func TestEncodePEM(t *testing.T) {
	p := &pem.Block{
		Type:  "CERTIFICATE REQUEST",
		Bytes: []byte(`¯\_(ツ)_/¯`),
	}
	t.Logf("PEM:\n%s\n\n", string(pem.EncodeToMemory(p)))
}

func TestLoadSigner(t *testing.T) {
	lca := &CA{}
	certPEM, csrPEM, keyPEM, err := initca.New(ExampleRequest())
	if err != nil {
		t.Fatal(err)
	}

	_, err = lca.CACertificate()
	if !errors.Is(err, errNotSetup) {
		t.Fatalf("expected an errNotSetup (%v), got: %v", errNotSetup, err)
	}

	_, err = lca.SignCSR(csrPEM)
	if !errors.Is(err, errNotSetup) {
		t.Fatalf("expected an errNotSetup (%v), got: %v", errNotSetup, err)
	}

	lca.KeyFile, err = tempName()
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(lca.KeyFile)

	lca.CertFile, err = tempName()
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(lca.CertFile)

	err = ioutil.WriteFile(lca.KeyFile, keyPEM, 0644)
	if err != nil {
		t.Fatal(err)
	}

	err = ioutil.WriteFile(lca.CertFile, certPEM, 0644)
	if err != nil {
		t.Fatal(err)
	}

	err = Load(lca, ExampleSigningConfig())
	if err != nil {
		t.Fatal(err)
	}
}

var testRequest = &csr.CertificateRequest{
	CN: "Transport Test Identity",
	KeyRequest: &csr.KeyRequest{
		A: "ecdsa",
		S: 256,
	},
	Hosts: []string{"127.0.0.1"},
}

func TestNewSigner(t *testing.T) {
	req := ExampleRequest()
	lca, err := New(req, ExampleSigningConfig())
	if err != nil {
		t.Fatal(err)
	}

	csrPEM, _, err := csr.ParseRequest(testRequest)
	if err != nil {
		t.Fatal(err)
	}

	certPEM, err := lca.SignCSR(csrPEM)
	if err != nil {
		t.Fatal(err)
	}

	_, err = helpers.ParseCertificatePEM(certPEM)
	if err != nil {
		t.Fatal(err)
	}

	certPEM, err = lca.CACertificate()
	if err != nil {
		t.Fatal(err)
	}

	cert, err := helpers.ParseCertificatePEM(certPEM)
	if err != nil {
		t.Fatal(err)
	}

	if cert.Subject.CommonName != req.CN {
		t.Fatalf("common names don't match: '%s' != '%s'", cert.Subject.CommonName, req.CN)
	}

	lca.Toggle()
	_, err = lca.SignCSR(csrPEM)
	if !errors.Is(err, errDisabled) {
		t.Fatalf("expected an errDisabled (%v), got: %v", errDisabled, err)
	}
	lca.Toggle()

	_, err = lca.SignCSR(certPEM)
	if err == nil {
		t.Fatal("shouldn't be able to sign non-CSRs")
	}

	p := &pem.Block{
		Type:  "CERTIFICATE REQUEST",
		Bytes: []byte(`¯\_(ツ)_/¯`),
	}
	junkCSR := pem.EncodeToMemory(p)

	_, err = lca.SignCSR(junkCSR)
	if err == nil {
		t.Fatal("signing a junk CSR should fail")
	}
	t.Logf("error: %s", err)
}
<commit_msg>transport/ca/localca: remove uses of deprecated io/ioutil<commit_after>package localca

import (
	"encoding/pem"
	"errors"
	"os"
	"path/filepath"
	"testing"

	"github.com/cloudflare/cfssl/csr"
	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/initca"
)

func TestEncodePEM(t *testing.T) {
	p := &pem.Block{
		Type:  "CERTIFICATE REQUEST",
		Bytes: []byte(`¯\_(ツ)_/¯`),
	}
	t.Logf("PEM:\n%s\n\n", string(pem.EncodeToMemory(p)))
}

func TestLoadSigner(t *testing.T) {
	lca := &CA{}
	certPEM, csrPEM, keyPEM, err := initca.New(ExampleRequest())
	if err != nil {
		t.Fatal(err)
	}

	_, err = lca.CACertificate()
	if !errors.Is(err, errNotSetup) {
		t.Fatalf("expected an errNotSetup (%v), got: %v", errNotSetup, err)
	}

	_, err = lca.SignCSR(csrPEM)
	if !errors.Is(err, errNotSetup) {
		t.Fatalf("expected an errNotSetup (%v), got: %v", errNotSetup, err)
	}

	tmpDir := t.TempDir()
	lca.KeyFile = filepath.Join(tmpDir, "KeyFile")
	lca.CertFile = filepath.Join(tmpDir, "CertFile")

	err = os.WriteFile(lca.KeyFile, keyPEM, 0644)
	if err != nil {
		t.Fatal(err)
	}

	err = os.WriteFile(lca.CertFile, certPEM, 0644)
	if err != nil {
		t.Fatal(err)
	}

	err = Load(lca, ExampleSigningConfig())
	if err != nil {
		t.Fatal(err)
	}
}

var testRequest = &csr.CertificateRequest{
	CN: "Transport Test Identity",
	KeyRequest: &csr.KeyRequest{
		A: "ecdsa",
		S: 256,
	},
	Hosts: []string{"127.0.0.1"},
}

func TestNewSigner(t *testing.T) {
	req := ExampleRequest()
	lca, err := New(req, ExampleSigningConfig())
	if err != nil {
		t.Fatal(err)
	}

	csrPEM, _, err := csr.ParseRequest(testRequest)
	if err != nil {
		t.Fatal(err)
	}

	certPEM, err := lca.SignCSR(csrPEM)
	if err != nil {
		t.Fatal(err)
	}

	_, err = helpers.ParseCertificatePEM(certPEM)
	if err != nil {
		t.Fatal(err)
	}

	certPEM, err = lca.CACertificate()
	if err != nil {
		t.Fatal(err)
	}

	cert, err := helpers.ParseCertificatePEM(certPEM)
	if err != nil {
		t.Fatal(err)
	}

	if cert.Subject.CommonName != req.CN {
		t.Fatalf("common names don't match: '%s' != '%s'", cert.Subject.CommonName, req.CN)
	}

	lca.Toggle()
	_, err = lca.SignCSR(csrPEM)
	if !errors.Is(err, errDisabled) {
		t.Fatalf("expected an errDisabled (%v), got: %v", errDisabled, err)
	}
	lca.Toggle()

	_, err = lca.SignCSR(certPEM)
	if err == nil {
		t.Fatal("shouldn't be able to sign non-CSRs")
	}

	p := &pem.Block{
		Type:  "CERTIFICATE REQUEST",
		Bytes: []byte(`¯\_(ツ)_/¯`),
	}
	junkCSR := pem.EncodeToMemory(p)

	_, err = lca.SignCSR(junkCSR)
	if err == nil {
		t.Fatal("signing a junk CSR should fail")
	}
	t.Logf("error: %s", err)
}
<|endoftext|>


<commit_before>package editform

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/aerogo/api"
	"github.com/animenotifier/arn"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

// Render renders a generic editing UI for any kind of datatype that has an ID.
func Render(obj interface{}, title string, user *arn.User) string {
	t := reflect.TypeOf(obj).Elem()
	v := reflect.ValueOf(obj).Elem()
	id := findMainID(t, v)
	lowerCaseTypeName := strings.ToLower(t.Name())
	endpoint := `/api/` + lowerCaseTypeName + `/` + id.String()

	var b bytes.Buffer

	b.WriteString(`<div class="widget-form">`)
	b.WriteString(`<div class="widget" data-api="` + endpoint + `">`)

	// Title
	b.WriteString(`<h1 class="mountable">`)
	b.WriteString(title)
	b.WriteString(`</h1>`)

	// Render the object with its fields
	RenderObject(&b, obj, "")

	// Additional buttons when logged in
	if user != nil {
		b.WriteString(`<div class="buttons">`)

		// Publish button
		_, ok := t.FieldByName("IsDraft")

		if ok {
			isDraft := v.FieldByName("IsDraft").Interface().(bool)

			if isDraft {
				b.WriteString(`<div class="buttons"><button class="mountable action" data-action="publish" data-trigger="click">` + utils.Icon("share-alt") + `Publish</button></div>`)
			}
		}

		// Delete button
		_, isDeletable := obj.(api.Deletable)

		if isDeletable && (user.Role == "editor" || user.Role == "admin") {
			returnPath := ""

			switch lowerCaseTypeName {
			case "anime":
				returnPath = "/explore"
			case "company":
				returnPath = "/companies"
			default:
				returnPath = "/" + lowerCaseTypeName + "s"
			}

			b.WriteString(`<button class="mountable action" data-action="deleteObject" data-trigger="click" data-return-path="` + returnPath + `" data-confirm-type="` + lowerCaseTypeName + `">` + utils.Icon("trash") + `Delete</button>`)
		}

		b.WriteString(`</div>`)
	}

	b.WriteString("
")
	b.WriteString("
")

	return b.String()
}

// RenderObject renders the UI for the object into the bytes buffer and appends an ID prefix for all API requests.
// The ID prefix should either be empty or end with a dot character.
func RenderObject(b *bytes.Buffer, obj interface{}, idPrefix string) {
	t := reflect.TypeOf(obj)
	v := reflect.ValueOf(obj)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		v = v.Elem()
	}

	// Fields
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		RenderField(b, &v, field, idPrefix)
	}
}

// RenderField ...
func RenderField(b *bytes.Buffer, v *reflect.Value, field reflect.StructField, idPrefix string) {
	fieldValue := reflect.Indirect(v.FieldByName(field.Name))

	// Embedded fields
	if field.Anonymous {
		RenderObject(b, fieldValue.Interface(), idPrefix)
		return
	}

	if field.Tag.Get("editable") != "true" {
		return
	}

	b.WriteString("
")
	defer b.WriteString("
")

	fieldType := field.Type.String()

	// String
	if fieldType == "string" {
		idType := field.Tag.Get("idType")

		// Try to infer the ID type by the field name
		if idType == "" {
			switch field.Name {
			case "AnimeID":
				idType = "Anime"

			case "CharacterID":
				idType = "Character"
			}
		}

		showPreview := idType != "" && fieldValue.String() != ""

		if showPreview {
			b.WriteString("
")
		}

		// Input field
		if field.Tag.Get("datalist") != "" {
			dataList := field.Tag.Get("datalist")
			values := arn.DataLists[dataList]
			b.WriteString(components.InputSelection(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip"), values))
		} else if field.Tag.Get("type") == "textarea" {
			b.WriteString(components.InputTextArea(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip")))
		} else if field.Tag.Get("type") == "upload" {
			endpoint := field.Tag.Get("endpoint")
			id := v.FieldByName("ID").String()
			endpoint = strings.Replace(endpoint, ":id", id, 1)

			b.WriteString(components.InputFileUpload(idPrefix+field.Name, field.Name, field.Tag.Get("filetype"), endpoint))
		} else {
			b.WriteString(components.InputText(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip")))
		}

		if showPreview {
			b.WriteString("
")
		}

		// Preview
		switch idType {
		case "Anime":
			animeID := fieldValue.String()
			anime, err := arn.GetAnime(animeID)

			if err == nil {
				b.WriteString(components.EditFormImagePreview(anime.Link(), anime.ImageLink("small"), true))
			}

		case "Character":
			characterID := fieldValue.String()
			character, err := arn.GetCharacter(characterID)

			if err == nil {
				b.WriteString(components.EditFormImagePreview(character.Link(), character.ImageLink("medium"), false))
			}

		case "":
			break

		default:
			fmt.Println("Error: Unknown idType tag: " + idType)
		}

		// Close preview tags
		if showPreview {
			b.WriteString("

")
		}

		return
	}

	// Int
	if fieldType == "int" {
		b.WriteString(components.InputNumber(idPrefix+field.Name, float64(fieldValue.Int()), field.Name, field.Tag.Get("tooltip"), "", "", "1"))
		return
	}

	// Float
	if fieldType == "float64" {
		b.WriteString(components.InputNumber(idPrefix+field.Name, fieldValue.Float(), field.Name, field.Tag.Get("tooltip"), "", "", ""))
		return
	}

	// Bool
	if fieldType == "bool" {
		if field.Name == "IsDraft" {
			return
		}

		b.WriteString(components.InputBool(idPrefix+field.Name, fieldValue.Bool(), field.Name, field.Tag.Get("tooltip")))
		return
	}

	// Array of strings
	if fieldType == "[]string" {
		b.WriteString(components.InputTags(idPrefix+field.Name, fieldValue.Interface().([]string), field.Name, field.Tag.Get("tooltip")))
		return
	}

	// Any kind of array
	if strings.HasPrefix(fieldType, "[]") {
		b.WriteString(`<div class="widget-section">`)
		b.WriteString(`<h3 class="widget-title">`)
		b.WriteString(field.Name)
		b.WriteString(`</h3>`)

		for sliceIndex := 0; sliceIndex < fieldValue.Len(); sliceIndex++ {
			b.WriteString(`<div class="widget-section">`)

			b.WriteString(`<div class="widget-title">`)

			// Title
			b.WriteString(strconv.Itoa(sliceIndex+1) + ". " + field.Name)
			b.WriteString(`<div class="spacer"></div>`)

			// Remove button
			b.WriteString(`<button class="action" title="Delete this ` + field.Name + `" data-action="arrayRemove" data-trigger="click" data-field="` + field.Name + `" data-index="`)
			b.WriteString(strconv.Itoa(sliceIndex))
			b.WriteString(`">` + utils.RawIcon("trash") + `</button>`)

			b.WriteString(`</div>`)

			arrayObj := fieldValue.Index(sliceIndex).Interface()
			arrayIDPrefix := fmt.Sprintf("%s[%d].", field.Name, sliceIndex)
			RenderObject(b, arrayObj, arrayIDPrefix)

			// Preview
			// elementValue := fieldValue.Index(sliceIndex)
			// RenderArrayElement(b, &elementValue)
			if fieldType == "[]*arn.ExternalMedia" {
				b.WriteString(components.ExternalMedia(fieldValue.Index(sliceIndex).Interface().(*arn.ExternalMedia)))
			}

			b.WriteString(`</div>`)
		}

		b.WriteString(`<div class="buttons">`)
		b.WriteString(`<button class="action" data-action="arrayAppend" data-trigger="click" data-field="` + field.Name + `">` + utils.Icon("plus") + `Add ` + field.Name + `</button>`)
		b.WriteString(`</div>`)

		b.WriteString(`</div>`)
		return
	}

	// Any custom field type will be recursively rendered via another RenderObject call
	b.WriteString(`<div class="widget-section">`)
	b.WriteString(`<h3 class="widget-title">` + field.Name + `</h3>`)

	// Indent the fields
	b.WriteString(`<div class="indent">`)
	RenderObject(b, fieldValue.Interface(), field.Name+".")
	b.WriteString(`</div>`)

	b.WriteString(`</div>`)
}

// findMainID finds the main ID of the object.
func findMainID(t reflect.Type, v reflect.Value) reflect.Value {
	idField := v.FieldByName("ID")

	if idField.IsValid() {
		return reflect.Indirect(idField)
	}

	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)

		if field.Tag.Get("mainID") == "true" {
			return reflect.Indirect(v.Field(i))
		}
	}

	panic("Type " + t.Name() + " doesn't have a main ID!")
}
<commit_msg>Made editform less complex<commit_after>package editform

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/aerogo/api"
	"github.com/animenotifier/arn"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

// Render renders a generic editing UI for any kind of datatype that has an ID.
func Render(obj interface{}, title string, user *arn.User) string {
	t := reflect.TypeOf(obj).Elem()
	v := reflect.ValueOf(obj).Elem()
	id := findMainID(t, v)
	lowerCaseTypeName := strings.ToLower(t.Name())
	endpoint := `/api/` + lowerCaseTypeName + `/` + id.String()

	var b bytes.Buffer

	b.WriteString(`<div class="widget-form">`)
	b.WriteString(`<div class="widget" data-api="` + endpoint + `">`)

	// Title
	b.WriteString(`<h1 class="mountable">`)
	b.WriteString(title)
	b.WriteString(`</h1>`)

	// Render the object with its fields
	RenderObject(&b, obj, "")

	// Additional buttons when logged in
	if user != nil {
		b.WriteString(`<div class="buttons">`)

		// Publish button
		_, ok := t.FieldByName("IsDraft")

		if ok {
			isDraft := v.FieldByName("IsDraft").Interface().(bool)

			if isDraft {
				b.WriteString(`<div class="buttons"><button class="mountable action" data-action="publish" data-trigger="click">` + utils.Icon("share-alt") + `Publish</button></div>`)
			}
		}

		// Delete button
		_, isDeletable := obj.(api.Deletable)

		if isDeletable && (user.Role == "editor" || user.Role == "admin") {
			returnPath := ""

			switch lowerCaseTypeName {
			case "anime":
				returnPath = "/explore"
			case "company":
				returnPath = "/companies"
			default:
				returnPath = "/" + lowerCaseTypeName + "s"
			}

			b.WriteString(`<button class="mountable action" data-action="deleteObject" data-trigger="click" data-return-path="` + returnPath + `" data-confirm-type="` + lowerCaseTypeName + `">` + utils.Icon("trash") + `Delete</button>`)
		}

		b.WriteString(`</div>`)
	}

	b.WriteString("
")
	b.WriteString("
")

	return b.String()
}

// RenderObject renders the UI for the object into the bytes buffer and appends an ID prefix for all API requests.
// The ID prefix should either be empty or end with a dot character.
func RenderObject(b *bytes.Buffer, obj interface{}, idPrefix string) {
	t := reflect.TypeOf(obj)
	v := reflect.ValueOf(obj)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		v = v.Elem()
	}

	// Fields
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		RenderField(b, &v, field, idPrefix)
	}
}

// RenderField ...
func RenderField(b *bytes.Buffer, v *reflect.Value, field reflect.StructField, idPrefix string) {
	fieldValue := reflect.Indirect(v.FieldByName(field.Name))

	// Embedded fields
	if field.Anonymous {
		RenderObject(b, fieldValue.Interface(), idPrefix)
		return
	}

	if field.Tag.Get("editable") != "true" {
		return
	}

	b.WriteString("
")
	defer b.WriteString("
")

	fieldType := field.Type.String()

	// String
	if fieldType == "string" {
		renderStringField(b, v, field, idPrefix, fieldValue)
		return
	}

	// Int
	if fieldType == "int" {
		b.WriteString(components.InputNumber(idPrefix+field.Name, float64(fieldValue.Int()), field.Name, field.Tag.Get("tooltip"), "", "", "1"))
		return
	}

	// Float
	if fieldType == "float64" {
		b.WriteString(components.InputNumber(idPrefix+field.Name, fieldValue.Float(), field.Name, field.Tag.Get("tooltip"), "", "", ""))
		return
	}

	// Bool
	if fieldType == "bool" {
		if field.Name == "IsDraft" {
			return
		}

		b.WriteString(components.InputBool(idPrefix+field.Name, fieldValue.Bool(), field.Name, field.Tag.Get("tooltip")))
		return
	}

	// Array of strings
	if fieldType == "[]string" {
		b.WriteString(components.InputTags(idPrefix+field.Name, fieldValue.Interface().([]string), field.Name, field.Tag.Get("tooltip")))
		return
	}

	// Any kind of array
	if strings.HasPrefix(fieldType, "[]") {
		renderSliceField(b, v, field, idPrefix, fieldType, fieldValue)
		return
	}

	// Any custom field type will be recursively rendered via another RenderObject call
	b.WriteString(`<div class="widget-section">`)
	b.WriteString(`<h3 class="widget-title">` + field.Name + `</h3>`)

	// Indent the fields
	b.WriteString(`<div class="indent">`)
	RenderObject(b, fieldValue.Interface(), field.Name+".")
	b.WriteString(`</div>`)

	b.WriteString(`</div>`)
}

// String field
func renderStringField(b *bytes.Buffer, v *reflect.Value, field reflect.StructField, idPrefix string, fieldValue reflect.Value) {
	idType := field.Tag.Get("idType")

	// Try to infer the ID type by the field name
	if idType == "" {
		switch field.Name {
		case "AnimeID":
			idType = "Anime"

		case "CharacterID":
			idType = "Character"
		}
	}

	showPreview := idType != "" && fieldValue.String() != ""

	if showPreview {
		b.WriteString("
")
	}

	// Input field
	if field.Tag.Get("datalist") != "" {
		dataList := field.Tag.Get("datalist")
		values := arn.DataLists[dataList]
		b.WriteString(components.InputSelection(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip"), values))
	} else if field.Tag.Get("type") == "textarea" {
		b.WriteString(components.InputTextArea(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip")))
	} else if field.Tag.Get("type") == "upload" {
		endpoint := field.Tag.Get("endpoint")
		id := v.FieldByName("ID").String()
		endpoint = strings.Replace(endpoint, ":id", id, 1)

		b.WriteString(components.InputFileUpload(idPrefix+field.Name, field.Name, field.Tag.Get("filetype"), endpoint))
	} else {
		b.WriteString(components.InputText(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip")))
	}

	if showPreview {
		b.WriteString("
")
	}

	// Preview
	switch idType {
	case "Anime":
		animeID := fieldValue.String()
		anime, err := arn.GetAnime(animeID)

		if err == nil {
			b.WriteString(components.EditFormImagePreview(anime.Link(), anime.ImageLink("small"), true))
		}

	case "Character":
		characterID := fieldValue.String()
		character, err := arn.GetCharacter(characterID)

		if err == nil {
			b.WriteString(components.EditFormImagePreview(character.Link(), character.ImageLink("medium"), false))
		}

	case "":
		break

	default:
		fmt.Println("Error: Unknown idType tag: " + idType)
	}

	// Close preview tags
	if showPreview {
		b.WriteString("

")
	}
}

// Slice field
func renderSliceField(b *bytes.Buffer, v *reflect.Value, field reflect.StructField, idPrefix string, fieldType string, fieldValue reflect.Value) {
	b.WriteString(`<div class="widget-section">`)
	b.WriteString(`<h3 class="widget-title">`)
	b.WriteString(field.Name)
	b.WriteString(`</h3>`)

	for sliceIndex := 0; sliceIndex < fieldValue.Len(); sliceIndex++ {
		b.WriteString(`<div class="widget-section">`)

		b.WriteString(`<div class="widget-title">`)

		// Title
		b.WriteString(strconv.Itoa(sliceIndex+1) + ". " + field.Name)
		b.WriteString(`<div class="spacer"></div>`)

		// Remove button
		b.WriteString(`<button class="action" title="Delete this ` + field.Name + `" data-action="arrayRemove" data-trigger="click" data-field="` + field.Name + `" data-index="`)
		b.WriteString(strconv.Itoa(sliceIndex))
		b.WriteString(`">` + utils.RawIcon("trash") + `</button>`)

		b.WriteString(`</div>`)

		arrayObj := fieldValue.Index(sliceIndex).Interface()
		arrayIDPrefix := fmt.Sprintf("%s[%d].", field.Name, sliceIndex)
		RenderObject(b, arrayObj, arrayIDPrefix)

		// Preview
		// elementValue := fieldValue.Index(sliceIndex)
		// RenderArrayElement(b, &elementValue)
		if fieldType == "[]*arn.ExternalMedia" {
			b.WriteString(components.ExternalMedia(fieldValue.Index(sliceIndex).Interface().(*arn.ExternalMedia)))
		}

		b.WriteString(`</div>`)
	}

	b.WriteString(`<div class="buttons">`)
	b.WriteString(`<button class="action" data-action="arrayAppend" data-trigger="click" data-field="` + field.Name + `">` + utils.Icon("plus") + `Add ` + field.Name + `</button>`)
	b.WriteString(`</div>`)

	b.WriteString(`</div>`)
}

// findMainID finds the main ID of the object.
func findMainID(t reflect.Type, v reflect.Value) reflect.Value {
	idField := v.FieldByName("ID")

	if idField.IsValid() {
		return reflect.Indirect(idField)
	}

	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)

		if field.Tag.Get("mainID") == "true" {
			return reflect.Indirect(v.Field(i))
		}
	}

	panic("Type " + t.Name() + " doesn't have a main ID!")
}
<|endoftext|>


<commit_before>package brokers_test

import (
	"testing"

	"errors"

	"github.com/RichardKnop/machinery/v1"
	"github.com/RichardKnop/machinery/v1/brokers"
	"github.com/RichardKnop/machinery/v1/config"
	"github.com/RichardKnop/machinery/v1/retry"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/stretchr/testify/assert"
)

var (
	testBroker           brokers.Interface
	testAWSSQSBroker     *brokers.AWSSQSBroker
	errAWSSQSBroker      *brokers.AWSSQSBroker
	cnf                  *config.Config
	receiveMessageOutput *sqs.ReceiveMessageOutput
)

func init() {
	testAWSSQSBroker = brokers.TestAWSSQSBroker
	errAWSSQSBroker = brokers.ErrAWSSQSBroker
	cnf = brokers.TestConf
	receiveMessageOutput = brokers.ReceiveMessageOutput
	testBroker = brokers.NewAWSSQSBroker(cnf)
}

func TestNewAWSSQSBroker(t *testing.T) {
	assert.IsType(t, testAWSSQSBroker, testBroker)
}

func TestPrivateFunc_continueReceivingMessages(t *testing.T) {
	qURL := testAWSSQSBroker.DefaultQueueURLForTest()
	deliveries := make(chan *sqs.ReceiveMessageOutput)
	nextStep := make(chan int)
	go func() {
		stopReceivingChan := testAWSSQSBroker.GetStopReceivingChanForTest()
		stopReceivingChan <- 1
	}()

	var (
		whetherContinue bool
		err             error
	)
	go func() {
		whetherContinue, err = testAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
		nextStep <- 1
	}()
	assert.False(t, whetherContinue)
	assert.Nil(t, err)

	<-nextStep
	whetherContinue, err = testAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
	assert.True(t, whetherContinue)
	assert.Nil(t, err)

	whetherContinue, err = errAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
	assert.True(t, whetherContinue)
	assert.NotNil(t, err)

	outputCopy := *receiveMessageOutput
	receiveMessageOutput.Messages = []*sqs.Message{}
	whetherContinue, err = testAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
	assert.True(t, whetherContinue)
	assert.Nil(t, err)
	// recover original value
	*receiveMessageOutput = outputCopy

}

func TestPrivateFunc_consume(t *testing.T) {
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}
	wk := server1.NewWorker("sms_worker", 0)
	deliveries := make(chan *sqs.ReceiveMessageOutput)
	outputCopy := *receiveMessageOutput
	outputCopy.Messages = []*sqs.Message{}
	go func() { deliveries <- &outputCopy }()

	// an infinite loop will be executed only when there is no error
	err = testAWSSQSBroker.ConsumeForTest(deliveries, 0, wk)
	assert.NotNil(t, err)

}

func TestPrivateFunc_consumeOne(t *testing.T) {
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}
	wk := server1.NewWorker("sms_worker", 0)
	err = testAWSSQSBroker.ConsumeOneForTest(receiveMessageOutput, wk)
	assert.Nil(t, err)

	outputCopy := *receiveMessageOutput
	outputCopy.Messages = []*sqs.Message{}
	err = testAWSSQSBroker.ConsumeOneForTest(&outputCopy, wk)
	assert.NotNil(t, err)

	outputCopy.Messages = []*sqs.Message{
		{
			Body: aws.String("foo message"),
		},
	}
	err = testAWSSQSBroker.ConsumeOneForTest(&outputCopy, wk)
	assert.NotNil(t, err)
}

func TestPrivateFunc_initializePool(t *testing.T) {
	concurrency := 9
	pool := make(chan struct{}, concurrency)
	testAWSSQSBroker.InitializePoolForTest(pool, concurrency)
	assert.Len(t, pool, concurrency)
}

func TestPrivateFunc_startConsuming(t *testing.T) {
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}

	wk := server1.NewWorker("sms_worker", 0)
	retryFunc := testAWSSQSBroker.GetRetryFuncForTest()
	stopChan := testAWSSQSBroker.GetStopChanForTest()
	retryStopChan := testAWSSQSBroker.GetRetryStopChanForTest()
	assert.Nil(t, retryFunc)
	testAWSSQSBroker.StartConsumingForTest("fooTag", wk)
	assert.IsType(t, retryFunc, retry.Closure())
	assert.Equal(t, len(stopChan), 0)
	assert.Equal(t, len(retryStopChan), 0)
}

func TestPrivateFuncDefaultQueueURL(t *testing.T) {
	qURL := testAWSSQSBroker.DefaultQueueURLForTest()

	assert.EqualValues(t, *qURL, "https://sqs.foo.amazonaws.com.cn/test_queue")
}

func TestPrivateFunc_stopReceiving(t *testing.T) {
	go testAWSSQSBroker.StopReceivingForTest()
	stopReceivingChan := testAWSSQSBroker.GetStopReceivingChanForTest()
	assert.NotNil(t, <-stopReceivingChan)
}

func TestPrivateFunc_receiveMessage(t *testing.T) {
	qURL := testAWSSQSBroker.DefaultQueueURLForTest()
	output, err := testAWSSQSBroker.ReceiveMessageForTest(qURL)
	assert.Nil(t, err)
	assert.Equal(t, receiveMessageOutput, output)
}

func TestPrivateFunc_consumeDeliveries(t *testing.T) {
	concurrency := 0
	pool := make(chan struct{}, concurrency)
	errorsChan := make(chan error)
	deliveries := make(chan *sqs.ReceiveMessageOutput)
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}
	wk := server1.NewWorker("sms_worker", 0)
	go func() { deliveries <- receiveMessageOutput }()
	whetherContinue, err := testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	assert.True(t, whetherContinue)
	assert.Nil(t, err)

	go func() { errorsChan <- errors.New("foo error") }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	assert.False(t, whetherContinue)
	assert.NotNil(t, err)

	go func() { testAWSSQSBroker.GetStopChanForTest() <- 1 }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	assert.False(t, whetherContinue)
	assert.Nil(t, err)

	outputCopy := *receiveMessageOutput
	outputCopy.Messages = []*sqs.Message{}
	go func() { deliveries <- &outputCopy }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	e := <-errorsChan
	assert.True(t, whetherContinue)
	assert.NotNil(t, e)
	assert.Nil(t, err)

	go func() { pool <- struct{}{} }()
	go func() { deliveries <- receiveMessageOutput }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	p := <-pool
	assert.True(t, whetherContinue)
	assert.NotNil(t, p)
	assert.Nil(t, err)
}

func TestPrivateFunc_deleteOne(t *testing.T) {
	err := testAWSSQSBroker.DeleteOneForTest(receiveMessageOutput)
	assert.Nil(t, err)

	err = errAWSSQSBroker.DeleteOneForTest(receiveMessageOutput)
	assert.NotNil(t, err)
}
<commit_msg>add more control on channel flow<commit_after>package brokers_test

import (
	"testing"

	"errors"

	"github.com/RichardKnop/machinery/v1"
	"github.com/RichardKnop/machinery/v1/brokers"
	"github.com/RichardKnop/machinery/v1/config"
	"github.com/RichardKnop/machinery/v1/retry"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/stretchr/testify/assert"
)

var (
	testBroker           brokers.Interface
	testAWSSQSBroker     *brokers.AWSSQSBroker
	errAWSSQSBroker      *brokers.AWSSQSBroker
	cnf                  *config.Config
	receiveMessageOutput *sqs.ReceiveMessageOutput
)

func init() {
	testAWSSQSBroker = brokers.TestAWSSQSBroker
	errAWSSQSBroker = brokers.ErrAWSSQSBroker
	cnf = brokers.TestConf
	receiveMessageOutput = brokers.ReceiveMessageOutput
	testBroker = brokers.NewAWSSQSBroker(cnf)
}

func TestNewAWSSQSBroker(t *testing.T) {
	assert.IsType(t, testAWSSQSBroker, testBroker)
}

func TestPrivateFunc_continueReceivingMessages(t *testing.T) {
	qURL := testAWSSQSBroker.DefaultQueueURLForTest()
	deliveries := make(chan *sqs.ReceiveMessageOutput)
	firstStep := make(chan int)
	nextStep := make(chan int)
	go func() {
		stopReceivingChan := testAWSSQSBroker.GetStopReceivingChanForTest()
		firstStep <- 1
		stopReceivingChan <- 1
	}()

	var (
		whetherContinue bool
		err             error
	)
	<-firstStep
	// Test the case that a signal was received from stopReceivingChan
	go func() {
		whetherContinue, err = testAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
		nextStep <- 1
	}()
	<-nextStep
	assert.False(t, whetherContinue)
	assert.Nil(t, err)

	// Test the default condition
	whetherContinue, err = testAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
	assert.True(t, whetherContinue)
	assert.Nil(t, err)

	// Test the error
	whetherContinue, err = errAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
	assert.True(t, whetherContinue)
	assert.NotNil(t, err)

	// Test when there is no message
	outputCopy := *receiveMessageOutput
	receiveMessageOutput.Messages = []*sqs.Message{}
	whetherContinue, err = testAWSSQSBroker.ContinueReceivingMessagesForTest(qURL, deliveries)
	assert.True(t, whetherContinue)
	assert.Nil(t, err)
	// recover original value
	*receiveMessageOutput = outputCopy

}

func TestPrivateFunc_consume(t *testing.T) {
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}
	wk := server1.NewWorker("sms_worker", 0)
	deliveries := make(chan *sqs.ReceiveMessageOutput)
	outputCopy := *receiveMessageOutput
	outputCopy.Messages = []*sqs.Message{}
	go func() { deliveries <- &outputCopy }()

	// an infinite loop will be executed only when there is no error
	err = testAWSSQSBroker.ConsumeForTest(deliveries, 0, wk)
	assert.NotNil(t, err)

}

func TestPrivateFunc_consumeOne(t *testing.T) {
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}
	wk := server1.NewWorker("sms_worker", 0)
	err = testAWSSQSBroker.ConsumeOneForTest(receiveMessageOutput, wk)
	assert.Nil(t, err)

	outputCopy := *receiveMessageOutput
	outputCopy.Messages = []*sqs.Message{}
	err = testAWSSQSBroker.ConsumeOneForTest(&outputCopy, wk)
	assert.NotNil(t, err)

	outputCopy.Messages = []*sqs.Message{
		{
			Body: aws.String("foo message"),
		},
	}
	err = testAWSSQSBroker.ConsumeOneForTest(&outputCopy, wk)
	assert.NotNil(t, err)
}

func TestPrivateFunc_initializePool(t *testing.T) {
	concurrency := 9
	pool := make(chan struct{}, concurrency)
	testAWSSQSBroker.InitializePoolForTest(pool, concurrency)
	assert.Len(t, pool, concurrency)
}

func TestPrivateFunc_startConsuming(t *testing.T) {
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}

	wk := server1.NewWorker("sms_worker", 0)
	retryFunc := testAWSSQSBroker.GetRetryFuncForTest()
	stopChan := testAWSSQSBroker.GetStopChanForTest()
	retryStopChan := testAWSSQSBroker.GetRetryStopChanForTest()
	assert.Nil(t, retryFunc)
	testAWSSQSBroker.StartConsumingForTest("fooTag", wk)
	assert.IsType(t, retryFunc, retry.Closure())
	assert.Equal(t, len(stopChan), 0)
	assert.Equal(t, len(retryStopChan), 0)
}

func TestPrivateFuncDefaultQueueURL(t *testing.T) {
	qURL := testAWSSQSBroker.DefaultQueueURLForTest()

	assert.EqualValues(t, *qURL, "https://sqs.foo.amazonaws.com.cn/test_queue")
}

func TestPrivateFunc_stopReceiving(t *testing.T) {
	go testAWSSQSBroker.StopReceivingForTest()
	stopReceivingChan := testAWSSQSBroker.GetStopReceivingChanForTest()
	assert.NotNil(t, <-stopReceivingChan)
}

func TestPrivateFunc_receiveMessage(t *testing.T) {
	qURL := testAWSSQSBroker.DefaultQueueURLForTest()
	output, err := testAWSSQSBroker.ReceiveMessageForTest(qURL)
	assert.Nil(t, err)
	assert.Equal(t, receiveMessageOutput, output)
}

func TestPrivateFunc_consumeDeliveries(t *testing.T) {
	concurrency := 0
	pool := make(chan struct{}, concurrency)
	errorsChan := make(chan error)
	deliveries := make(chan *sqs.ReceiveMessageOutput)
	server1, err := machinery.NewServer(cnf)
	if err != nil {
		t.Fatal(err)
	}
	wk := server1.NewWorker("sms_worker", 0)
	go func() { deliveries <- receiveMessageOutput }()
	whetherContinue, err := testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	assert.True(t, whetherContinue)
	assert.Nil(t, err)

	go func() { errorsChan <- errors.New("foo error") }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	assert.False(t, whetherContinue)
	assert.NotNil(t, err)

	go func() { testAWSSQSBroker.GetStopChanForTest() <- 1 }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	assert.False(t, whetherContinue)
	assert.Nil(t, err)

	outputCopy := *receiveMessageOutput
	outputCopy.Messages = []*sqs.Message{}
	go func() { deliveries <- &outputCopy }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	e := <-errorsChan
	assert.True(t, whetherContinue)
	assert.NotNil(t, e)
	assert.Nil(t, err)

	go func() { pool <- struct{}{} }()
	go func() { deliveries <- receiveMessageOutput }()
	whetherContinue, err = testAWSSQSBroker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)
	p := <-pool
	assert.True(t, whetherContinue)
	assert.NotNil(t, p)
	assert.Nil(t, err)
}

func TestPrivateFunc_deleteOne(t *testing.T) {
	err := testAWSSQSBroker.DeleteOneForTest(receiveMessageOutput)
	assert.Nil(t, err)

	err = errAWSSQSBroker.DeleteOneForTest(receiveMessageOutput)
	assert.NotNil(t, err)
}
<|endoftext|>


<commit_before>package builder

import (
	"context"
	"fmt"
	"strings"

	"github.com/docker/cli/cli"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/opts"
	"github.com/docker/docker/api/types"
	units "github.com/docker/go-units"
	"github.com/spf13/cobra"
)

type pruneOptions struct {
	force       bool
	all         bool
	filter      opts.FilterOpt
	keepStorage opts.MemBytes
}

// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
	options := pruneOptions{filter: opts.NewFilterOpt()}

	cmd := &cobra.Command{
		Use:   "prune",
		Short: "Remove build cache",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			spaceReclaimed, output, err := runPrune(dockerCli, options)
			if err != nil {
				return err
			}
			if output != "" {
				fmt.Fprintln(dockerCli.Out(), output)
			}
			fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
			return nil
		},
		Annotations: map[string]string{"version": "1.39"},
	}

	flags := cmd.Flags()
	flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
	flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
	flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'max-age=24h')")
	flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")

	return cmd
}

const (
	normalWarning   = `WARNING! This will remove all dangling build cache. Are you sure you want to continue?`
	allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
)

func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
	pruneFilters := options.filter.Value()
	pruneFilters = command.PruneFilters(dockerCli, pruneFilters)

	warning := normalWarning
	if options.all {
		warning = allCacheWarning
	}
	if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
		return 0, "", nil
	}

	report, err := dockerCli.Client().BuildCachePrune(context.Background(), types.BuildCachePruneOptions{
		All:         options.all,
		KeepStorage: options.keepStorage.Value(),
		Filters:     pruneFilters,
	})
	if err != nil {
		return 0, "", err
	}

	if len(report.CachesDeleted) > 0 {
		var sb strings.Builder
		sb.WriteString("Deleted build cache objects:\n")
		for _, id := range report.CachesDeleted {
			sb.WriteString(id)
			sb.WriteByte('\n')
		}
		output = sb.String()
	}

	return report.SpaceReclaimed, output, nil
}

// CachePrune executes a prune command for build cache
func CachePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
	return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
}
<commit_msg>builder/prune: rename max-age filter to unused-for in help output<commit_after>package builder

import (
	"context"
	"fmt"
	"strings"

	"github.com/docker/cli/cli"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/opts"
	"github.com/docker/docker/api/types"
	units "github.com/docker/go-units"
	"github.com/spf13/cobra"
)

type pruneOptions struct {
	force       bool
	all         bool
	filter      opts.FilterOpt
	keepStorage opts.MemBytes
}

// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
	options := pruneOptions{filter: opts.NewFilterOpt()}

	cmd := &cobra.Command{
		Use:   "prune",
		Short: "Remove build cache",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			spaceReclaimed, output, err := runPrune(dockerCli, options)
			if err != nil {
				return err
			}
			if output != "" {
				fmt.Fprintln(dockerCli.Out(), output)
			}
			fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
			return nil
		},
		Annotations: map[string]string{"version": "1.39"},
	}

	flags := cmd.Flags()
	flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
	flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
	flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'unused-for=24h')")
	flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")

	return cmd
}

const (
	normalWarning   = `WARNING! This will remove all dangling build cache. Are you sure you want to continue?`
	allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
)

func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
	pruneFilters := options.filter.Value()
	pruneFilters = command.PruneFilters(dockerCli, pruneFilters)

	warning := normalWarning
	if options.all {
		warning = allCacheWarning
	}
	if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
		return 0, "", nil
	}

	report, err := dockerCli.Client().BuildCachePrune(context.Background(), types.BuildCachePruneOptions{
		All:         options.all,
		KeepStorage: options.keepStorage.Value(),
		Filters:     pruneFilters,
	})
	if err != nil {
		return 0, "", err
	}

	if len(report.CachesDeleted) > 0 {
		var sb strings.Builder
		sb.WriteString("Deleted build cache objects:\n")
		for _, id := range report.CachesDeleted {
			sb.WriteString(id)
			sb.WriteByte('\n')
		}
		output = sb.String()
	}

	return report.SpaceReclaimed, output, nil
}

// CachePrune executes a prune command for build cache
func CachePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
	return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package exporter

import (
	"expvar"
	"flag"
	"fmt"

	"github.com/google/mtail/metrics"
)

var (
	statsdHostPort = flag.String("statsd_hostport", "",
		"Host:port to statsd server to write metrics to.")

	statsdExportTotal   = expvar.NewInt("statsd_export_total")
	statsdExportSuccess = expvar.NewInt("statsd_export_success")
)

func metricToStatsd(hostname string, m *metrics.Metric, l *metrics.LabelSet) string {
	// TODO(jaq): handle units better, send timing as |ms
	m.RLock()
	defer m.RUnlock()
	return fmt.Sprintf("%s.%s:%d|c",
		m.Program,
		formatLabels(m.Name, l.Labels, ".", "."),
		l.Datum.Get())
}
<commit_msg>Add a todo for statsd gauges.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package exporter

import (
	"expvar"
	"flag"
	"fmt"

	"github.com/google/mtail/metrics"
)

var (
	statsdHostPort = flag.String("statsd_hostport", "",
		"Host:port to statsd server to write metrics to.")

	statsdExportTotal   = expvar.NewInt("statsd_export_total")
	statsdExportSuccess = expvar.NewInt("statsd_export_success")
)

func metricToStatsd(hostname string, m *metrics.Metric, l *metrics.LabelSet) string {
	// TODO(jaq): handle units better, send timing as |ms
	m.RLock()
	defer m.RUnlock()
	// TODO(jaq): handle gauge types
	return fmt.Sprintf("%s.%s:%d|c",
		m.Program,
		formatLabels(m.Name, l.Labels, ".", "."),
		l.Datum.Get())
}
<|endoftext|>


<commit_before>package berlingo

import (
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

func do(ai AI, r io.Reader) (response *Response, response_json []byte, err error) {

	game, err := NewGame(ai, r)
	if err != nil {
		return nil, nil, err
	}

	game.Do()

	response_json, err = game.Response.ToJson()
	if err != nil {
		return nil, nil, err
	}

	return response, response_json, nil
}

// Callback used to process an incoming HTTP request
func serveHttpRequest(ai AI, w http.ResponseWriter, r *http.Request) {

	log.Printf("HTTP: [%v] Processing %v %v", r.RemoteAddr, r.Method, r.RequestURI)
	w.Header().Set("Content-Type", "application/json")

	var input io.Reader
	content_type := r.Header.Get("Content-Type")
	switch {
	case r.Method == "POST" && content_type == "application/json":
		input = r.Body
	case r.Method == "POST" && content_type == "application/x-www-form-urlencoded":
		// Detect & work-around bug https://github.com/thirdside/berlin-ai/issues/4
		r.ParseForm()
		j := `{
				"action": "` + r.Form["action"][0] + `",
				"infos": ` + r.Form["infos"][0] + `,
				"map": ` + r.Form["map"][0] + `,
				"state": ` + r.Form["state"][0] + `
			}`
		input = strings.NewReader(j)
	default:
		log.Printf("HTTP: Replying with error")
		w.Write([]byte(`{"error": "Invalid request"}`))
		return
	}

	_, response_json, err := do(ai, input)
	if err != nil {
		log.Printf("HTTP: Responding with error: %+v\n", err)
		w.Write([]byte("Error"))
	} else {
		log.Printf("HTTP: Responding with moves\n")
		w.Write(response_json)
	}

}

// ServeHttp serves the given AI over HTTP on the given port
func ServeHttp(ai AI, port string) {

	log.Println("Starting HTTP server on port", port)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		serveHttpRequest(ai, w, r)
	})

	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Println("HTTP Serving Error:", err)
	}

}

// ServeHttp serves the given AI a single time
// Request is read from the given filename
// filename may be supplied as "-" to indicate STDIN
func ServeFile(ai AI, filename string) {

	var fh *os.File
	var err error

	if filename == "-" {
		fh = os.Stdin
	} else {
		fh, err = os.Open(filename)
		if err != nil {
			log.Println("Error opening", filename, ": ", err)
			return
		}
		defer fh.Close()
	}

	_, response_json, err := do(ai, fh)
	if err != nil {
		log.Println("Error processing request:", err)
		return
	}
	os.Stdout.Write(response_json)
}

// Serve will inspect the CLI arguments and automatically call either ServeHttp or ServeFile
func Serve(ai AI) {

	port_or_filename := "-"
	if len(os.Args) >= 2 {
		port_or_filename = os.Args[1]
	}

	_, err := strconv.Atoi(port_or_filename)
	if err == nil {
		ServeHttp(ai, port_or_filename)
	} else {
		ServeFile(ai, port_or_filename)
	}
}
<commit_msg>Adding InitAppEngine<commit_after>package berlingo

import (
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

func do(ai AI, r io.Reader) (response *Response, response_json []byte, err error) {

	game, err := NewGame(ai, r)
	if err != nil {
		return nil, nil, err
	}

	game.Do()

	response_json, err = game.Response.ToJson()
	if err != nil {
		return nil, nil, err
	}

	return response, response_json, nil
}

// Callback used to process an incoming HTTP request
func serveHttpRequest(ai AI, w http.ResponseWriter, r *http.Request) {

	log.Printf("HTTP: [%v] Processing %v %v", r.RemoteAddr, r.Method, r.RequestURI)
	w.Header().Set("Content-Type", "application/json")

	var input io.Reader
	content_type := r.Header.Get("Content-Type")
	switch {
	case r.Method == "POST" && content_type == "application/json":
		input = r.Body
	case r.Method == "POST" && content_type == "application/x-www-form-urlencoded":
		// Detect & work-around bug https://github.com/thirdside/berlin-ai/issues/4
		r.ParseForm()
		j := `{
				"action": "` + r.Form["action"][0] + `",
				"infos": ` + r.Form["infos"][0] + `,
				"map": ` + r.Form["map"][0] + `,
				"state": ` + r.Form["state"][0] + `
			}`
		input = strings.NewReader(j)
	default:
		log.Printf("HTTP: Replying with error")
		w.Write([]byte(`{"error": "Invalid request"}`))
		return
	}

	_, response_json, err := do(ai, input)
	if err != nil {
		log.Printf("HTTP: Responding with error: %+v\n", err)
		w.Write([]byte("Error"))
	} else {
		log.Printf("HTTP: Responding with moves\n")
		w.Write(response_json)
	}

}

func InitAppEngine(ai AI) {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		serveHttpRequest(ai, w, r)
	})
}

// ServeHttp serves the given AI over HTTP on the given port
func ServeHttp(ai AI, port string) {

	log.Println("Starting HTTP server on port", port)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		serveHttpRequest(ai, w, r)
	})

	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Println("HTTP Serving Error:", err)
	}

}

// ServeHttp serves the given AI a single time
// Request is read from the given filename
// filename may be supplied as "-" to indicate STDIN
func ServeFile(ai AI, filename string) {

	var fh *os.File
	var err error

	if filename == "-" {
		fh = os.Stdin
	} else {
		fh, err = os.Open(filename)
		if err != nil {
			log.Println("Error opening", filename, ": ", err)
			return
		}
		defer fh.Close()
	}

	_, response_json, err := do(ai, fh)
	if err != nil {
		log.Println("Error processing request:", err)
		return
	}
	os.Stdout.Write(response_json)
}

// Serve will inspect the CLI arguments and automatically call either ServeHttp or ServeFile
func Serve(ai AI) {

	port_or_filename := "-"
	if len(os.Args) >= 2 {
		port_or_filename = os.Args[1]
	}

	_, err := strconv.Atoi(port_or_filename)
	if err == nil {
		ServeHttp(ai, port_or_filename)
	} else {
		ServeFile(ai, port_or_filename)
	}
}
<|endoftext|>


<commit_before>package rfm69

import (
	"bytes"
	"time"

	"github.com/ecc1/gpio"
	"github.com/ecc1/medtronic/radio"
)

const (
	spiSpeed  = 10000000 // Hz
	resetPin  = 12       // Intel Edison GPIO for hardware reset
	hwVersion = 0x0204
)

type flavor struct{}

func (hw flavor) Name() string {
	return "RFM69HCW"
}

func (hw flavor) Speed() int {
	return spiSpeed
}

func (hw flavor) ReadSingleAddress(addr byte) byte {
	return addr
}

func (hw flavor) ReadBurstAddress(addr byte) byte {
	return addr
}

func (hw flavor) WriteSingleAddress(addr byte) byte {
	return SpiWriteMode | addr
}

func (hw flavor) WriteBurstAddress(addr byte) byte {
	return SpiWriteMode | addr
}

type Radio struct {
	hw            *radio.Hardware
	resetPin      gpio.OutputPin
	receiveBuffer bytes.Buffer
	stats         radio.Statistics
	err           error
}

func Open() radio.Interface {
	r := &Radio{hw: radio.Open(flavor{})}
	v := r.Version()
	if r.Error() != nil {
		return r
	}
	if v != hwVersion {
		r.hw.Close()
		r.SetError(radio.HardwareVersionError{Actual: v, Expected: hwVersion})
		return r
	}
	r.resetPin, r.err = gpio.Output(resetPin, false)
	if r.Error() != nil {
		r.hw.Close()
		return r
	}
	return r
}

func (r *Radio) Version() uint16 {
	v := r.hw.ReadRegister(RegVersion)
	return uint16(v>>4)<<8 | uint16(v&0xF)
}

// Reset module.  See section 7.2.2 of data sheet.
func (r *Radio) Reset() {
	if r.Error() != nil {
		return
	}
	r.err = r.resetPin.Write(true)
	if r.Error() != nil {
		r.resetPin.Write(false)
		return
	}
	time.Sleep(100 * time.Microsecond)
	r.err = r.resetPin.Write(false)
	if r.Error() != nil {
		return
	}
	time.Sleep(5 * time.Millisecond)
}

func (r *Radio) Init(frequency uint32) {
	r.Reset()
	r.InitRF(frequency)
}

func (r *Radio) Statistics() radio.Statistics {
	return r.stats
}

func (r *Radio) Error() error {
	err := r.hw.Error()
	if err != nil {
		return err
	}
	return r.err
}

func (r *Radio) SetError(err error) {
	r.hw.SetError(err)
	r.err = err
}

func (r *Radio) Hardware() *radio.Hardware {
	return r.hw
}
<commit_msg>Lower SPI speed to avoid errors on Edison 3.5 kernel<commit_after>package rfm69

import (
	"bytes"
	"time"

	"github.com/ecc1/gpio"
	"github.com/ecc1/medtronic/radio"
)

const (
	spiSpeed  = 6000000 // Hz
	resetPin  = 12      // Intel Edison GPIO for hardware reset
	hwVersion = 0x0204
)

type flavor struct{}

func (hw flavor) Name() string {
	return "RFM69HCW"
}

func (hw flavor) Speed() int {
	return spiSpeed
}

func (hw flavor) ReadSingleAddress(addr byte) byte {
	return addr
}

func (hw flavor) ReadBurstAddress(addr byte) byte {
	return addr
}

func (hw flavor) WriteSingleAddress(addr byte) byte {
	return SpiWriteMode | addr
}

func (hw flavor) WriteBurstAddress(addr byte) byte {
	return SpiWriteMode | addr
}

type Radio struct {
	hw            *radio.Hardware
	resetPin      gpio.OutputPin
	receiveBuffer bytes.Buffer
	stats         radio.Statistics
	err           error
}

func Open() radio.Interface {
	r := &Radio{hw: radio.Open(flavor{})}
	v := r.Version()
	if r.Error() != nil {
		return r
	}
	if v != hwVersion {
		r.hw.Close()
		r.SetError(radio.HardwareVersionError{Actual: v, Expected: hwVersion})
		return r
	}
	r.resetPin, r.err = gpio.Output(resetPin, false)
	if r.Error() != nil {
		r.hw.Close()
		return r
	}
	return r
}

func (r *Radio) Version() uint16 {
	v := r.hw.ReadRegister(RegVersion)
	return uint16(v>>4)<<8 | uint16(v&0xF)
}

// Reset module.  See section 7.2.2 of data sheet.
func (r *Radio) Reset() {
	if r.Error() != nil {
		return
	}
	r.err = r.resetPin.Write(true)
	if r.Error() != nil {
		r.resetPin.Write(false)
		return
	}
	time.Sleep(100 * time.Microsecond)
	r.err = r.resetPin.Write(false)
	if r.Error() != nil {
		return
	}
	time.Sleep(5 * time.Millisecond)
}

func (r *Radio) Init(frequency uint32) {
	r.Reset()
	r.InitRF(frequency)
}

func (r *Radio) Statistics() radio.Statistics {
	return r.stats
}

func (r *Radio) Error() error {
	err := r.hw.Error()
	if err != nil {
		return err
	}
	return r.err
}

func (r *Radio) SetError(err error) {
	r.hw.SetError(err)
	r.err = err
}

func (r *Radio) Hardware() *radio.Hardware {
	return r.hw
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/guardduty"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsGuardDutyMember() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsGuardDutyMemberCreate,
		Read:   resourceAwsGuardDutyMemberRead,
		Delete: resourceAwsGuardDutyMemberDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"account_id": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateAwsAccountId,
			},
			"detector_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"email": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"invite": {
				Type: schema.TypeBool,
				Required: false,
			},
		},
	}
}

func resourceAwsGuardDutyMemberCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).guarddutyconn
	accountID := d.Get("account_id").(string)
	detectorID := d.Get("detector_id").(string)

	input := guardduty.CreateMembersInput{
		AccountDetails: []*guardduty.AccountDetail{{
			AccountId: aws.String(accountID),
			Email:     aws.String(d.Get("email").(string)),
		}},
		DetectorId: aws.String(detectorID),
	}

	log.Printf("[DEBUG] Creating GuardDuty Member: %s", input)
	_, err := conn.CreateMembers(&input)
	if err != nil {
		return fmt.Errorf("Creating GuardDuty Member failed: %s", err.Error())
	}
	d.SetId(fmt.Sprintf("%s:%s", detectorID, accountID))

	imi := &guardduty.InviteMembersInput{
		DetectorId: &detectorID,
		AccountIds: []*string{&accountID},
		Message: aws.String(d.Get("message").(string)),
	}

	_, err = conn.InviteMembers(imi)

	if err != nil {
		return fmt.Errorf("Inviting GuardDuty Member failed: %s", err.Error())
	}

	return resourceAwsGuardDutyMemberRead(d, meta)
}

func resourceAwsGuardDutyMemberRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).guarddutyconn

	accountID, detectorID, err := decodeGuardDutyMemberID(d.Id())
	if err != nil {
		return err
	}

	input := guardduty.GetMembersInput{
		AccountIds: []*string{aws.String(accountID)},
		DetectorId: aws.String(detectorID),
	}

	log.Printf("[DEBUG] Reading GuardDuty Member: %s", input)
	gmo, err := conn.GetMembers(&input)
	if err != nil {
		if isAWSErr(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") {
			log.Printf("[WARN] GuardDuty detector %q not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Reading GuardDuty Member '%s' failed: %s", d.Id(), err.Error())
	}

	if gmo.Members == nil || (len(gmo.Members) < 1) {
		log.Printf("[WARN] GuardDuty Member %q not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}
	member := gmo.Members[0]
	d.Set("account_id", member.AccountId)
	d.Set("detector_id", detectorID)
	d.Set("email", member.Email)
	d.Set("invite", member.RelationshipStatus == aws.String("INVITED"))

	return nil
}

func resourceAwsGuardDutyMemberDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).guarddutyconn

	accountID, detectorID, err := decodeGuardDutyMemberID(d.Id())
	if err != nil {
		return err
	}

	input := guardduty.DeleteMembersInput{
		AccountIds: []*string{aws.String(accountID)},
		DetectorId: aws.String(detectorID),
	}

	log.Printf("[DEBUG] Delete GuardDuty Member: %s", input)
	_, err = conn.DeleteMembers(&input)
	if err != nil {
		return fmt.Errorf("Deleting GuardDuty Member '%s' failed: %s", d.Id(), err.Error())
	}
	return nil
}

func decodeGuardDutyMemberID(id string) (accountID, detectorID string, err error) {
	parts := strings.Split(id, ":")
	if len(parts) != 2 {
		err = fmt.Errorf("GuardDuty Member ID must be of the form <Detector ID>:<Member AWS Account ID>, was provided: %s", id)
		return
	}
	accountID = parts[1]
	detectorID = parts[0]
	return
}
<commit_msg>Add lines to wait for email verification progress finishes<commit_after>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/guardduty"
	"github.com/hashicorp/terraform/helper/schema"
	"time"
	"github.com/hashicorp/terraform/helper/resource"
)

func resourceAwsGuardDutyMember() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsGuardDutyMemberCreate,
		Read:   resourceAwsGuardDutyMemberRead,
		Delete: resourceAwsGuardDutyMemberDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"account_id": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateAwsAccountId,
			},
			"detector_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"email": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"relationshipStatus": {
				Type: schema.TypeString,
				Computed: true,
			},
			"invite": {
				Type: schema.TypeBool,
				Description: "Indicate whether to invite the account",
				Default: true,
				Optional: true,
			},
			"invitation_message": {
				Type:     schema.TypeString,
				Optional: true,
			},
		},
		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(1 * time.Minute),
		},
	}
}

func resourceAwsGuardDutyMemberCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).guarddutyconn
	accountID := d.Get("account_id").(string)
	detectorID := d.Get("detector_id").(string)

	input := guardduty.CreateMembersInput{
		AccountDetails: []*guardduty.AccountDetail{{
			AccountId: aws.String(accountID),
			Email:     aws.String(d.Get("email").(string)),
		}},
		DetectorId: aws.String(detectorID),
	}

	log.Printf("[DEBUG] Creating GuardDuty Member: %s", input)
	_, err := conn.CreateMembers(&input)
	if err != nil {
		return fmt.Errorf("Creating GuardDuty Member failed: %s", err.Error())
	}

	d.SetId(fmt.Sprintf("%s:%s", detectorID, accountID))

	if !d.Get("invite").(bool) {
		return resourceAwsGuardDutyMemberRead(d, meta)
	}

	imi := &guardduty.InviteMembersInput{
		DetectorId: &detectorID,
		AccountIds: []*string{&accountID},
		Message:    aws.String(d.Get("message").(string)),
	}

	_, err = conn.InviteMembers(imi)
	if err != nil {
		return fmt.Errorf("Inviting GuardDuty Member failed: %s", err.Error())
	}

	// wait until e-mail verification finishes
	return resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
		if d.Get("relationshipStatus").(string) != "EmailVerificationInProgress" {
			return nil
		}

		log.Printf("[INFO] Email verification for %s is still in progress", accountID)
		return resource.NonRetryableError(resourceAwsGuardDutyMemberRead(d, meta))
	})
}

func resourceAwsGuardDutyMemberRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).guarddutyconn

	accountID, detectorID, err := decodeGuardDutyMemberID(d.Id())
	if err != nil {
		return err
	}

	input := guardduty.GetMembersInput{
		AccountIds: []*string{aws.String(accountID)},
		DetectorId: aws.String(detectorID),
	}

	log.Printf("[DEBUG] Reading GuardDuty Member: %s", input)
	gmo, err := conn.GetMembers(&input)
	if err != nil {
		if isAWSErr(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") {
			log.Printf("[WARN] GuardDuty detector %q not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Reading GuardDuty Member '%s' failed: %s", d.Id(), err.Error())
	}

	if gmo.Members == nil || (len(gmo.Members) < 1) {
		log.Printf("[WARN] GuardDuty Member %q not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	member := gmo.Members[0]
	d.Set("account_id", member.AccountId)
	d.Set("detector_id", detectorID)
	d.Set("email", member.Email)
	d.Set("relationshipStatus", member.RelationshipStatus)

	return nil
}

func resourceAwsGuardDutyMemberDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).guarddutyconn

	accountID, detectorID, err := decodeGuardDutyMemberID(d.Id())
	if err != nil {
		return err
	}

	input := guardduty.DeleteMembersInput{
		AccountIds: []*string{aws.String(accountID)},
		DetectorId: aws.String(detectorID),
	}

	log.Printf("[DEBUG] Delete GuardDuty Member: %s", input)
	_, err = conn.DeleteMembers(&input)
	if err != nil {
		return fmt.Errorf("Deleting GuardDuty Member '%s' failed: %s", d.Id(), err.Error())
	}
	return nil
}

func decodeGuardDutyMemberID(id string) (accountID, detectorID string, err error) {
	parts := strings.Split(id, ":")
	if len(parts) != 2 {
		err = fmt.Errorf("GuardDuty Member ID must be of the form <Detector ID>:<Member AWS Account ID>, was provided: %s", id)
		return
	}
	accountID = parts[1]
	detectorID = parts[0]
	return
}
<|endoftext|>