<commit_before>/*

Package freeze enables the "freezing" of data, similar to JavaScript's
Object.freeze(). A frozen object cannot be modified; attempting to do so will
result in an unrecoverable panic.

Freezing is useful for providing soft guarantees of immutability. That is: the
compiler can't prevent you from mutating an frozen object, but the runtime
can. One of the unfortunate aspects of Go is its limited support for
constants: structs, slices, and even arrays cannot be declared as consts. This
becomes a problem when you want to pass a slice around to many consumers
without worrying about them modifying it. With freeze, you can guard against
these unwanted or intended behaviors.

To accomplish this, the mprotect syscall is used. Sadly, this necessitates
allocating new memory via mmap and copying the data into it. This performance
penalty should not be prohibitive, but it's something to be aware of.

In case it wasn't clear from the previous paragraph, this package is not
intended to be used in production. A well-designed API is a much saner
solution than freezing your data structures. I would even caution against
using freeze in your automated testing, due to its platform-specific nature.
freeze is best used for "one-off" debugging. Something like this:

1. Observe bug
2. Suspect that shared mutable data is the culprit
3. Call freeze.Object on the data after it is created
4. Run program again; it crashes
5. Inspect stack trace to identify where the data was modified
6. Fix bug
7. Remove call to freeze.Object

Again: do not use freeze in production. It's a cool proof-of-concept, and it
can be useful for debugging, but that's about it. Let me put it another way:
freeze imports four packages: reflect, runtime, unsafe, and syscall (actually
golang.org/x/sys/unix). Does that sound like a package you want to depend on?

Okay, back to the real documention:

Functions are provided for freezing the three "pointer types:" Pointer, Slice,
and Map. Each function returns a copy of their input that is backed by
protected memory. In addition, Object is provided for freezing recursively.
Given a slice of pointers, Object will prevent modifications to both the
pointer data and the slice data, while Slice merely does the latter.

To freeze an object:

	type foo struct {
		X int
		y bool // yes, freeze works on unexported fields!
	}
	f := &foo{3, true}
	f = freeze.Object(f).(*foo)
	println(f.X) // ok; prints 3
	f.X++        // not ok; panics

Note that since foo does not contain any pointers, calling Pointer(f) would
have the same effect here.

It is recommended that, where convenient, you reassign the return value to its
original variable, as with append. Otherwise, you will retain both the mutable
original and the frozen copy.

Likewise, to freeze a slice:

	xs := []int{1, 2, 3}
	xs = freeze.Slice(xs).([]int)
	println(xs[0]) // ok; prints 1
	xs[0]++        // not ok; panics

Interfaces can also be frozen, since internally they are just pointers to
objects. The effect of this is that the interface's pure methods can still be
called, but impure methods cannot. Unfortunately the impurity of a given
method is defined by the implementation, not the interface. Even a String
method could conceivably modify some internal state. Furthermore, the caveat
about unexported struct fields (see below) applies here, so many exported
objects cannot be completely frozen.

Caveats

This package depends heavily on the internal representations of the slice and
map types. These objects are not likely to change, but if they do, this
package will break.

In general, you can't call Object on the same object twice. This is because
Object will attempt to rewrite the object's internal pointers -- which is a
memory modification. Calling Pointer or Slice twice should be fine.

Object cannot descend into unexported struct fields. It can still freeze the
field itself, but if the field contains a pointer, the data it points to will
not be frozen.

Appending to a frozen slice will trigger a panic iff len(slice) < cap(slice).
This is because appending to a full slice will allocate new memory.

Unix is the only supported platform. Windows support is not planned, because
it doesn't support a syscall analogous to mprotect.
*/
package freeze

import (
	"reflect"
	"runtime"
	"unsafe"

	"golang.org/x/sys/unix"
)

// Pointer returns a frozen copy of v, which must be a pointer. Future writes
// to the copy's memory will result in a panic. In most cases, the copy should
// be reassigned to v.
func Pointer(v interface{}) interface{} {
	if v == nil {
		return v
	}
	typ := reflect.TypeOf(v)
	if typ.Kind() != reflect.Ptr {
		panic("Pointer called on non-pointer type")
	}

	// freeze the memory pointed to by the interface's data pointer
	size := typ.Elem().Size()
	ptrs := (*[2]uintptr)(unsafe.Pointer(&v))
	ptrs[1] = copyAndFreeze(ptrs[1], size)

	return v
}

// Slice returns a frozen copy of v, which must be a slice. Future writes to
// the copy's memory will result in a panic. In most cases, the copy should be
// reassigned to v.
func Slice(v interface{}) interface{} {
	if v == nil {
		return v
	}
	val := reflect.ValueOf(v)
	if val.Kind() != reflect.Slice {
		panic("Slice called on non-slice type")
	}

	// freeze the memory pointed to by the slice's data pointer
	size := val.Type().Elem().Size() * uintptr(val.Len())
	slice := (*[3]uintptr)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1]) // should be [2]uintptr, but go vet complains
	slice[0] = copyAndFreeze(slice[0], size)

	return v
}

// Map returns a frozen copy of v, which must be a map. Future writes to
// the copy's memory will result in a panic. In most cases, the copy should be
// reassigned to v. Note that both the keys and values of the map are frozen.
func Map(v interface{}) interface{} {
	if v == nil {
		return v
	}
	typ := reflect.TypeOf(v)
	if typ.Kind() != reflect.Map {
		panic("Map called on non-map type")
	}

	// copied from runtime/hmap.go
	type hmap struct {
		count      int
		flags      uint8
		B          uint8
		hash0      uint32
		buckets    uintptr
		oldbuckets uintptr
		nevacuate  uintptr
		overflow   *[2]*[]uintptr
	}

	// convert v to a mapType so we can access 'B' and 'buckets'
	m := (*hmap)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1])

	// copied from reflect/type.go
	bucketSize := 8*(1+typ.Key().Size()+typ.Elem().Size()) + unsafe.Sizeof(uintptr(0))
	// size of map's bucket data is 2^B * bucketSize
	size := (uintptr(1) << m.B) * bucketSize

	// freeze the map's buckets
	m.buckets = copyAndFreeze(m.buckets, size)

	return v
}

// Object returns a recursively frozen copy of v, which must be a pointer or a
// slice. It will descend into pointers, arrays, slices, and structs until
// "bottoming out," freezing the entire chain. Passing a cyclic structure to
// Object will result in infinite recursion. Note that Object can only descend
// into exported struct fields (the fields themselves will still be frozen).
func Object(v interface{}) interface{} {
	if v == nil {
		return v
	}
	val := reflect.ValueOf(v)
	switch val.Kind() {
	case reflect.Ptr, reflect.Slice, reflect.Map:
		return object(val).Interface()
	}
	panic("Object called on invalid type")
}

// object updates all pointers in val to point to frozen memory containing the
// same data.
func object(val reflect.Value) reflect.Value {
	// we only need to recurse into types that might have pointers
	hasPtrs := func(t reflect.Type) bool {
		switch t.Kind() {
		case reflect.Ptr, reflect.Array, reflect.Slice, reflect.Map, reflect.Struct:
			return true
		}
		return false
	}

	switch val.Type().Kind() {
	default:
		return val

	case reflect.Ptr:
		if val.IsNil() {
			return val
		} else if hasPtrs(val.Type().Elem()) {
			val.Elem().Set(object(val.Elem()))
		}
		return reflect.ValueOf(Pointer(val.Interface()))

	case reflect.Array:
		if hasPtrs(val.Type().Elem()) {
			for i := 0; i < val.Len(); i++ {
				val.Index(i).Set(object(val.Index(i)))
			}
		}
		return val

	case reflect.Slice:
		if hasPtrs(val.Type().Elem()) {
			for i := 0; i < val.Len(); i++ {
				val.Index(i).Set(object(val.Index(i)))
			}
		}
		return reflect.ValueOf(Slice(val.Interface()))

	case reflect.Map:
		if hasPtrs(val.Type().Elem()) || hasPtrs(val.Type().Key()) {
			newMap := reflect.MakeMap(val.Type())
			for _, key := range val.MapKeys() {
				newMap.SetMapIndex(object(key), object(val.MapIndex(key)))
			}
			val = newMap
		}
		return reflect.ValueOf(Map(val.Interface()))

	case reflect.Struct:
		for i := 0; i < val.NumField(); i++ {
			// can't recurse into unexported fields
			t := val.Type().Field(i)
			if !(t.PkgPath != "" && !t.Anonymous) && hasPtrs(t.Type) {
				val.Field(i).Set(object(val.Field(i)))
			}
		}
		return val
	}
}

// copyAndFreeze copies n bytes from dataptr into new memory, freezes it, and
// returns a uintptr to the new memory.
func copyAndFreeze(dataptr, n uintptr) uintptr {
	if dataptr == 0 || n == 0 {
		return dataptr
	}
	// allocate new memory to be frozen
	newMem, err := unix.Mmap(-1, 0, int(n), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE)
	if err != nil {
		panic(err)
	}
	// set a finalizer to unmap the memory when it would normally be GC'd
	runtime.SetFinalizer(&newMem, func(b *[]byte) { _ = unix.Munmap(*b) })

	// copy n bytes into newMem
	copy(newMem, *(*[]byte)(unsafe.Pointer(&[3]uintptr{dataptr, n, n})))

	// freeze the new memory
	if err = unix.Mprotect(newMem, unix.PROT_READ); err != nil {
		panic(err)
	}

	// return pointer to new memory
	return uintptr(unsafe.Pointer(&newMem[0]))
}
<commit_msg>use reflect.SliceHeader instead of [3]uintptr<commit_after>/*
Package freeze enables the "freezing" of data, similar to JavaScript's
Object.freeze(). A frozen object cannot be modified; attempting to do so will
result in an unrecoverable panic.

Freezing is useful for providing soft guarantees of immutability. That is: the
compiler can't prevent you from mutating an frozen object, but the runtime
can. One of the unfortunate aspects of Go is its limited support for
constants: structs, slices, and even arrays cannot be declared as consts. This
becomes a problem when you want to pass a slice around to many consumers
without worrying about them modifying it. With freeze, you can guard against
these unwanted or intended behaviors.

To accomplish this, the mprotect syscall is used. Sadly, this necessitates
allocating new memory via mmap and copying the data into it. This performance
penalty should not be prohibitive, but it's something to be aware of.

In case it wasn't clear from the previous paragraph, this package is not
intended to be used in production. A well-designed API is a much saner
solution than freezing your data structures. I would even caution against
using freeze in your automated testing, due to its platform-specific nature.
freeze is best used for "one-off" debugging. Something like this:

1. Observe bug
2. Suspect that shared mutable data is the culprit
3. Call freeze.Object on the data after it is created
4. Run program again; it crashes
5. Inspect stack trace to identify where the data was modified
6. Fix bug
7. Remove call to freeze.Object

Again: do not use freeze in production. It's a cool proof-of-concept, and it
can be useful for debugging, but that's about it. Let me put it another way:
freeze imports four packages: reflect, runtime, unsafe, and syscall (actually
golang.org/x/sys/unix). Does that sound like a package you want to depend on?

Okay, back to the real documention:

Functions are provided for freezing the three "pointer types:" Pointer, Slice,
and Map. Each function returns a copy of their input that is backed by
protected memory. In addition, Object is provided for freezing recursively.
Given a slice of pointers, Object will prevent modifications to both the
pointer data and the slice data, while Slice merely does the latter.

To freeze an object:

	type foo struct {
		X int
		y bool // yes, freeze works on unexported fields!
	}
	f := &foo{3, true}
	f = freeze.Object(f).(*foo)
	println(f.X) // ok; prints 3
	f.X++        // not ok; panics

Note that since foo does not contain any pointers, calling Pointer(f) would
have the same effect here.

It is recommended that, where convenient, you reassign the return value to its
original variable, as with append. Otherwise, you will retain both the mutable
original and the frozen copy.

Likewise, to freeze a slice:

	xs := []int{1, 2, 3}
	xs = freeze.Slice(xs).([]int)
	println(xs[0]) // ok; prints 1
	xs[0]++        // not ok; panics

Interfaces can also be frozen, since internally they are just pointers to
objects. The effect of this is that the interface's pure methods can still be
called, but impure methods cannot. Unfortunately the impurity of a given
method is defined by the implementation, not the interface. Even a String
method could conceivably modify some internal state. Furthermore, the caveat
about unexported struct fields (see below) applies here, so many exported
objects cannot be completely frozen.

Caveats

This package depends heavily on the internal representations of the slice and
map types. These objects are not likely to change, but if they do, this
package will break.

In general, you can't call Object on the same object twice. This is because
Object will attempt to rewrite the object's internal pointers -- which is a
memory modification. Calling Pointer or Slice twice should be fine.

Object cannot descend into unexported struct fields. It can still freeze the
field itself, but if the field contains a pointer, the data it points to will
not be frozen.

Appending to a frozen slice will trigger a panic iff len(slice) < cap(slice).
This is because appending to a full slice will allocate new memory.

Unix is the only supported platform. Windows support is not planned, because
it doesn't support a syscall analogous to mprotect.
*/
package freeze

import (
	"reflect"
	"runtime"
	"unsafe"

	"golang.org/x/sys/unix"
)

// Pointer returns a frozen copy of v, which must be a pointer. Future writes
// to the copy's memory will result in a panic. In most cases, the copy should
// be reassigned to v.
func Pointer(v interface{}) interface{} {
	if v == nil {
		return v
	}
	typ := reflect.TypeOf(v)
	if typ.Kind() != reflect.Ptr {
		panic("Pointer called on non-pointer type")
	}

	// freeze the memory pointed to by the interface's data pointer
	size := typ.Elem().Size()
	ptrs := (*[2]uintptr)(unsafe.Pointer(&v))
	ptrs[1] = copyAndFreeze(ptrs[1], size)

	return v
}

// Slice returns a frozen copy of v, which must be a slice. Future writes to
// the copy's memory will result in a panic. In most cases, the copy should be
// reassigned to v.
func Slice(v interface{}) interface{} {
	if v == nil {
		return v
	}
	val := reflect.ValueOf(v)
	if val.Kind() != reflect.Slice {
		panic("Slice called on non-slice type")
	}

	// freeze the memory pointed to by the slice's data pointer
	size := val.Type().Elem().Size() * uintptr(val.Len())
	slice := (*reflect.SliceHeader)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1])
	slice.Data = copyAndFreeze(slice.Data, size)

	return v
}

// Map returns a frozen copy of v, which must be a map. Future writes to
// the copy's memory will result in a panic. In most cases, the copy should be
// reassigned to v. Note that both the keys and values of the map are frozen.
func Map(v interface{}) interface{} {
	if v == nil {
		return v
	}
	typ := reflect.TypeOf(v)
	if typ.Kind() != reflect.Map {
		panic("Map called on non-map type")
	}

	// copied from runtime/hmap.go
	type hmap struct {
		count      int
		flags      uint8
		B          uint8
		hash0      uint32
		buckets    uintptr
		oldbuckets uintptr
		nevacuate  uintptr
		overflow   *[2]*[]uintptr
	}

	// convert v to a hmap so we can access 'B' and 'buckets'
	m := (*hmap)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1])

	// copied from reflect/type.go
	bucketSize := 8*(1+typ.Key().Size()+typ.Elem().Size()) + unsafe.Sizeof(uintptr(0))
	// size of map's bucket data is 2^B * bucketSize
	size := (uintptr(1) << m.B) * bucketSize

	// freeze the map's buckets
	m.buckets = copyAndFreeze(m.buckets, size)

	return v
}

// Object returns a recursively frozen copy of v, which must be a pointer or a
// slice. It will descend into pointers, arrays, slices, and structs until
// "bottoming out," freezing the entire chain. Passing a cyclic structure to
// Object will result in infinite recursion. Note that Object can only descend
// into exported struct fields (the fields themselves will still be frozen).
func Object(v interface{}) interface{} {
	if v == nil {
		return v
	}
	val := reflect.ValueOf(v)
	switch val.Kind() {
	case reflect.Ptr, reflect.Slice, reflect.Map:
		return object(val).Interface()
	}
	panic("Object called on invalid type")
}

// object updates all pointers in val to point to frozen memory containing the
// same data.
func object(val reflect.Value) reflect.Value {
	// we only need to recurse into types that might have pointers
	hasPtrs := func(t reflect.Type) bool {
		switch t.Kind() {
		case reflect.Ptr, reflect.Array, reflect.Slice, reflect.Map, reflect.Struct:
			return true
		}
		return false
	}

	switch val.Type().Kind() {
	default:
		return val

	case reflect.Ptr:
		if val.IsNil() {
			return val
		} else if hasPtrs(val.Type().Elem()) {
			val.Elem().Set(object(val.Elem()))
		}
		return reflect.ValueOf(Pointer(val.Interface()))

	case reflect.Array:
		if hasPtrs(val.Type().Elem()) {
			for i := 0; i < val.Len(); i++ {
				val.Index(i).Set(object(val.Index(i)))
			}
		}
		return val

	case reflect.Slice:
		if hasPtrs(val.Type().Elem()) {
			for i := 0; i < val.Len(); i++ {
				val.Index(i).Set(object(val.Index(i)))
			}
		}
		return reflect.ValueOf(Slice(val.Interface()))

	case reflect.Map:
		if hasPtrs(val.Type().Elem()) || hasPtrs(val.Type().Key()) {
			newMap := reflect.MakeMap(val.Type())
			for _, key := range val.MapKeys() {
				newMap.SetMapIndex(object(key), object(val.MapIndex(key)))
			}
			val = newMap
		}
		return reflect.ValueOf(Map(val.Interface()))

	case reflect.Struct:
		for i := 0; i < val.NumField(); i++ {
			// can't recurse into unexported fields
			t := val.Type().Field(i)
			if !(t.PkgPath != "" && !t.Anonymous) && hasPtrs(t.Type) {
				val.Field(i).Set(object(val.Field(i)))
			}
		}
		return val
	}
}

// copyAndFreeze copies n bytes from dataptr into new memory, freezes it, and
// returns a uintptr to the new memory.
func copyAndFreeze(dataptr, n uintptr) uintptr {
	if dataptr == 0 || n == 0 {
		return dataptr
	}
	// allocate new memory to be frozen
	newMem, err := unix.Mmap(-1, 0, int(n), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE)
	if err != nil {
		panic(err)
	}
	// set a finalizer to unmap the memory when it would normally be GC'd
	runtime.SetFinalizer(&newMem, func(b *[]byte) { _ = unix.Munmap(*b) })

	// copy n bytes into newMem
	copy(newMem, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{dataptr, int(n), int(n)})))

	// freeze the new memory
	if err = unix.Mprotect(newMem, unix.PROT_READ); err != nil {
		panic(err)
	}

	// return pointer to new memory
	return uintptr(unsafe.Pointer(&newMem[0]))
}
<|endoftext|>


<commit_before>package rest

import (
	"encoding/json"
	"fmt"
	"path"

	"github.com/bitfinexcom/bitfinex-api-go/v2"
)

// OrderService manages data flow for the Order API endpoint
type OrderService struct {
	requestFactory
	Synchronous
}

type OrderIDs []int
type GroupOrderIDs []int
type ClientOrderIDs [][]interface{}
type OrderOps [][]interface{}
type OrderMultiArgs struct {
	Ops OrderOps `json:"ops"`
}

type CancelOrderMultiArgs struct {
	OrderIDs       OrderIDs       `json:"id,omitempty"`
	GroupOrderIDs  GroupOrderIDs  `json:"gid,omitempty"`
	ClientOrderIDs ClientOrderIDs `json:"cid,omitempty"`
	All            int            `json:"all,omitempty"`
}

// Retrieves all of the active orders
// See https://docs.bitfinex.com/reference#rest-auth-orders for more info
func (s *OrderService) All() (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getActiveOrders("")
}

// Retrieves all of the active orders with for the given symbol
// See https://docs.bitfinex.com/reference#rest-auth-orders for more info
func (s *OrderService) GetBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getActiveOrders(symbol)
}

// Retrieve an active order by the given ID
// See https://docs.bitfinex.com/reference#rest-auth-orders for more info
func (s *OrderService) GetByOrderId(orderID int64) (o *bitfinex.Order, err error) {
	os, err := s.All()
	if err != nil {
		return nil, err
	}
	for _, order := range os.Snapshot {
		if order.ID == orderID {
			return order, nil
		}
	}
	return nil, bitfinex.ErrNotFound
}

// Retrieves all past orders
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) AllHistory() (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getHistoricalOrders("")
}

// Retrieves all past orders with the given symbol
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) GetHistoryBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getHistoricalOrders(symbol)
}

// Retrieve a single order in history with the given id
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) GetHistoryByOrderId(orderID int64) (o *bitfinex.Order, err error) {
	os, err := s.AllHistory()
	if err != nil {
		return nil, err
	}
	for _, order := range os.Snapshot {
		if order.ID == orderID {
			return order, nil
		}
	}
	return nil, bitfinex.ErrNotFound
}

// Retrieves the trades generated by an order
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) OrderTrades(symbol string, orderID int64) (*bitfinex.TradeExecutionUpdateSnapshot, error) {
	key := fmt.Sprintf("%s:%d", symbol, orderID)
	req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("order", key, "trades"))
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	return bitfinex.NewTradeExecutionUpdateSnapshotFromRaw(raw)
}

func (s *OrderService) getActiveOrders(symbol string) (*bitfinex.OrderSnapshot, error) {
	req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("orders", symbol))
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	os, err := bitfinex.NewOrderSnapshotFromRaw(raw)
	if err != nil {
		return nil, err
	}
	if os == nil {
		return &bitfinex.OrderSnapshot{}, nil
	}
	return os, nil
}

func (s *OrderService) getHistoricalOrders(symbol string) (*bitfinex.OrderSnapshot, error) {
	req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("orders", symbol, "hist"))
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	os, err := bitfinex.NewOrderSnapshotFromRaw(raw)
	if err != nil {
		return nil, err
	}
	if os == nil {
		return &bitfinex.OrderSnapshot{}, nil
	}
	return os, nil
}

// Submit a request to create a new order
// see https://docs.bitfinex.com/reference#submit-order for more info
func (s *OrderService) SubmitOrder(order *bitfinex.OrderNewRequest) (*bitfinex.Notification, error) {
	bytes, err := order.ToJSON()
	if err != nil {
		return nil, err
	}
	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "submit"), bytes)
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	return bitfinex.NewNotificationFromRaw(raw)
}

// Submit a request to update an order with the given id with the given changes
// see https://docs.bitfinex.com/reference#order-update for more info
func (s *OrderService) SubmitUpdateOrder(order *bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) {
	bytes, err := order.ToJSON()
	if err != nil {
		return nil, err
	}
	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "update"), bytes)
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	return bitfinex.NewNotificationFromRaw(raw)
}

// Submit a request to cancel an order with the given Id
// see https://docs.bitfinex.com/reference#cancel-order for more info
func (s *OrderService) SubmitCancelOrder(oc *bitfinex.OrderCancelRequest) error {
	bytes, err := oc.ToJSON()
	if err != nil {
		return err
	}
	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "cancel"), bytes)
	if err != nil {
		return err
	}
	_, err = s.Request(req)
	if err != nil {
		return err
	}
	return nil
}

// CancelOrderMulti cancels multiple orders simultaneously. Orders can be canceled based on the Order ID,
// the combination of Client Order ID and Client Order Date, or the Group Order ID. Alternatively, the body
// param 'all' can be used with a value of 1 to cancel all orders.
// see https://docs.bitfinex.com/reference#rest-auth-order-cancel-multi for more info
func (s *OrderService) CancelOrderMulti(args CancelOrderMultiArgs) (*bitfinex.Notification, error) {
	bytes, err := json.Marshal(args)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "cancel", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// CancelOrdersMultiOp cancels multiple orders simultaneously. Accepts a slice of order ID's to be canceled.
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) CancelOrdersMultiOp(ids OrderIDs) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"oc_multi",
				map[string][]int{"id": ids},
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// CancelOrderMultiOp cancels order. Accepts orderID to be canceled.
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) CancelOrderMultiOp(orderID int) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"oc",
				map[string]int{"id": orderID},
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// OrderNewMultiOp creates new order. Accepts instance of bitfinex.OrderNewRequest
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) OrderNewMultiOp(order bitfinex.OrderNewRequest) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"on",
				order.EnrichedPayload(),
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// OrderMultiOp - send Multiple order-related operations. Please note the sent object has
// only one property with a value of a slice of slices detailing each order operation.
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) OrderMultiOp(ops OrderOps) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{Ops: ops}
	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}
<commit_msg>v2/rest/order.go new OrderUpdateMultiOp function<commit_after>package rest

import (
	"encoding/json"
	"fmt"
	"path"

	"github.com/bitfinexcom/bitfinex-api-go/v2"
)

// OrderService manages data flow for the Order API endpoint
type OrderService struct {
	requestFactory
	Synchronous
}

type OrderIDs []int
type GroupOrderIDs []int
type ClientOrderIDs [][]interface{}
type OrderOps [][]interface{}
type OrderMultiArgs struct {
	Ops OrderOps `json:"ops"`
}

type CancelOrderMultiArgs struct {
	OrderIDs       OrderIDs       `json:"id,omitempty"`
	GroupOrderIDs  GroupOrderIDs  `json:"gid,omitempty"`
	ClientOrderIDs ClientOrderIDs `json:"cid,omitempty"`
	All            int            `json:"all,omitempty"`
}

// Retrieves all of the active orders
// See https://docs.bitfinex.com/reference#rest-auth-orders for more info
func (s *OrderService) All() (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getActiveOrders("")
}

// Retrieves all of the active orders with for the given symbol
// See https://docs.bitfinex.com/reference#rest-auth-orders for more info
func (s *OrderService) GetBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getActiveOrders(symbol)
}

// Retrieve an active order by the given ID
// See https://docs.bitfinex.com/reference#rest-auth-orders for more info
func (s *OrderService) GetByOrderId(orderID int64) (o *bitfinex.Order, err error) {
	os, err := s.All()
	if err != nil {
		return nil, err
	}
	for _, order := range os.Snapshot {
		if order.ID == orderID {
			return order, nil
		}
	}
	return nil, bitfinex.ErrNotFound
}

// Retrieves all past orders
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) AllHistory() (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getHistoricalOrders("")
}

// Retrieves all past orders with the given symbol
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) GetHistoryBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) {
	// use no symbol, this will get all orders
	return s.getHistoricalOrders(symbol)
}

// Retrieve a single order in history with the given id
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) GetHistoryByOrderId(orderID int64) (o *bitfinex.Order, err error) {
	os, err := s.AllHistory()
	if err != nil {
		return nil, err
	}
	for _, order := range os.Snapshot {
		if order.ID == orderID {
			return order, nil
		}
	}
	return nil, bitfinex.ErrNotFound
}

// Retrieves the trades generated by an order
// See https://docs.bitfinex.com/reference#orders-history for more info
func (s *OrderService) OrderTrades(symbol string, orderID int64) (*bitfinex.TradeExecutionUpdateSnapshot, error) {
	key := fmt.Sprintf("%s:%d", symbol, orderID)
	req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("order", key, "trades"))
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	return bitfinex.NewTradeExecutionUpdateSnapshotFromRaw(raw)
}

func (s *OrderService) getActiveOrders(symbol string) (*bitfinex.OrderSnapshot, error) {
	req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("orders", symbol))
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	os, err := bitfinex.NewOrderSnapshotFromRaw(raw)
	if err != nil {
		return nil, err
	}
	if os == nil {
		return &bitfinex.OrderSnapshot{}, nil
	}
	return os, nil
}

func (s *OrderService) getHistoricalOrders(symbol string) (*bitfinex.OrderSnapshot, error) {
	req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("orders", symbol, "hist"))
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	os, err := bitfinex.NewOrderSnapshotFromRaw(raw)
	if err != nil {
		return nil, err
	}
	if os == nil {
		return &bitfinex.OrderSnapshot{}, nil
	}
	return os, nil
}

// Submit a request to create a new order
// see https://docs.bitfinex.com/reference#submit-order for more info
func (s *OrderService) SubmitOrder(order *bitfinex.OrderNewRequest) (*bitfinex.Notification, error) {
	bytes, err := order.ToJSON()
	if err != nil {
		return nil, err
	}
	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "submit"), bytes)
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	return bitfinex.NewNotificationFromRaw(raw)
}

// Submit a request to update an order with the given id with the given changes
// see https://docs.bitfinex.com/reference#order-update for more info
func (s *OrderService) SubmitUpdateOrder(order *bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) {
	bytes, err := order.ToJSON()
	if err != nil {
		return nil, err
	}
	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "update"), bytes)
	if err != nil {
		return nil, err
	}
	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}
	return bitfinex.NewNotificationFromRaw(raw)
}

// Submit a request to cancel an order with the given Id
// see https://docs.bitfinex.com/reference#cancel-order for more info
func (s *OrderService) SubmitCancelOrder(oc *bitfinex.OrderCancelRequest) error {
	bytes, err := oc.ToJSON()
	if err != nil {
		return err
	}
	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "cancel"), bytes)
	if err != nil {
		return err
	}
	_, err = s.Request(req)
	if err != nil {
		return err
	}
	return nil
}

// CancelOrderMulti cancels multiple orders simultaneously. Orders can be canceled based on the Order ID,
// the combination of Client Order ID and Client Order Date, or the Group Order ID. Alternatively, the body
// param 'all' can be used with a value of 1 to cancel all orders.
// see https://docs.bitfinex.com/reference#rest-auth-order-cancel-multi for more info
func (s *OrderService) CancelOrderMulti(args CancelOrderMultiArgs) (*bitfinex.Notification, error) {
	bytes, err := json.Marshal(args)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "cancel", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// CancelOrdersMultiOp cancels multiple orders simultaneously. Accepts a slice of order ID's to be canceled.
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) CancelOrdersMultiOp(ids OrderIDs) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"oc_multi",
				map[string][]int{"id": ids},
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// CancelOrderMultiOp cancels order. Accepts orderID to be canceled.
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) CancelOrderMultiOp(orderID int) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"oc",
				map[string]int{"id": orderID},
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// OrderNewMultiOp creates new order. Accepts instance of bitfinex.OrderNewRequest
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) OrderNewMultiOp(order bitfinex.OrderNewRequest) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"on",
				order.EnrichedPayload(),
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// OrderUpdateMultiOp updates order. Accepts instance of bitfinex.OrderUpdateRequest
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) OrderUpdateMultiOp(order bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{
		Ops: OrderOps{
			{
				"ou",
				order.EnrichedPayload(),
			},
		},
	}

	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}

// OrderMultiOp - send Multiple order-related operations. Please note the sent object has
// only one property with a value of a slice of slices detailing each order operation.
// see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info
func (s *OrderService) OrderMultiOp(ops OrderOps) (*bitfinex.Notification, error) {
	pld := OrderMultiArgs{Ops: ops}
	bytes, err := json.Marshal(pld)
	if err != nil {
		return nil, err
	}

	req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(
		bitfinex.PermissionWrite,
		path.Join("order", "multi"),
		bytes,
	)
	if err != nil {
		return nil, err
	}

	raw, err := s.Request(req)
	if err != nil {
		return nil, err
	}

	return bitfinex.NewNotificationFromRaw(raw)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"github.com/kardianos/osext"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"
	"sync"
	"time"
)

const app = "BrewTroller Cloud Compiler Service"
const version = "1.0.0"

const SourceDir = "/BrewTroller"
const OptionsFileName = "/BrewTroller/options.json"

// Command line flags
var (
	debugMode  = flag.Bool("debug", false, "Enables server debug mode")
	pollPeriod = flag.Duration("poll", 5*time.Minute, "Github poll period")
	gitRepo    = flag.String("git", "http://github.com/brewtroller/brewtroller", "BrewTroller Remote Repository")
)

type BuildServer struct {
	version    string
	gitURL     string
	pollPeriod time.Duration

	execFolder string

	mu           sync.RWMutex //Protect the version tags and the source dir
	optionsCache map[string][]map[string]interface{}
}

func (bs *BuildServer) updateTags() {
	bs.mu.Lock()
	//clone the remote in a local repo
	localSrcDir := bs.execFolder + SourceDir
	os.RemoveAll(localSrcDir)

	cloneCmd := exec.Command("git", "clone", bs.gitURL, localSrcDir)
	_, err := cloneCmd.CombinedOutput()
	if err != nil {
		panic(err)
	}
	//Check if Source dir exists
	_, err = os.Stat(localSrcDir)
	if err != nil {
		panic("Could not create local source copy")
	}
	bs.mu.Unlock()

	for true {
		bs.mu.Lock()
		//Clear out all current tags, in case any have been removed
		clearCmd := exec.Command("git", "tag", "-l")
		clearCmd.Dir = localSrcDir
		removeCmd := exec.Command("xargs", "git", "tag", "-d")
		removeCmd.Dir = localSrcDir
		removeCmd.Stdin, _ = clearCmd.StdoutPipe()
		removeCmd.Start()
		clearCmd.Run()
		removeCmd.Wait()

		//Update the local repo
		pullCmd := exec.Command("git", "pull")
		pullCmd.Dir = localSrcDir
		pullCmd.Run()

		//get tag list
		tagCmd := exec.Command("git", "tag", "-l", "v[0-9]*\\.[0-9]*\\.[0-9]*")
		tagCmd.Dir = localSrcDir
		list, _ := tagCmd.Output()

		versionTags := strings.Split(string(list), "\n")
		//remove any blank tags
		for i := range versionTags {
			if strings.EqualFold(versionTags[i], "") {
				versionTags = append(versionTags[:i], versionTags[i+1:]...)
			}
		}
		//Build options cache
		bs.updateOptions(versionTags)

		bs.mu.Unlock()
		time.Sleep(bs.pollPeriod)
	}
}

// This method should only ever be called from within the poll worker, as it does not explicity lock the optionsCache itself
func (bs *BuildServer) updateOptions(versions []string) {

	optsManifest := make(map[string][]map[string]interface{})

	//parse the options manifest for each available version
	for _, ver := range versions {
		//checkout the version
		checkoutCmd := exec.Command("git", "checkout", ver)
		checkoutCmd.Dir = bs.execFolder + SourceDir
		checkoutCmd.Run()

		//parse the options file
		var opts, err = ioutil.ReadFile(bs.execFolder + OptionsFileName)
		if err != nil {
			// file doesn't exist, don't add version to manifest
			if *debugMode {
				fmt.Println("Options file for " + version + " does not exist, or cannot be opened!")
			}
			continue
		}
		var parsedOpts []map[string]interface{}
		err = json.Unmarshal(opts, &parsedOpts)
		if err != nil {
			if *debugMode {
				fmt.Println("Options file for " + version + " is invalid and cannot be parsed!")
			}
			continue
		}
		optsManifest[ver] = parsedOpts
	}
	//update BuildServer Options Cache
	bs.optionsCache = optsManifest
}

func NewServer(version string, gitUrl string, period time.Duration) *BuildServer {
	execFolder, _ := osext.ExecutableFolder()
	serv := &BuildServer{version: version, gitURL: gitUrl, pollPeriod: period, execFolder: execFolder}
	go serv.updateTags()
	return serv
}

func main() {
	flag.Parse()
	if *debugMode {
		fmt.Println("Debug mode enabled")
	}
	server := NewServer(version, *gitRepo, *pollPeriod)
	router := mux.NewRouter()
	router.HandleFunc("/", server.HomeHandler).Methods("GET")
	router.HandleFunc("/options", server.OptionsHandler).Methods("GET")
	router.HandleFunc("/build", server.BuildHandler).Methods("POST")
	http.ListenAndServe(":8080", router)
}

func makeErrorResonse(code string, err error, context ...string) []byte {
	em := make(map[string]string)

	em["code"] = code

	//If we are running in debug mode use the actual error as the message
	if *debugMode {
		em["message"] = err.Error()
	} else {
		//Not in debug mode, use generic response
		switch code {
		case "500":
			em["message"] = "Internal Server Error"
		case "400":
			em["message"] = "Bad Request"
		}
	}

	if *debugMode {
		for i, v := range context {
			em[fmt.Sprintf("context%i", i)] = v
		}
	}

	//Encode the error reponse for transmission
	enc, _ := json.Marshal(em)

	return enc
}

func (bs *BuildServer) HomeHandler(rw http.ResponseWriter, req *http.Request) {
	info := make(map[string]string)
	info["app"] = app
	info["version"] = version
	if *debugMode {
		c := exec.Command("uname", "-a")
		uname, _ := c.Output()
		info["host"] = string(uname)
	}
	rw.Header().Add("Access-Control-Allow-Origin", "*")
	rw.Header().Add("Content-Type", "application/json")
	encRes, _ := json.Marshal(info)
	rw.Write(encRes)
}

func (bs *BuildServer) OptionsHandler(rw http.ResponseWriter, req *http.Request) {
	bs.mu.RLock()
	opts, _ := json.Marshal(bs.optionsCache)
	bs.mu.RUnlock()

	rw.Header().Add("Access-Control-Allow-Origin", "*")
	rw.Header().Add("Content-Type", "application/json")
	rw.Write(opts)
}

func (bs *BuildServer) BuildHandler(rw http.ResponseWriter, req *http.Request) {
	//Generate a unique folder name to execute the build in
	// create a temp prefix with the requester addr, with '.' and ':' subbed
	reqID := strings.Replace(req.RemoteAddr, ".", "_", -1)
	reqID = strings.Replace(reqID, ":", "-", -1) + "-"
	tempDir, err := ioutil.TempDir("", reqID)

	//Handle error making temp build directory
	if err != nil {
		errResp := makeErrorResonse("500", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}
	//Clean-up the temp dir
	defer os.RemoveAll(tempDir)

	//Get request data
	reqData, err := ioutil.ReadAll(req.Body)

	//Handle error reading POST data
	if err != nil {
		errResp := makeErrorResonse("500", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//Convert the post data to a map
	optsMap := make(map[string]string)
	err = json.Unmarshal(reqData, &optsMap)

	//Handle errors unmarshalling build options
	if err != nil {
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}

	//Ensure we have a board option
	board, found := optsMap["board"]
	if !found {
		err := errors.New("Board Option Must be Supplied!")
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}

	//Ensure we have a build verison
	version, found := optsMap["BuildVersion"]
	if !found {
		err := errors.New("Build Version Must be Supplied!")
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}
	//Ensure that the build version is valid
	bs.mu.RLock()
	_, validVer := bs.optionsCache[version]
	bs.mu.RUnlock()
	if !validVer {
		err := errors.New("Build Version " + version + " is invalid!")
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}

	//Remove the build version from the opts map, as CMake cannot use it
	delete(optsMap, "BuildVersion")

	//Make a slice to hold the options, with an init len of 0 and a capacity of 20
	//   we start with a capacity of 20 to prevent having to initialize a new slice after every append
	cmakeOpts := make([]string, 0, 20)
	//iterate through the build options requested and make a slice to pass to cmake
	for k, v := range optsMap {
		opt := fmt.Sprintf("-D%s=%s", k, v)
		cmakeOpts = append(cmakeOpts, opt)
	}
	//Append the absolute path to the brewtroller source directory
	cmakeOpts = append(cmakeOpts, tempDir)

	//Clone the source repo into the temp dir
	pathToSource := bs.execFolder + SourceDir
	cloneCmd := exec.Command("git", "clone", pathToSource, tempDir)
	bs.mu.RLock()
	cloneCmd.Run()
	bs.mu.RUnlock()

	//Checkout the build version in the temp dir
	checkoutCmd := exec.Command("git", "checkout", version)
	checkoutCmd.Dir = tempDir
	checkoutCmd.Run()
	//Create the build dir
	buildDir := path.Join(tempDir, "/build")
	os.MkdirAll(buildDir, 0777)

	//Attempt to setup Cmake build dir
	cmakeCmd := exec.Command("cmake", cmakeOpts...)
	cmakeCmd.Dir = buildDir

	cmakeOut, err := cmakeCmd.CombinedOutput()
	//Handle cmake setup error
	if err != nil {
		errResp := makeErrorResonse("500", err, string(cmakeOut))
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//build the image(s) -- in the future we will build an eeprom image to upload
	makeCmd := exec.Command("make")
	makeCmd.Dir = buildDir
	makeOut, err := makeCmd.CombinedOutput()
	//Handle any errors from make
	if err != nil {
		errResp := makeErrorResonse("500", err, string(makeOut))
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//Grab the binary and read it
	binary, err := ioutil.ReadFile(buildDir + "/src/BrewTroller-" + board + ".hex")
	if err != nil {
		errResp := makeErrorResonse("500", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//Create response map
	resp := make(map[string]string)

	if *debugMode {
		resp["reqID"] = reqID
		resp["buildLocation"] = tempDir
		resp["reqDat"] = string(reqData)
		resp["cmake-output"] = string(cmakeOut)
		resp["make-output"] = string(makeOut)
	}

	resp["binary"] = string(binary)

	enc, _ := json.Marshal(resp)
	rw.Header().Add("Content-Type", "application/json")
	rw.Header().Add("Access-Control-Allow-Origin", "*")
	rw.Write(enc)
}
<commit_msg>Save received options to user_config.json for build processing<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"github.com/kardianos/osext"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"
	"sync"
	"time"
)

const app = "BrewTroller Cloud Compiler Service"
const version = "1.0.0"

const SourceDir = "/BrewTroller"
const OptionsFileName = "/BrewTroller/options.json"

// Command line flags
var (
	debugMode  = flag.Bool("debug", false, "Enables server debug mode")
	pollPeriod = flag.Duration("poll", 5*time.Minute, "Github poll period")
	gitRepo    = flag.String("git", "http://github.com/brewtroller/brewtroller", "BrewTroller Remote Repository")
)

type BuildServer struct {
	version    string
	gitURL     string
	pollPeriod time.Duration

	execFolder string

	mu           sync.RWMutex //Protect the version tags and the source dir
	optionsCache map[string][]map[string]interface{}
}

func (bs *BuildServer) updateTags() {
	bs.mu.Lock()
	//clone the remote in a local repo
	localSrcDir := bs.execFolder + SourceDir
	os.RemoveAll(localSrcDir)

	cloneCmd := exec.Command("git", "clone", bs.gitURL, localSrcDir)
	_, err := cloneCmd.CombinedOutput()
	if err != nil {
		panic(err)
	}
	//Check if Source dir exists
	_, err = os.Stat(localSrcDir)
	if err != nil {
		panic("Could not create local source copy")
	}
	bs.mu.Unlock()

	for true {
		bs.mu.Lock()
		//Clear out all current tags, in case any have been removed
		clearCmd := exec.Command("git", "tag", "-l")
		clearCmd.Dir = localSrcDir
		removeCmd := exec.Command("xargs", "git", "tag", "-d")
		removeCmd.Dir = localSrcDir
		removeCmd.Stdin, _ = clearCmd.StdoutPipe()
		removeCmd.Start()
		clearCmd.Run()
		removeCmd.Wait()

		//Update the local repo
		pullCmd := exec.Command("git", "pull")
		pullCmd.Dir = localSrcDir
		pullCmd.Run()

		//get tag list
		tagCmd := exec.Command("git", "tag", "-l", "v[0-9]*\\.[0-9]*\\.[0-9]*")
		tagCmd.Dir = localSrcDir
		list, _ := tagCmd.Output()

		versionTags := strings.Split(string(list), "\n")
		//remove any blank tags
		for i := range versionTags {
			if strings.EqualFold(versionTags[i], "") {
				versionTags = append(versionTags[:i], versionTags[i+1:]...)
			}
		}
		//Build options cache
		bs.updateOptions(versionTags)

		bs.mu.Unlock()
		time.Sleep(bs.pollPeriod)
	}
}

// This method should only ever be called from within the poll worker, as it does not explicity lock the optionsCache itself
func (bs *BuildServer) updateOptions(versions []string) {

	optsManifest := make(map[string][]map[string]interface{})

	//parse the options manifest for each available version
	for _, ver := range versions {
		//checkout the version
		checkoutCmd := exec.Command("git", "checkout", ver)
		checkoutCmd.Dir = bs.execFolder + SourceDir
		checkoutCmd.Run()

		//parse the options file
		var opts, err = ioutil.ReadFile(bs.execFolder + OptionsFileName)
		if err != nil {
			// file doesn't exist, don't add version to manifest
			if *debugMode {
				fmt.Println("Options file for " + version + " does not exist, or cannot be opened!")
			}
			continue
		}
		var parsedOpts []map[string]interface{}
		err = json.Unmarshal(opts, &parsedOpts)
		if err != nil {
			if *debugMode {
				fmt.Println("Options file for " + version + " is invalid and cannot be parsed!")
			}
			continue
		}
		optsManifest[ver] = parsedOpts
	}
	//update BuildServer Options Cache
	bs.optionsCache = optsManifest
}

func NewServer(version string, gitUrl string, period time.Duration) *BuildServer {
	execFolder, _ := osext.ExecutableFolder()
	serv := &BuildServer{version: version, gitURL: gitUrl, pollPeriod: period, execFolder: execFolder}
	go serv.updateTags()
	return serv
}

func main() {
	flag.Parse()
	if *debugMode {
		fmt.Println("Debug mode enabled")
	}
	server := NewServer(version, *gitRepo, *pollPeriod)
	router := mux.NewRouter()
	router.HandleFunc("/", server.HomeHandler).Methods("GET")
	router.HandleFunc("/options", server.OptionsHandler).Methods("GET")
	router.HandleFunc("/build", server.BuildHandler).Methods("POST")
	http.ListenAndServe(":8080", router)
}

func makeErrorResonse(code string, err error, context ...string) []byte {
	em := make(map[string]string)

	em["code"] = code

	//If we are running in debug mode use the actual error as the message
	if *debugMode {
		em["message"] = err.Error()
	} else {
		//Not in debug mode, use generic response
		switch code {
		case "500":
			em["message"] = "Internal Server Error"
		case "400":
			em["message"] = "Bad Request"
		}
	}

	if *debugMode {
		for i, v := range context {
			em[fmt.Sprintf("context%i", i)] = v
		}
	}

	//Encode the error reponse for transmission
	enc, _ := json.Marshal(em)

	return enc
}

func (bs *BuildServer) HomeHandler(rw http.ResponseWriter, req *http.Request) {
	info := make(map[string]string)
	info["app"] = app
	info["version"] = version
	if *debugMode {
		c := exec.Command("uname", "-a")
		uname, _ := c.Output()
		info["host"] = string(uname)
	}
	rw.Header().Add("Access-Control-Allow-Origin", "*")
	rw.Header().Add("Content-Type", "application/json")
	encRes, _ := json.Marshal(info)
	rw.Write(encRes)
}

func (bs *BuildServer) OptionsHandler(rw http.ResponseWriter, req *http.Request) {
	bs.mu.RLock()
	opts, _ := json.Marshal(bs.optionsCache)
	bs.mu.RUnlock()

	rw.Header().Add("Access-Control-Allow-Origin", "*")
	rw.Header().Add("Content-Type", "application/json")
	rw.Write(opts)
}

func (bs *BuildServer) BuildHandler(rw http.ResponseWriter, req *http.Request) {
	//Generate a unique folder name to execute the build in
	// create a temp prefix with the requester addr, with '.' and ':' subbed
	reqID := strings.Replace(req.RemoteAddr, ".", "_", -1)
	reqID = strings.Replace(reqID, ":", "-", -1) + "-"
	tempDir, err := ioutil.TempDir("", reqID)

	//Handle error making temp build directory
	if err != nil {
		errResp := makeErrorResonse("500", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}
	//Clean-up the temp dir
	defer os.RemoveAll(tempDir)

	//Get request data
	reqData, err := ioutil.ReadAll(req.Body)

	//Handle error reading POST data
	if err != nil {
		errResp := makeErrorResonse("500", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//Convert the post data to a map
	optsMap := make(map[string]string)
	err = json.Unmarshal(reqData, &optsMap)

	//Handle errors unmarshalling build options
	if err != nil {
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}

	//Ensure we have a board option
	board, found := optsMap["board"]
	if !found {
		err := errors.New("Board Option Must be Supplied!")
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}

	//Ensure we have a build verison
	version, found := optsMap["BuildVersion"]
	if !found {
		err := errors.New("Build Version Must be Supplied!")
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}
	//Ensure that the build version is valid
	bs.mu.RLock()
	_, validVer := bs.optionsCache[version]
	bs.mu.RUnlock()
	if !validVer {
		err := errors.New("Build Version " + version + " is invalid!")
		errResp := makeErrorResonse("400", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write(errResp)
		return
	}

	//Remove the build version from the opts map, as CMake cannot use it
	delete(optsMap, "BuildVersion")

	//Make a slice to hold the options, with an init len of 0 and a capacity of 20
	//   we start with a capacity of 20 to prevent having to initialize a new slice after every append
	cmakeOpts := make([]string, 0, 20)
	//iterate through the build options requested and make a slice to pass to cmake
	for k, v := range optsMap {
		opt := fmt.Sprintf("-D%s=%s", k, v)
		cmakeOpts = append(cmakeOpts, opt)
	}
	//Append the absolute path to the brewtroller source directory
	cmakeOpts = append(cmakeOpts, tempDir)

	//Clone the source repo into the temp dir
	pathToSource := bs.execFolder + SourceDir
	cloneCmd := exec.Command("git", "clone", pathToSource, tempDir)
	bs.mu.RLock()
	cloneCmd.Run()
	bs.mu.RUnlock()

	//Checkout the build version in the temp dir
	checkoutCmd := exec.Command("git", "checkout", version)
	checkoutCmd.Dir = tempDir
	checkoutCmd.Run()
	//Create the build dir
	buildDir := path.Join(tempDir, "/build")
	os.MkdirAll(buildDir, 0777)

        // Save copy of settings to build directory
        optionsPath := path.Join(tempDir,"user_config.json")

        err = ioutil.WriteFile(optionsPath, reqData, 0644)
        if err != nil {
               errResp := makeErrorResonse("500", err)
               rw.Header().Add("Access-Control-Allow-Origin", "*")
               rw.WriteHeader(http.StatusInternalServerError)
               rw.Write(errResp)
               return
        }

	//Attempt to setup Cmake build dir
	cmakeCmd := exec.Command("cmake", cmakeOpts...)
	cmakeCmd.Dir = buildDir

	cmakeOut, err := cmakeCmd.CombinedOutput()
	//Handle cmake setup error
	if err != nil {
		errResp := makeErrorResonse("500", err, string(cmakeOut))
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//build the image(s) -- in the future we will build an eeprom image to upload
	makeCmd := exec.Command("make")
	makeCmd.Dir = buildDir
	makeOut, err := makeCmd.CombinedOutput()
	//Handle any errors from make
	if err != nil {
		errResp := makeErrorResonse("500", err, string(makeOut))
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//Grab the binary and read it
	binary, err := ioutil.ReadFile(buildDir + "/src/BrewTroller-" + board + ".hex")
	if err != nil {
		errResp := makeErrorResonse("500", err)
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write(errResp)
		return
	}

	//Create response map
	resp := make(map[string]string)

	if *debugMode {
		resp["reqID"] = reqID
		resp["buildLocation"] = tempDir
		resp["reqDat"] = string(reqData)
		resp["cmake-output"] = string(cmakeOut)
		resp["make-output"] = string(makeOut)
	}

	resp["binary"] = string(binary)

	enc, _ := json.Marshal(resp)
	rw.Header().Add("Content-Type", "application/json")
	rw.Header().Add("Access-Control-Allow-Origin", "*")
	rw.Write(enc)
}
<|endoftext|>


<commit_before>package multiqueue

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/mercari/go-circuitbreaker"
	"github.com/nabeken/aws-go-sqs/v3/queue"
)

// Dispatcher manages multiple *queue.Queue instances with circit breaker and dispatches it by random or round-robin.
// Circuit breaker is installed per queue. Dispatcher doesn't dispatch a queue while the circuit breaker is open.
type Dispatcher struct {
	// circuit breaker for each queue
	cb            map[string]*circuitbreaker.CircuitBreaker
	onStateChange func(q *queue.Queue, oldState, newState circuitbreaker.State)

	monitor *monitor

	rand *rand.Rand

	// protect queues
	mu sync.Mutex
	// all of the registered queues
	queues []*queue.Queue
	// queues believed to be available
	avail []*queue.Queue
	// index to a queue which will be dispatched next
	nextIndex int
}

// WithOnStateChange installs a hook which will be invoked when the state of the circuit breaker is changed.
func (d *Dispatcher) WithOnStateChange(f func(*queue.Queue, circuitbreaker.State, circuitbreaker.State)) *Dispatcher {
	d.onStateChange = f
	return d
}

// New creates a dispatcher with mercari/go-circuitbreaker enabled per queue.
func New(cbOpts *circuitbreaker.Options, queues ...*queue.Queue) *Dispatcher {
	if len(queues) == 0 {
		panic("at least one queue is required")
	}

	avail := make([]*queue.Queue, len(queues))
	copy(avail, queues)

	d := &Dispatcher{
		queues: queues,
		avail:  avail,
		rand:   rand.New(rand.NewSource(time.Now().UnixNano())),
	}

	d.buildCircuitBreaker(cbOpts)
	d.buildMonitor()

	return d
}

func (d *Dispatcher) buildCircuitBreaker(opts *circuitbreaker.Options) {
	cb := map[string]*circuitbreaker.CircuitBreaker{}
	for i := range d.queues {
		q := d.queues[i]
		cb[*q.URL] = circuitbreaker.New(&circuitbreaker.Options{
			Interval:              opts.Interval,
			OpenTimeout:           opts.OpenTimeout,
			OpenBackOff:           opts.OpenBackOff,
			HalfOpenMaxSuccesses:  opts.HalfOpenMaxSuccesses,
			ShouldTrip:            opts.ShouldTrip,
			FailOnContextCancel:   opts.FailOnContextCancel,
			FailOnContextDeadline: opts.FailOnContextDeadline,
		})
	}
	d.cb = cb
}

func (d *Dispatcher) buildMonitor() {
	mon := &monitor{d: d}
	mon.initState()
	d.monitor = mon
}

func (d *Dispatcher) markUnavailable(q *queue.Queue) {
	d.mu.Lock()
	defer d.mu.Unlock()

	var newAvail []*queue.Queue
	for i := range d.avail {
		if *q.URL != *d.avail[i].URL {
			newAvail = append(newAvail, d.avail[i])
		}
	}

	d.nextIndex = 0
	d.avail = newAvail
}

func (d *Dispatcher) markAvailable(q *queue.Queue) {
	d.mu.Lock()
	defer d.mu.Unlock()
	for i := range d.avail {
		if *q.URL == *d.avail[i].URL {
			// it exists
			return
		}
	}

	d.nextIndex = 0
	d.avail = append(d.avail, q)
}

func (d *Dispatcher) handleStateChange(q *queue.Queue, prev, cur circuitbreaker.State) {
	if f := d.onStateChange; f != nil {
		f(q, prev, cur)
	}

	switch cur {
	case circuitbreaker.StateOpen:
		d.markUnavailable(q)
	case circuitbreaker.StateHalfOpen, circuitbreaker.StateClosed:
		d.markAvailable(q)
	default:
		panic(fmt.Sprintf("unknown state: %s -> %s", prev, cur))
	}
}

// DispatchByRR dispatches Executor by round-robin fasion.
func (d *Dispatcher) DispatchByRR() *Executor {
	d.mu.Lock()
	defer d.mu.Unlock()
	return d.dispatch(d.dispatchByRR())
}

// caller of this must hold the lock
func (d *Dispatcher) dispatchByRR() *queue.Queue {
	if len(d.avail) == 0 {
		return d.dispatchByRandom()
	}

	if d.nextIndex >= len(d.avail) {
		d.nextIndex = 0
	}

	i := d.nextIndex
	d.nextIndex++
	return d.avail[i]
}

// caller of this must hold the lock
func (d *Dispatcher) dispatchByRandom() *queue.Queue {
	// when there is no available queue, it will choose a queue from all of the registered queues
	if len(d.avail) > 0 {
		return d.avail[d.rand.Intn(len(d.avail))]
	}
	return d.queues[d.rand.Intn(len(d.queues))]
}

// Dispatch dispatches Executor by random.
func (d *Dispatcher) Dispatch() *Executor {
	d.mu.Lock()
	defer d.mu.Unlock()
	return d.dispatch(d.dispatchByRandom())
}

func (d *Dispatcher) dispatch(q *queue.Queue) *Executor {
	return &Executor{
		Queue: q,
		cb:    d.cb[*q.URL],
	}
}

// StartStateMonitor starts the state monitor and it will be blocked until ctx is canceled.
func (d *Dispatcher) StartStateMonitor(ctx context.Context) {
	d.monitor.start(ctx)
}

type monitor struct {
	d *Dispatcher

	mu       sync.Mutex
	curState map[string]circuitbreaker.State
}

func (m *monitor) initState() {
	m.curState = make(map[string]circuitbreaker.State)

	for k, cb := range m.d.cb {
		m.curState[k] = cb.State()
		//log.Printf("%s: init %s", n, m.curState[n])
	}
}

func (m *monitor) start(ctx context.Context) {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			for _, q := range m.d.queues {
				m.checkState(q)
			}
		}
	}
}

func (m *monitor) checkState(q *queue.Queue) {
	m.mu.Lock()
	defer m.mu.Unlock()

	k := *q.URL
	prev := m.curState[k]
	cur := m.d.cb[k].State()

	if prev != cur {
		m.d.handleStateChange(q, prev, cur)
		m.curState[k] = cur
	}
}

// Executor is a wrapper of *queue.Queue with the circuit breaker.
type Executor struct {
	*queue.Queue

	cb *circuitbreaker.CircuitBreaker
}

// Do allows you to call req under the circuit breaker.
func (e *Executor) Do(ctx context.Context, req func() (interface{}, error)) (interface{}, error) {
	return e.cb.Do(ctx, req)
}
<commit_msg>multiqueue: Add GetExecutors() to return all of the registered queues<commit_after>package multiqueue

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/mercari/go-circuitbreaker"
	"github.com/nabeken/aws-go-sqs/v3/queue"
)

// Dispatcher manages multiple *queue.Queue instances with circit breaker and dispatches it by random or round-robin.
// Circuit breaker is installed per queue. Dispatcher doesn't dispatch a queue while the circuit breaker is open.
type Dispatcher struct {
	// circuit breaker for each queue
	cb            map[string]*circuitbreaker.CircuitBreaker
	onStateChange func(q *queue.Queue, oldState, newState circuitbreaker.State)

	monitor *monitor

	rand *rand.Rand

	// protect queues
	mu sync.Mutex
	// all of the registered queues
	queues []*queue.Queue
	// queues believed to be available
	avail []*queue.Queue
	// index to a queue which will be dispatched next
	nextIndex int
}

// WithOnStateChange installs a hook which will be invoked when the state of the circuit breaker is changed.
func (d *Dispatcher) WithOnStateChange(f func(*queue.Queue, circuitbreaker.State, circuitbreaker.State)) *Dispatcher {
	d.onStateChange = f
	return d
}

// New creates a dispatcher with mercari/go-circuitbreaker enabled per queue.
func New(cbOpts *circuitbreaker.Options, queues ...*queue.Queue) *Dispatcher {
	if len(queues) == 0 {
		panic("at least one queue is required")
	}

	avail := make([]*queue.Queue, len(queues))
	copy(avail, queues)

	d := &Dispatcher{
		queues: queues,
		avail:  avail,
		rand:   rand.New(rand.NewSource(time.Now().UnixNano())),
	}

	d.buildCircuitBreaker(cbOpts)
	d.buildMonitor()

	return d
}

func (d *Dispatcher) GetExecutors() []*Executor {
	var execs []*Executor
	for i := range d.queues {
		execs = append(execs, d.dispatch(d.queues[i]))
	}
	return execs
}

func (d *Dispatcher) buildCircuitBreaker(opts *circuitbreaker.Options) {
	cb := map[string]*circuitbreaker.CircuitBreaker{}
	for i := range d.queues {
		q := d.queues[i]
		cb[*q.URL] = circuitbreaker.New(&circuitbreaker.Options{
			Interval:              opts.Interval,
			OpenTimeout:           opts.OpenTimeout,
			OpenBackOff:           opts.OpenBackOff,
			HalfOpenMaxSuccesses:  opts.HalfOpenMaxSuccesses,
			ShouldTrip:            opts.ShouldTrip,
			FailOnContextCancel:   opts.FailOnContextCancel,
			FailOnContextDeadline: opts.FailOnContextDeadline,
		})
	}
	d.cb = cb
}

func (d *Dispatcher) buildMonitor() {
	mon := &monitor{d: d}
	mon.initState()
	d.monitor = mon
}

func (d *Dispatcher) markUnavailable(q *queue.Queue) {
	d.mu.Lock()
	defer d.mu.Unlock()

	var newAvail []*queue.Queue
	for i := range d.avail {
		if *q.URL != *d.avail[i].URL {
			newAvail = append(newAvail, d.avail[i])
		}
	}

	d.nextIndex = 0
	d.avail = newAvail
}

func (d *Dispatcher) markAvailable(q *queue.Queue) {
	d.mu.Lock()
	defer d.mu.Unlock()
	for i := range d.avail {
		if *q.URL == *d.avail[i].URL {
			// it exists
			return
		}
	}

	d.nextIndex = 0
	d.avail = append(d.avail, q)
}

func (d *Dispatcher) handleStateChange(q *queue.Queue, prev, cur circuitbreaker.State) {
	if f := d.onStateChange; f != nil {
		f(q, prev, cur)
	}

	switch cur {
	case circuitbreaker.StateOpen:
		d.markUnavailable(q)
	case circuitbreaker.StateHalfOpen, circuitbreaker.StateClosed:
		d.markAvailable(q)
	default:
		panic(fmt.Sprintf("unknown state: %s -> %s", prev, cur))
	}
}

// DispatchByRR dispatches Executor by round-robin fasion.
func (d *Dispatcher) DispatchByRR() *Executor {
	d.mu.Lock()
	defer d.mu.Unlock()
	return d.dispatch(d.dispatchByRR())
}

// caller of this must hold the lock
func (d *Dispatcher) dispatchByRR() *queue.Queue {
	if len(d.avail) == 0 {
		return d.dispatchByRandom()
	}

	if d.nextIndex >= len(d.avail) {
		d.nextIndex = 0
	}

	i := d.nextIndex
	d.nextIndex++
	return d.avail[i]
}

// caller of this must hold the lock
func (d *Dispatcher) dispatchByRandom() *queue.Queue {
	// when there is no available queue, it will choose a queue from all of the registered queues
	if len(d.avail) > 0 {
		return d.avail[d.rand.Intn(len(d.avail))]
	}
	return d.queues[d.rand.Intn(len(d.queues))]
}

// Dispatch dispatches Executor by random.
func (d *Dispatcher) Dispatch() *Executor {
	d.mu.Lock()
	defer d.mu.Unlock()
	return d.dispatch(d.dispatchByRandom())
}

func (d *Dispatcher) dispatch(q *queue.Queue) *Executor {
	return &Executor{
		Queue: q,
		cb:    d.cb[*q.URL],
	}
}

// StartStateMonitor starts the state monitor and it will be blocked until ctx is canceled.
func (d *Dispatcher) StartStateMonitor(ctx context.Context) {
	d.monitor.start(ctx)
}

type monitor struct {
	d *Dispatcher

	mu       sync.Mutex
	curState map[string]circuitbreaker.State
}

func (m *monitor) initState() {
	m.curState = make(map[string]circuitbreaker.State)

	for k, cb := range m.d.cb {
		m.curState[k] = cb.State()
		//log.Printf("%s: init %s", n, m.curState[n])
	}
}

func (m *monitor) start(ctx context.Context) {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			for _, q := range m.d.queues {
				m.checkState(q)
			}
		}
	}
}

func (m *monitor) checkState(q *queue.Queue) {
	m.mu.Lock()
	defer m.mu.Unlock()

	k := *q.URL
	prev := m.curState[k]
	cur := m.d.cb[k].State()

	if prev != cur {
		m.d.handleStateChange(q, prev, cur)
		m.curState[k] = cur
	}
}

// Executor is a wrapper of *queue.Queue with the circuit breaker.
type Executor struct {
	*queue.Queue

	cb *circuitbreaker.CircuitBreaker
}

// Do allows you to call req under the circuit breaker.
func (e *Executor) Do(ctx context.Context, req func() (interface{}, error)) (interface{}, error) {
	return e.cb.Do(ctx, req)
}
<|endoftext|>


<commit_before>package raymond

import (
	"io/ioutil"
	"path"
	"regexp"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"
)

//
// Note that, as the JS implementation, we do not support:
//   - support alternative delimeters
//   - the mustache lambda spec
//

type mustacheTest struct {
	Name     string
	Desc     string
	Data     interface{}
	Template string
	Expected string
	Partials map[string]string
}

type mustacheTestFile struct {
	Overview string
	Tests    []mustacheTest
}

var (
	rAltDelim = regexp.MustCompile(regexp.QuoteMeta("{{="))
)

func TestMustache(t *testing.T) {
	skipFiles := map[string]bool{
		"partials.yml": true,
		"sections.yml": true,
		"~lambdas.yml": true,
	}

	for _, fileName := range mustacheTestFiles() {
		if skipFiles[fileName] {
			// fmt.Printf("Skipped file: %s\n", fileName)
			continue
		}

		launchMustacheTests(t, testsFromMustacheFile(fileName))
	}
}

func testsFromMustacheFile(fileName string) []raymondTest {
	result := []raymondTest{}

	fileData, err := ioutil.ReadFile(path.Join("mustache", "specs", fileName))
	if err != nil {
		panic(err)
	}

	var testFile mustacheTestFile
	if err := yaml.Unmarshal(fileData, &testFile); err != nil {
		panic(err)
	}

	for _, mustacheTest := range testFile.Tests {
		if mustBeSkipped(mustacheTest) {
			// fmt.Printf("Skipped test: %s\n", mustacheTest.Name)
			continue
		}

		test := raymondTest{
			name:   mustacheTest.Name,
			input:  mustacheTest.Template,
			data:   mustacheTest.Data,
			output: mustacheTest.Expected,
		}

		result = append(result, test)
	}

	return result
}

// returns true if test must be skipped
func mustBeSkipped(test mustacheTest) bool {
	// @todo Skip partials tests "Failed Lookup" and "Standalone Indentation"
	return haveAltDelimiter(test)
}

// returns true if test have alternative delimeter in template or in partials
func haveAltDelimiter(test mustacheTest) bool {
	// check template
	if rAltDelim.MatchString(test.Template) {
		return true
	}

	// check partials
	for _, partial := range test.Partials {
		if rAltDelim.MatchString(partial) {
			return true
		}
	}

	return false
}

func mustacheTestFiles() []string {
	var result []string

	files, err := ioutil.ReadDir(path.Join("mustache", "specs"))
	if err != nil {
		panic(err)
	}

	for _, file := range files {
		fileName := file.Name()

		if !file.IsDir() && strings.HasSuffix(fileName, ".yml") {
			result = append(result, fileName)
		}
	}

	return result
}
<commit_msg>Enables sections.yml mustache tests<commit_after>package raymond

import (
	"io/ioutil"
	"path"
	"regexp"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"
)

//
// Note that, as the JS implementation, we do not support:
//   - support alternative delimeters
//   - the mustache lambda spec
//

type mustacheTest struct {
	Name     string
	Desc     string
	Data     interface{}
	Template string
	Expected string
	Partials map[string]string
}

type mustacheTestFile struct {
	Overview string
	Tests    []mustacheTest
}

var (
	rAltDelim = regexp.MustCompile(regexp.QuoteMeta("{{="))
)

func TestMustache(t *testing.T) {
	skipFiles := map[string]bool{
		"partials.yml": true,
		"~lambdas.yml": true,
	}

	for _, fileName := range mustacheTestFiles() {
		if skipFiles[fileName] {
			// fmt.Printf("Skipped file: %s\n", fileName)
			continue
		}

		launchMustacheTests(t, testsFromMustacheFile(fileName))
	}
}

func testsFromMustacheFile(fileName string) []raymondTest {
	result := []raymondTest{}

	fileData, err := ioutil.ReadFile(path.Join("mustache", "specs", fileName))
	if err != nil {
		panic(err)
	}

	var testFile mustacheTestFile
	if err := yaml.Unmarshal(fileData, &testFile); err != nil {
		panic(err)
	}

	for _, mustacheTest := range testFile.Tests {
		if mustBeSkipped(mustacheTest) {
			// fmt.Printf("Skipped test: %s\n", mustacheTest.Name)
			continue
		}

		test := raymondTest{
			name:   mustacheTest.Name,
			input:  mustacheTest.Template,
			data:   mustacheTest.Data,
			output: mustacheTest.Expected,
		}

		result = append(result, test)
	}

	return result
}

// returns true if test must be skipped
func mustBeSkipped(test mustacheTest) bool {
	// @todo Skip partials tests "Failed Lookup" and "Standalone Indentation"
	return haveAltDelimiter(test)
}

// returns true if test have alternative delimeter in template or in partials
func haveAltDelimiter(test mustacheTest) bool {
	// check template
	if rAltDelim.MatchString(test.Template) {
		return true
	}

	// check partials
	for _, partial := range test.Partials {
		if rAltDelim.MatchString(partial) {
			return true
		}
	}

	return false
}

func mustacheTestFiles() []string {
	var result []string

	files, err := ioutil.ReadDir(path.Join("mustache", "specs"))
	if err != nil {
		panic(err)
	}

	for _, file := range files {
		fileName := file.Name()

		if !file.IsDir() && strings.HasSuffix(fileName, ".yml") {
			result = append(result, fileName)
		}
	}

	return result
}
<|endoftext|>


<commit_before>package interfaces

import (
	"fmt"
	"strings"

	"github.com/webitel/cdr/src/conf"
	"github.com/webitel/cdr/src/entity"
)

const (
	cdrInsertQueryB = "INSERT INTO #table#(uuid, parent_uuid, created_at, stored_at, archived_at, size, event, stored_state, archived_state) VALUES "
	cdrInsertQueryA = "INSERT INTO #table#(uuid, created_at, stored_at, archived_at, size, event, stored_state, archived_state) VALUES "
	cdrValuesB      = "(%v, %v, %v, %v, %v, %v, %v, %v, %v),"
	cdrValuesA      = "(%v, %v, %v, %v, %v, %v, %v, %v),"
	// cdrSelectByState        = "SELECT uuid, event FROM #table# WHERE #state#_state=$1 ORDER BY created_at ASC LIMIT $2"
	// cdrSelectByStateB       = "SELECT uuid, event FROM #table# WHERE #state#_state=$1 AND parent_uuid != '' ORDER BY created_at ASC LIMIT $2"
	cdrUpdateWithReturning  = "UPDATE #table# SET #state#_state = 1 WHERE uuid IN ( SELECT uuid FROM #table# WHERE #state#_state = $1 ORDER BY created_at LIMIT $2 ) RETURNING uuid, event"
	cdrUpdateWithReturningB = "UPDATE #table# SET #state#_state = 1 WHERE uuid IN ( SELECT uuid FROM #table# WHERE #state#_state = $1 AND parent_uuid != '' ORDER BY created_at LIMIT $2 ) RETURNING uuid, event"
	cdrJoin                 = "SELECT a.uuid as parent_uuid, b.event as event, b.uuid as uuid FROM #table_a# as a INNER JOIN #table_b# as b ON a.uuid = b.parent_uuid WHERE a.stored_state=$1 AND b.stored_state=$2 ORDER BY b.created_at ASC LIMIT $3"
	cdrUpdateStateQuery     = "UPDATE #table# SET #state#_state=$1, #state#_at=$2 WHERE uuid IN (#values#)"
	cdrCreateTableA         = `
							CREATE TABLE IF NOT EXISTS #table#
							(
								uuid character varying(255) COLLATE pg_catalog."default" NOT NULL,
								created_at bigint,
								stored_at bigint,
								archived_at bigint,
								size integer,
								event bytea,
								stored_state smallint,
								archived_state smallint,
								CONSTRAINT #table#_pkey PRIMARY KEY (uuid)
							)
							WITH (
								OIDS = FALSE
							)
							TABLESPACE pg_default;

							ALTER TABLE #table#
								OWNER to #user#;

							create index if not exists #table#_created_at_stored_state_index
								on #table# (created_at, stored_state)
							;

							create index if not exists #table#_created_at_archived_state_index
								on #table# (created_at, archived_state)
							;
						` //$1 - public.cdr $2 - webitel
	cdrCreateTableB = `
						CREATE TABLE IF NOT EXISTS #table#
						(
							uuid character varying(255) COLLATE pg_catalog."default" NOT NULL,
							parent_uuid character varying(255) COLLATE pg_catalog."default",
							created_at bigint,
							stored_at bigint,
							archived_at bigint,
							size integer,
							event bytea,
							stored_state smallint,
							archived_state smallint,
							CONSTRAINT #table#_pkey PRIMARY KEY (uuid)
						)
						WITH (
							OIDS = FALSE
						)
						TABLESPACE pg_default;

						ALTER TABLE #table#
							OWNER to #user#;

						create index if not exists #table#_created_at_stored_state_index
							on #table# (created_at, stored_state)
						;

						create index if not exists #table#_created_at_archived_state_index
							on #table# (created_at, archived_state)
						;
					` //$1 - public.cdr $2 - webitel
)

var config conf.Postgres

func InitConfig() {
	config = conf.GetPostgres()
}

type DbHandler interface {
	ExecuteQuery(query string, params ...interface{}) error
	GetRows(query string, params ...interface{}) (Row, error)
	CreateTable(query string) error
}

type Row interface {
	Scan(dest ...interface{}) error
	Next() bool
	Close() error
}

type DbRepo struct {
	dbHandlers map[string]DbHandler
	dbHandler  DbHandler
}

type DbCdrARepo DbRepo
type DbCdrBRepo DbRepo

func NewDbCdrARepo(dbHandlers map[string]DbHandler) *DbCdrARepo {
	DbCdrARepo := new(DbCdrARepo)
	DbCdrARepo.dbHandlers = dbHandlers
	DbCdrARepo.dbHandler = dbHandlers["DbCdrARepo"]
	return DbCdrARepo
}

func NewDbCdrBRepo(dbHandlers map[string]DbHandler) *DbCdrBRepo {
	DbCdrBRepo := new(DbCdrBRepo)
	DbCdrBRepo.dbHandlers = dbHandlers
	DbCdrBRepo.dbHandler = dbHandlers["DbCdrBRepo"]
	return DbCdrBRepo
}

func (repo *DbCdrARepo) InsertPack(calls []entity.SqlCdr) error {
	sqlStr := strings.Replace(cdrInsertQueryA, "#table#", config.TableA, -1)
	vals := []interface{}{}
	var strValues string
	valCounter := 1
	for _, row := range calls {
		strValues = fmt.Sprintf(cdrValuesA,
			fmt.Sprintf("$%v", valCounter),
			fmt.Sprintf("$%v", valCounter+1),
			fmt.Sprintf("$%v", valCounter+2),
			fmt.Sprintf("$%v", valCounter+3),
			fmt.Sprintf("$%v", valCounter+4),
			fmt.Sprintf("$%v", valCounter+5),
			fmt.Sprintf("$%v", valCounter+6),
			fmt.Sprintf("$%v", valCounter+7))
		sqlStr += strValues
		vals = append(vals,
			row.Uuid,
			row.Created_at,
			row.Stored_at,
			row.Archived_at,
			row.Size,
			row.Event,
			row.Stored_state,
			row.Archived_state)
		valCounter = valCounter + 8
	}
	sqlStr = sqlStr[0 : len(sqlStr)-1]
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrARepo) SelectPackByState(count uint32, state uint8, stateName string) ([]entity.SqlCdr, error) {
	rows, err := repo.dbHandler.GetRows(strings.Replace(strings.Replace(cdrUpdateWithReturning, "#table#", config.TableA, -1), "#state#", stateName, -1), state, count)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var cdr []entity.SqlCdr
	var call entity.SqlCdr
	for rows.Next() {
		err = rows.Scan(&call.Uuid, &call.Event)
		if err != nil {
			return nil, err
		}
		cdr = append(cdr, call)
	}
	return cdr, nil
}

func (repo *DbCdrARepo) JoinLegsPack(count uint32) ([]entity.SqlCdr, error) {
	panic(count)
}

func (repo *DbCdrARepo) UpdateState(calls []entity.SqlCdr, state uint8, timestamp uint64, stateName string) error {
	sqlStr := strings.Replace(strings.Replace(cdrUpdateStateQuery, "#table#", config.TableA, -1), "#state#", stateName, -1)
	vals := []interface{}{}
	vals = append(vals, state, timestamp) //uint64(time.Now().UnixNano()/1000000)
	var strValues string
	for i, row := range calls {
		strValues += fmt.Sprintf("$%v, ", i+3)
		vals = append(vals, row.Uuid)
	}
	strValues = strValues[0 : len(strValues)-2]
	sqlStr = strings.Replace(sqlStr, "#values#", strValues, -1)
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrBRepo) InsertPack(calls []entity.SqlCdr) error {
	sqlStr := strings.Replace(cdrInsertQueryB, "#table#", config.TableB, -1)
	vals := []interface{}{}
	var strValues string
	valCounter := 1
	for _, row := range calls {
		strValues = fmt.Sprintf(cdrValuesB,
			fmt.Sprintf("$%v", valCounter),
			fmt.Sprintf("$%v", valCounter+1),
			fmt.Sprintf("$%v", valCounter+2),
			fmt.Sprintf("$%v", valCounter+3),
			fmt.Sprintf("$%v", valCounter+4),
			fmt.Sprintf("$%v", valCounter+5),
			fmt.Sprintf("$%v", valCounter+6),
			fmt.Sprintf("$%v", valCounter+7),
			fmt.Sprintf("$%v", valCounter+8))
		sqlStr += strValues
		vals = append(vals,
			row.Uuid,
			row.Parent_uuid,
			row.Created_at,
			row.Stored_at,
			row.Archived_at,
			row.Size,
			row.Event,
			row.Stored_state,
			row.Archived_state)
		valCounter = valCounter + 9
	}
	sqlStr = sqlStr[0 : len(sqlStr)-1]
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrARepo) CreateTableIfNotExist() error {
	sqlStr := strings.Replace(strings.Replace(cdrCreateTableA, "#table#", config.TableA, -1), "#user#", config.User, -1)
	return repo.dbHandler.CreateTable(sqlStr)
}

func (repo *DbCdrBRepo) SelectPackByState(count uint32, state uint8, stateName string) ([]entity.SqlCdr, error) {
	rows, err := repo.dbHandler.GetRows(strings.Replace(strings.Replace(cdrUpdateWithReturningB, "#table#", config.TableB, -1), "#state#", stateName, -1), state, count)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var cdr []entity.SqlCdr
	var call entity.SqlCdr
	for rows.Next() {
		err = rows.Scan(&call.Uuid, &call.Event)
		if err != nil {
			return nil, err
		}
		cdr = append(cdr, call)
	}
	return cdr, nil
}

func (repo *DbCdrBRepo) JoinLegsPack(count uint32) ([]entity.SqlCdr, error) {
	rows, err := repo.dbHandler.GetRows(strings.Replace(strings.Replace(cdrJoin, "#table_a#", config.TableA, -1), "#table_b#", config.TableB, -1), 2, 0, count)
	if err != nil {
		return nil, err
	}
	var cdr []entity.SqlCdr
	var call entity.SqlCdr
	for rows.Next() {
		err = rows.Scan(&call.Parent_uuid, &call.Event, &call.Uuid)
		if err != nil {
			return nil, err
		}
		cdr = append(cdr, call)
	}
	return cdr, nil
}

func (repo *DbCdrBRepo) UpdateState(calls []entity.SqlCdr, state uint8, timestamp uint64, stateName string) error {
	sqlStr := strings.Replace(strings.Replace(cdrUpdateStateQuery, "#table#", config.TableB, -1), "#state#", stateName, -1)
	vals := []interface{}{}
	vals = append(vals, state, timestamp) //uint64(time.Now().UnixNano()/1000000)
	var strValues string
	for i, row := range calls {
		strValues += fmt.Sprintf("$%v, ", i+3)
		vals = append(vals, row.Uuid)
	}
	strValues = strValues[0 : len(strValues)-2]
	sqlStr = strings.Replace(sqlStr, "#values#", strValues, -1)
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrBRepo) CreateTableIfNotExist() error {
	sqlStr := strings.Replace(strings.Replace(cdrCreateTableB, "#table#", config.TableB, -1), "#user#", config.User, -1)
	return repo.dbHandler.CreateTable(sqlStr)
}
<commit_msg>change pg event: bytea to json<commit_after>package interfaces

import (
	"fmt"
	"strings"

	"github.com/webitel/cdr/src/conf"
	"github.com/webitel/cdr/src/entity"
)

const (
	cdrInsertQueryB = "INSERT INTO #table#(uuid, parent_uuid, created_at, stored_at, archived_at, size, event, stored_state, archived_state) VALUES "
	cdrInsertQueryA = "INSERT INTO #table#(uuid, created_at, stored_at, archived_at, size, event, stored_state, archived_state) VALUES "
	cdrValuesB      = "(%v, %v, %v, %v, %v, %v, %v, %v, %v),"
	cdrValuesA      = "(%v, %v, %v, %v, %v, %v, %v, %v),"
	// cdrSelectByState        = "SELECT uuid, event FROM #table# WHERE #state#_state=$1 ORDER BY created_at ASC LIMIT $2"
	// cdrSelectByStateB       = "SELECT uuid, event FROM #table# WHERE #state#_state=$1 AND parent_uuid != '' ORDER BY created_at ASC LIMIT $2"
	cdrUpdateWithReturning  = "UPDATE #table# SET #state#_state = 1 WHERE uuid IN ( SELECT uuid FROM #table# WHERE #state#_state = $1 ORDER BY created_at LIMIT $2 ) RETURNING uuid, event"
	cdrUpdateWithReturningB = "UPDATE #table# SET #state#_state = 1 WHERE uuid IN ( SELECT uuid FROM #table# WHERE #state#_state = $1 AND parent_uuid != '' ORDER BY created_at LIMIT $2 ) RETURNING uuid, event"
	cdrJoin                 = "SELECT a.uuid as parent_uuid, b.event as event, b.uuid as uuid FROM #table_a# as a INNER JOIN #table_b# as b ON a.uuid = b.parent_uuid WHERE a.stored_state=$1 AND b.stored_state=$2 ORDER BY b.created_at ASC LIMIT $3"
	cdrUpdateStateQuery     = "UPDATE #table# SET #state#_state=$1, #state#_at=$2 WHERE uuid IN (#values#)"
	cdrCreateTableA         = `
							CREATE TABLE IF NOT EXISTS #table#
							(
								uuid character varying(255) COLLATE pg_catalog."default" NOT NULL,
								created_at bigint,
								stored_at bigint,
								archived_at bigint,
								size integer,
								event json,
								stored_state smallint,
								archived_state smallint,
								CONSTRAINT #table#_pkey PRIMARY KEY (uuid)
							)
							WITH (
								OIDS = FALSE
							)
							TABLESPACE pg_default;

							ALTER TABLE #table#
								OWNER to #user#;

							create index if not exists #table#_created_at_stored_state_index
								on #table# (created_at, stored_state)
							;

							create index if not exists #table#_created_at_archived_state_index
								on #table# (created_at, archived_state)
							;
						` //$1 - public.cdr $2 - webitel
	cdrCreateTableB = `
						CREATE TABLE IF NOT EXISTS #table#
						(
							uuid character varying(255) COLLATE pg_catalog."default" NOT NULL,
							parent_uuid character varying(255) COLLATE pg_catalog."default",
							created_at bigint,
							stored_at bigint,
							archived_at bigint,
							size integer,
							event json,
							stored_state smallint,
							archived_state smallint,
							CONSTRAINT #table#_pkey PRIMARY KEY (uuid)
						)
						WITH (
							OIDS = FALSE
						)
						TABLESPACE pg_default;

						ALTER TABLE #table#
							OWNER to #user#;

						create index if not exists #table#_created_at_stored_state_index
							on #table# (created_at, stored_state)
						;

						create index if not exists #table#_created_at_archived_state_index
							on #table# (created_at, archived_state)
						;
					` //$1 - public.cdr $2 - webitel
)

var config conf.Postgres

func InitConfig() {
	config = conf.GetPostgres()
}

type DbHandler interface {
	ExecuteQuery(query string, params ...interface{}) error
	GetRows(query string, params ...interface{}) (Row, error)
	CreateTable(query string) error
}

type Row interface {
	Scan(dest ...interface{}) error
	Next() bool
	Close() error
}

type DbRepo struct {
	dbHandlers map[string]DbHandler
	dbHandler  DbHandler
}

type DbCdrARepo DbRepo
type DbCdrBRepo DbRepo

func NewDbCdrARepo(dbHandlers map[string]DbHandler) *DbCdrARepo {
	DbCdrARepo := new(DbCdrARepo)
	DbCdrARepo.dbHandlers = dbHandlers
	DbCdrARepo.dbHandler = dbHandlers["DbCdrARepo"]
	return DbCdrARepo
}

func NewDbCdrBRepo(dbHandlers map[string]DbHandler) *DbCdrBRepo {
	DbCdrBRepo := new(DbCdrBRepo)
	DbCdrBRepo.dbHandlers = dbHandlers
	DbCdrBRepo.dbHandler = dbHandlers["DbCdrBRepo"]
	return DbCdrBRepo
}

func (repo *DbCdrARepo) InsertPack(calls []entity.SqlCdr) error {
	sqlStr := strings.Replace(cdrInsertQueryA, "#table#", config.TableA, -1)
	vals := []interface{}{}
	var strValues string
	valCounter := 1
	for _, row := range calls {
		strValues = fmt.Sprintf(cdrValuesA,
			fmt.Sprintf("$%v", valCounter),
			fmt.Sprintf("$%v", valCounter+1),
			fmt.Sprintf("$%v", valCounter+2),
			fmt.Sprintf("$%v", valCounter+3),
			fmt.Sprintf("$%v", valCounter+4),
			fmt.Sprintf("$%v", valCounter+5),
			fmt.Sprintf("$%v", valCounter+6),
			fmt.Sprintf("$%v", valCounter+7))
		sqlStr += strValues
		vals = append(vals,
			row.Uuid,
			row.Created_at,
			row.Stored_at,
			row.Archived_at,
			row.Size,
			row.Event,
			row.Stored_state,
			row.Archived_state)
		valCounter = valCounter + 8
	}
	sqlStr = sqlStr[0 : len(sqlStr)-1]
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrARepo) SelectPackByState(count uint32, state uint8, stateName string) ([]entity.SqlCdr, error) {
	rows, err := repo.dbHandler.GetRows(strings.Replace(strings.Replace(cdrUpdateWithReturning, "#table#", config.TableA, -1), "#state#", stateName, -1), state, count)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var cdr []entity.SqlCdr
	var call entity.SqlCdr
	for rows.Next() {
		err = rows.Scan(&call.Uuid, &call.Event)
		if err != nil {
			return nil, err
		}
		cdr = append(cdr, call)
	}
	return cdr, nil
}

func (repo *DbCdrARepo) JoinLegsPack(count uint32) ([]entity.SqlCdr, error) {
	panic(count)
}

func (repo *DbCdrARepo) UpdateState(calls []entity.SqlCdr, state uint8, timestamp uint64, stateName string) error {
	sqlStr := strings.Replace(strings.Replace(cdrUpdateStateQuery, "#table#", config.TableA, -1), "#state#", stateName, -1)
	vals := []interface{}{}
	vals = append(vals, state, timestamp) //uint64(time.Now().UnixNano()/1000000)
	var strValues string
	for i, row := range calls {
		strValues += fmt.Sprintf("$%v, ", i+3)
		vals = append(vals, row.Uuid)
	}
	strValues = strValues[0 : len(strValues)-2]
	sqlStr = strings.Replace(sqlStr, "#values#", strValues, -1)
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrBRepo) InsertPack(calls []entity.SqlCdr) error {
	sqlStr := strings.Replace(cdrInsertQueryB, "#table#", config.TableB, -1)
	vals := []interface{}{}
	var strValues string
	valCounter := 1
	for _, row := range calls {
		strValues = fmt.Sprintf(cdrValuesB,
			fmt.Sprintf("$%v", valCounter),
			fmt.Sprintf("$%v", valCounter+1),
			fmt.Sprintf("$%v", valCounter+2),
			fmt.Sprintf("$%v", valCounter+3),
			fmt.Sprintf("$%v", valCounter+4),
			fmt.Sprintf("$%v", valCounter+5),
			fmt.Sprintf("$%v", valCounter+6),
			fmt.Sprintf("$%v", valCounter+7),
			fmt.Sprintf("$%v", valCounter+8))
		sqlStr += strValues
		vals = append(vals,
			row.Uuid,
			row.Parent_uuid,
			row.Created_at,
			row.Stored_at,
			row.Archived_at,
			row.Size,
			row.Event,
			row.Stored_state,
			row.Archived_state)
		valCounter = valCounter + 9
	}
	sqlStr = sqlStr[0 : len(sqlStr)-1]
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrARepo) CreateTableIfNotExist() error {
	sqlStr := strings.Replace(strings.Replace(cdrCreateTableA, "#table#", config.TableA, -1), "#user#", config.User, -1)
	return repo.dbHandler.CreateTable(sqlStr)
}

func (repo *DbCdrBRepo) SelectPackByState(count uint32, state uint8, stateName string) ([]entity.SqlCdr, error) {
	rows, err := repo.dbHandler.GetRows(strings.Replace(strings.Replace(cdrUpdateWithReturningB, "#table#", config.TableB, -1), "#state#", stateName, -1), state, count)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var cdr []entity.SqlCdr
	var call entity.SqlCdr
	for rows.Next() {
		err = rows.Scan(&call.Uuid, &call.Event)
		if err != nil {
			return nil, err
		}
		cdr = append(cdr, call)
	}
	return cdr, nil
}

func (repo *DbCdrBRepo) JoinLegsPack(count uint32) ([]entity.SqlCdr, error) {
	rows, err := repo.dbHandler.GetRows(strings.Replace(strings.Replace(cdrJoin, "#table_a#", config.TableA, -1), "#table_b#", config.TableB, -1), 2, 0, count)
	if err != nil {
		return nil, err
	}
	var cdr []entity.SqlCdr
	var call entity.SqlCdr
	for rows.Next() {
		err = rows.Scan(&call.Parent_uuid, &call.Event, &call.Uuid)
		if err != nil {
			return nil, err
		}
		cdr = append(cdr, call)
	}
	return cdr, nil
}

func (repo *DbCdrBRepo) UpdateState(calls []entity.SqlCdr, state uint8, timestamp uint64, stateName string) error {
	sqlStr := strings.Replace(strings.Replace(cdrUpdateStateQuery, "#table#", config.TableB, -1), "#state#", stateName, -1)
	vals := []interface{}{}
	vals = append(vals, state, timestamp) //uint64(time.Now().UnixNano()/1000000)
	var strValues string
	for i, row := range calls {
		strValues += fmt.Sprintf("$%v, ", i+3)
		vals = append(vals, row.Uuid)
	}
	strValues = strValues[0 : len(strValues)-2]
	sqlStr = strings.Replace(sqlStr, "#values#", strValues, -1)
	return repo.dbHandler.ExecuteQuery(sqlStr, vals...)
}

func (repo *DbCdrBRepo) CreateTableIfNotExist() error {
	sqlStr := strings.Replace(strings.Replace(cdrCreateTableB, "#table#", config.TableB, -1), "#user#", config.User, -1)
	return repo.dbHandler.CreateTable(sqlStr)
}
<|endoftext|>