<commit_before>package xpath

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

// The XPath function list.

func predicate(q query) func(NodeNavigator) bool {
	type Predicater interface {
		Test(NodeNavigator) bool
	}
	if p, ok := q.(Predicater); ok {
		return p.Test
	}
	return func(NodeNavigator) bool { return true }
}

// positionFunc is a XPath Node Set functions position().
func positionFunc(q query, t iterator) interface{} {
	var (
		count = 1
		node  = t.Current()
	)
	test := predicate(q)
	for node.MoveToPrevious() {
		if test(node) {
			count++
		}
	}
	return float64(count)
}

// lastFunc is a XPath Node Set functions last().
func lastFunc(q query, t iterator) interface{} {
	var (
		count = 0
		node  = t.Current()
	)
	node.MoveToFirst()
	test := predicate(q)
	for {
		if test(node) {
			count++
		}
		if !node.MoveToNext() {
			break
		}
	}
	return float64(count)
}

// countFunc is a XPath Node Set functions count(node-set).
func countFunc(q query, t iterator) interface{} {
	var count = 0
	test := predicate(q)
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if test(node) {
				count++
			}
		}
	}
	return float64(count)
}

// sumFunc is a XPath Node Set functions sum(node-set).
func sumFunc(q query, t iterator) interface{} {
	var sum float64
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
				sum += v
			}
		}
	case float64:
		sum = typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("sum() function argument type must be a node-set or number"))
		}
		sum = v
	}
	return sum
}

func asNumber(t iterator, o interface{}) float64 {
	switch typ := o.(type) {
	case query:
		node := typ.Select(t)
		if node == nil {
			return float64(0)
		}
		if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
			return v
		}
	case float64:
		return typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("ceiling() function argument type must be a node-set or number"))
		}
		return v
	}
	return 0
}

// ceilingFunc is a XPath Node Set functions ceiling(node-set).
func ceilingFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Ceil(val)
}

// floorFunc is a XPath Node Set functions floor(node-set).
func floorFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Floor(val)
}

// math.Round() is supported by Go 1.9x,
// This method just compatible for version <1.9x.
// https://github.com/golang/go/issues/4594
func round(f float64) int {
	if math.Abs(f) < 0.5 {
		return 0
	}
	return int(f + math.Copysign(0.5, f))
}

// roundFunc is a XPath Node Set functions round(node-set).
func roundFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	// math.Round() supported by Go 1.9x,
	//return math.Round(val)
	return round(val)
}

// nameFunc is a XPath functions name([node-set]).
func nameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	ns := v.Prefix()
	if ns == "" {
		return v.LocalName()
	}
	return ns + ":" + v.LocalName()
}

// localNameFunc is a XPath functions local-name([node-set]).
func localNameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.LocalName()
}

// namespaceFunc is a XPath functions namespace-uri([node-set]).
func namespaceFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.Prefix()
}

func asBool(t iterator, v interface{}) bool {
	switch v := v.(type) {
	case nil:
		return false
	case *NodeIterator:
		return v.MoveNext()
	case bool:
		return bool(v)
	case float64:
		return v == 0
	case string:
		return v != ""
	case query:
		return v.Select(t) != nil
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

func asString(t iterator, v interface{}) string {
	switch v := v.(type) {
	case nil:
		return ""
	case bool:
		if v {
			return "true"
		}
		return "false"
	case float64:
		return strconv.FormatFloat(v, 'g', -1, 64)
	case string:
		return v
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

// booleanFunc is a XPath functions boolean([node-set]).
func booleanFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asBool(t, v)
}

// numberFunc is a XPath functions number([node-set]).
func numberFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asNumber(t, v)
}

// stringFunc is a XPath functions string([node-set]).
func stringFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asString(t, v)
}

// startwithFunc is a XPath functions starts-with(string, string).
func startwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("starts-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("starts-with() function argument type must be string"))
		}
		return strings.HasPrefix(m, n)
	}
}

// endwithFunc is a XPath functions ends-with(string, string).
func endwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("ends-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("ends-with() function argument type must be string"))
		}
		return strings.HasSuffix(m, n)
	}
}

// containsFunc is a XPath functions contains(string or @attr, string).
func containsFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)

		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("contains() function argument type must be string"))
		}

		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("contains() function argument type must be string"))
		}

		return strings.Contains(m, n)
	}
}

// normalizespaceFunc is XPath functions normalize-space(string?)
func normalizespaceFunc(q query, t iterator) interface{} {
	var m string
	switch typ := q.Evaluate(t).(type) {
	case string:
		m = typ
	case query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	}
	return strings.TrimSpace(m)
}

// substringFunc is XPath functions substring function returns a part of a given string.
func substringFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var m string
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return ""
			}
			m = node.Value()
		}

		var start, length float64
		var ok bool

		if start, ok = arg2.Evaluate(t).(float64); !ok {
			panic(errors.New("substring() function first argument type must be int"))
		} else if start < 1 {
			panic(errors.New("substring() function first argument type must be >= 1"))
		}
		start--
		if arg3 != nil {
			if length, ok = arg3.Evaluate(t).(float64); !ok {
				panic(errors.New("substring() function second argument type must be int"))
			}
		}
		if (len(m) - int(start)) < int(length) {
			panic(errors.New("substring() function start and length argument out of range"))
		}
		if length > 0 {
			return m[int(start):int(length+start)]
		}
		return m[int(start):]
	}
}

// substringIndFunc is XPath functions substring-before/substring-after function returns a part of a given string.
func substringIndFunc(arg1, arg2 query, after bool) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var str string
		switch v := arg1.Evaluate(t).(type) {
		case string:
			str = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			str = node.Value()
		}
		var word string
		switch v := arg2.Evaluate(t).(type) {
		case string:
			word = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			word = node.Value()
		}
		if word == "" {
			return ""
		}

		i := strings.Index(str, word)
		if i < 0 {
			return ""
		}
		if after {
			return str[i+len(word):]
		}
		return str[:i]
	}
}

// stringLengthFunc is XPATH string-length( [string] ) function that returns a number
// equal to the number of characters in a given string.
func stringLengthFunc(arg1 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		switch v := arg1.Evaluate(t).(type) {
		case string:
			return float64(len(v))
		case query:
			node := v.Select(t)
			if node == nil {
				break
			}
			return float64(len(node.Value()))
		}
		return float64(0)
	}
}

// translateFunc is XPath functions translate() function returns a replaced string.
func translateFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		str := asString(t, arg1.Evaluate(t))
		src := asString(t, arg2.Evaluate(t))
		dst := asString(t, arg3.Evaluate(t))

		var replace []string
		for i, s := range src {
			d := ""
			if i < len(dst) {
				d = string(dst[i])
			}
			replace = append(replace, string(s), d)
		}
		return strings.NewReplacer(replace...).Replace(str)
	}
}

// notFunc is XPATH functions not(expression) function operation.
func notFunc(q query, t iterator) interface{} {
	switch v := q.Evaluate(t).(type) {
	case bool:
		return !v
	case query:
		node := v.Select(t)
		return node == nil
	default:
		return false
	}
}

// concatFunc is the concat function concatenates two or more
// strings and returns the resulting string.
// concat( string1 , string2 [, stringn]* )
func concatFunc(args ...query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var a []string
		for _, v := range args {
			switch v := v.Evaluate(t).(type) {
			case string:
				a = append(a, v)
			case query:
				node := v.Select(t)
				if node != nil {
					a = append(a, node.Value())
				}
			}
		}
		return strings.Join(a, "")
	}
}
<commit_msg>math.Round(), compatible with Go<1.9x.<commit_after>package xpath

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

// The XPath function list.

func predicate(q query) func(NodeNavigator) bool {
	type Predicater interface {
		Test(NodeNavigator) bool
	}
	if p, ok := q.(Predicater); ok {
		return p.Test
	}
	return func(NodeNavigator) bool { return true }
}

// positionFunc is a XPath Node Set functions position().
func positionFunc(q query, t iterator) interface{} {
	var (
		count = 1
		node  = t.Current()
	)
	test := predicate(q)
	for node.MoveToPrevious() {
		if test(node) {
			count++
		}
	}
	return float64(count)
}

// lastFunc is a XPath Node Set functions last().
func lastFunc(q query, t iterator) interface{} {
	var (
		count = 0
		node  = t.Current()
	)
	node.MoveToFirst()
	test := predicate(q)
	for {
		if test(node) {
			count++
		}
		if !node.MoveToNext() {
			break
		}
	}
	return float64(count)
}

// countFunc is a XPath Node Set functions count(node-set).
func countFunc(q query, t iterator) interface{} {
	var count = 0
	test := predicate(q)
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if test(node) {
				count++
			}
		}
	}
	return float64(count)
}

// sumFunc is a XPath Node Set functions sum(node-set).
func sumFunc(q query, t iterator) interface{} {
	var sum float64
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
				sum += v
			}
		}
	case float64:
		sum = typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("sum() function argument type must be a node-set or number"))
		}
		sum = v
	}
	return sum
}

func asNumber(t iterator, o interface{}) float64 {
	switch typ := o.(type) {
	case query:
		node := typ.Select(t)
		if node == nil {
			return float64(0)
		}
		if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
			return v
		}
	case float64:
		return typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("ceiling() function argument type must be a node-set or number"))
		}
		return v
	}
	return 0
}

// ceilingFunc is a XPath Node Set functions ceiling(node-set).
func ceilingFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Ceil(val)
}

// floorFunc is a XPath Node Set functions floor(node-set).
func floorFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Floor(val)
}

// roundFunc is a XPath Node Set functions round(node-set).
func roundFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Round(val)
}

// nameFunc is a XPath functions name([node-set]).
func nameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	ns := v.Prefix()
	if ns == "" {
		return v.LocalName()
	}
	return ns + ":" + v.LocalName()
}

// localNameFunc is a XPath functions local-name([node-set]).
func localNameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.LocalName()
}

// namespaceFunc is a XPath functions namespace-uri([node-set]).
func namespaceFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.Prefix()
}

func asBool(t iterator, v interface{}) bool {
	switch v := v.(type) {
	case nil:
		return false
	case *NodeIterator:
		return v.MoveNext()
	case bool:
		return bool(v)
	case float64:
		return v == 0
	case string:
		return v != ""
	case query:
		return v.Select(t) != nil
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

func asString(t iterator, v interface{}) string {
	switch v := v.(type) {
	case nil:
		return ""
	case bool:
		if v {
			return "true"
		}
		return "false"
	case float64:
		return strconv.FormatFloat(v, 'g', -1, 64)
	case string:
		return v
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

// booleanFunc is a XPath functions boolean([node-set]).
func booleanFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asBool(t, v)
}

// numberFunc is a XPath functions number([node-set]).
func numberFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asNumber(t, v)
}

// stringFunc is a XPath functions string([node-set]).
func stringFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asString(t, v)
}

// startwithFunc is a XPath functions starts-with(string, string).
func startwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("starts-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("starts-with() function argument type must be string"))
		}
		return strings.HasPrefix(m, n)
	}
}

// endwithFunc is a XPath functions ends-with(string, string).
func endwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("ends-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("ends-with() function argument type must be string"))
		}
		return strings.HasSuffix(m, n)
	}
}

// containsFunc is a XPath functions contains(string or @attr, string).
func containsFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)

		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("contains() function argument type must be string"))
		}

		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("contains() function argument type must be string"))
		}

		return strings.Contains(m, n)
	}
}

// normalizespaceFunc is XPath functions normalize-space(string?)
func normalizespaceFunc(q query, t iterator) interface{} {
	var m string
	switch typ := q.Evaluate(t).(type) {
	case string:
		m = typ
	case query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	}
	return strings.TrimSpace(m)
}

// substringFunc is XPath functions substring function returns a part of a given string.
func substringFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var m string
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return ""
			}
			m = node.Value()
		}

		var start, length float64
		var ok bool

		if start, ok = arg2.Evaluate(t).(float64); !ok {
			panic(errors.New("substring() function first argument type must be int"))
		} else if start < 1 {
			panic(errors.New("substring() function first argument type must be >= 1"))
		}
		start--
		if arg3 != nil {
			if length, ok = arg3.Evaluate(t).(float64); !ok {
				panic(errors.New("substring() function second argument type must be int"))
			}
		}
		if (len(m) - int(start)) < int(length) {
			panic(errors.New("substring() function start and length argument out of range"))
		}
		if length > 0 {
			return m[int(start):int(length+start)]
		}
		return m[int(start):]
	}
}

// substringIndFunc is XPath functions substring-before/substring-after function returns a part of a given string.
func substringIndFunc(arg1, arg2 query, after bool) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var str string
		switch v := arg1.Evaluate(t).(type) {
		case string:
			str = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			str = node.Value()
		}
		var word string
		switch v := arg2.Evaluate(t).(type) {
		case string:
			word = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			word = node.Value()
		}
		if word == "" {
			return ""
		}

		i := strings.Index(str, word)
		if i < 0 {
			return ""
		}
		if after {
			return str[i+len(word):]
		}
		return str[:i]
	}
}

// stringLengthFunc is XPATH string-length( [string] ) function that returns a number
// equal to the number of characters in a given string.
func stringLengthFunc(arg1 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		switch v := arg1.Evaluate(t).(type) {
		case string:
			return float64(len(v))
		case query:
			node := v.Select(t)
			if node == nil {
				break
			}
			return float64(len(node.Value()))
		}
		return float64(0)
	}
}

// translateFunc is XPath functions translate() function returns a replaced string.
func translateFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		str := asString(t, arg1.Evaluate(t))
		src := asString(t, arg2.Evaluate(t))
		dst := asString(t, arg3.Evaluate(t))

		var replace []string
		for i, s := range src {
			d := ""
			if i < len(dst) {
				d = string(dst[i])
			}
			replace = append(replace, string(s), d)
		}
		return strings.NewReplacer(replace...).Replace(str)
	}
}

// notFunc is XPATH functions not(expression) function operation.
func notFunc(q query, t iterator) interface{} {
	switch v := q.Evaluate(t).(type) {
	case bool:
		return !v
	case query:
		node := v.Select(t)
		return node == nil
	default:
		return false
	}
}

// concatFunc is the concat function concatenates two or more
// strings and returns the resulting string.
// concat( string1 , string2 [, stringn]* )
func concatFunc(args ...query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var a []string
		for _, v := range args {
			switch v := v.Evaluate(t).(type) {
			case string:
				a = append(a, v)
			case query:
				node := v.Select(t)
				if node != nil {
					a = append(a, node.Value())
				}
			}
		}
		return strings.Join(a, "")
	}
}
<|endoftext|>


<commit_before>package g11n

import (
	"fmt"
	"reflect"
	"sync"

	g11nLocale "github.com/s2gatev/g11n/locale"
)

const /* application constants */ (
	defaultMessageTag = "default"
)

const /* error message patterns */ (
	wrongResultsCountMessage = "Wrong number of results in a g11n message. Expected 1, got %v."
	unknownFormatMessage     = "Unknown locale format '%v'."
)

// Synchronizer synchronizes asynchronous tasks.
type Synchronizer struct {
	tasks     *sync.WaitGroup
	completed bool
}

// Await awaits the completion of the tasks.
func (s *Synchronizer) Await() {
	s.tasks.Wait()
}

// Completed returns whether the tasks are already completed.
func (s *Synchronizer) Completed() bool {
	return s.completed
}

// paramFormatter represents a type that supports custom formatting
// when it is used as parameter in a call to a g11n message.
type paramFormatter interface {

	// G11nParam formats a type in a specific way when passed to a g11n message.
	G11nParam() string
}

// resultFormatter represents a type that supports custom formatting
// when it is returned from a call to a g11n message.
type resultFormatter interface {

	// G11nResult accepts a formatted g11n message and modifies it before returning.
	G11nResult(formattedMessage string) string
}

// formatParam extracts the data from a reflected argument value and returns it.
func formatParam(value reflect.Value) interface{} {
	valueInterface := value.Interface()

	if paramFormatter, ok := valueInterface.(paramFormatter); ok {
		return paramFormatter.G11nParam()
	}

	return valueInterface
}

// messageHandler creates a handler that formats a message based on provided parameters.
func messageHandler(messagePattern string, resultType reflect.Type) func([]reflect.Value) []reflect.Value {
	return func(args []reflect.Value) []reflect.Value {
		// Format message parameters.
		var formattedParams []interface{}
		for _, arg := range args {
			formattedParams = append(formattedParams, formatParam(arg))
		}

		// Find the result message value.
		message := fmt.Sprintf(messagePattern, formattedParams...)
		messageValue := reflect.ValueOf(message)

		// Format message result.
		resultValue := reflect.New(resultType).Elem()
		if resultFormatter, ok := resultValue.Interface().(resultFormatter); ok {
			formattedResult := resultFormatter.G11nResult(message)
			messageValue = reflect.ValueOf(formattedResult).Convert(resultType)
		}
		resultValue.Set(messageValue)

		return []reflect.Value{resultValue}
	}
}

// MessageFactory initializes message structs and provides language
// translations to messages.
type MessageFactory struct {
	activeLocale string
	locales      map[string]map[string]string
}

// New returns a fresh G11n message factory.
func New() *MessageFactory {
	return &MessageFactory{
		locales: map[string]map[string]string{},
	}
}

// LoadLocale loads the content of a locale file in the specified format.
func (mf *MessageFactory) LoadLocale(format, locale, fileName string) {
	if loader, ok := g11nLocale.GetLoader(format); ok {
		mf.locales[locale] = loader.Load(fileName)
	} else {
		panic(fmt.Sprintf(unknownFormatMessage, format))
	}
}

// SetLocale sets the currently active locale for the messages generated
// by this factory.
func (mf *MessageFactory) SetLocale(locale string) {
	mf.activeLocale = locale
}

// Init initializes the message fields of a structure pointer.
func (mf *MessageFactory) Init(structPtr interface{}) interface{} {
	mf.initializeStruct(structPtr)

	return structPtr
}

// InitAsync initializes the message fields of a structure pointer asynchronously.
func (mf *MessageFactory) InitAsync(structPtr interface{}) (interface{}, *Synchronizer) {
	var initializers sync.WaitGroup
	synchronizer := &Synchronizer{tasks: &initializers}

	initializers.Add(1)
	go func() {
		mf.initializeStruct(structPtr)
		initializers.Done()
		synchronizer.completed = true
	}()

	return structPtr, synchronizer
}

// initializeStruct initializes the message fields of a struct pointer.
func (mf *MessageFactory) initializeStruct(structPtr interface{}) {
	instance := reflect.ValueOf(structPtr).Elem()
	concreteType := instance.Type()

	// Initialize each message func of the struct.
	for i := 0; i < concreteType.NumField(); i++ {
		field := concreteType.Field(i)
		instanceField := instance.FieldByName(field.Name)

		// Extract default message.
		messagePattern := field.Tag.Get(defaultMessageTag)

		// Extract localized message.
		if locale, ok := mf.locales[mf.activeLocale]; ok {
			messageKey := fmt.Sprintf("%v.%v", concreteType.Name(), field.Name)
			if message, ok := locale[messageKey]; ok {
				messagePattern = message
			}
		}

		// Check if return type of the message func is correct.
		if field.Type.NumOut() != 1 {
			panic(fmt.Sprintf(wrongResultsCountMessage, field.Type.NumOut()))
		}
		resultType := field.Type.Out(0)

		// Create proxy function for handling the message.
		messageProxyFunc := reflect.MakeFunc(
			field.Type, messageHandler(messagePattern, resultType))

		instanceField.Set(messageProxyFunc)
	}
}
<commit_msg>Fix a race condition in async initialization<commit_after>package g11n

import (
	"fmt"
	"reflect"
	"sync"

	g11nLocale "github.com/s2gatev/g11n/locale"
)

const /* application constants */ (
	defaultMessageTag = "default"
)

const /* error message patterns */ (
	wrongResultsCountMessage = "Wrong number of results in a g11n message. Expected 1, got %v."
	unknownFormatMessage     = "Unknown locale format '%v'."
)

// Synchronizer synchronizes asynchronous tasks.
type Synchronizer struct {
	tasks     *sync.WaitGroup
	completed bool
	sync.RWMutex
}

// Await awaits the completion of the tasks.
func (s *Synchronizer) Await() {
	s.tasks.Wait()
}

// Completed returns whether the tasks are already completed.
func (s *Synchronizer) Completed() bool {
	s.RLock()
	defer s.RUnlock()

	return s.completed
}

// paramFormatter represents a type that supports custom formatting
// when it is used as parameter in a call to a g11n message.
type paramFormatter interface {

	// G11nParam formats a type in a specific way when passed to a g11n message.
	G11nParam() string
}

// resultFormatter represents a type that supports custom formatting
// when it is returned from a call to a g11n message.
type resultFormatter interface {

	// G11nResult accepts a formatted g11n message and modifies it before returning.
	G11nResult(formattedMessage string) string
}

// formatParam extracts the data from a reflected argument value and returns it.
func formatParam(value reflect.Value) interface{} {
	valueInterface := value.Interface()

	if paramFormatter, ok := valueInterface.(paramFormatter); ok {
		return paramFormatter.G11nParam()
	}

	return valueInterface
}

// messageHandler creates a handler that formats a message based on provided parameters.
func messageHandler(messagePattern string, resultType reflect.Type) func([]reflect.Value) []reflect.Value {
	return func(args []reflect.Value) []reflect.Value {
		// Format message parameters.
		var formattedParams []interface{}
		for _, arg := range args {
			formattedParams = append(formattedParams, formatParam(arg))
		}

		// Find the result message value.
		message := fmt.Sprintf(messagePattern, formattedParams...)
		messageValue := reflect.ValueOf(message)

		// Format message result.
		resultValue := reflect.New(resultType).Elem()
		if resultFormatter, ok := resultValue.Interface().(resultFormatter); ok {
			formattedResult := resultFormatter.G11nResult(message)
			messageValue = reflect.ValueOf(formattedResult).Convert(resultType)
		}
		resultValue.Set(messageValue)

		return []reflect.Value{resultValue}
	}
}

// MessageFactory initializes message structs and provides language
// translations to messages.
type MessageFactory struct {
	activeLocale string
	locales      map[string]map[string]string
}

// New returns a fresh G11n message factory.
func New() *MessageFactory {
	return &MessageFactory{
		locales: map[string]map[string]string{},
	}
}

// LoadLocale loads the content of a locale file in the specified format.
func (mf *MessageFactory) LoadLocale(format, locale, fileName string) {
	if loader, ok := g11nLocale.GetLoader(format); ok {
		mf.locales[locale] = loader.Load(fileName)
	} else {
		panic(fmt.Sprintf(unknownFormatMessage, format))
	}
}

// SetLocale sets the currently active locale for the messages generated
// by this factory.
func (mf *MessageFactory) SetLocale(locale string) {
	mf.activeLocale = locale
}

// Init initializes the message fields of a structure pointer.
func (mf *MessageFactory) Init(structPtr interface{}) interface{} {
	mf.initializeStruct(structPtr)

	return structPtr
}

// InitAsync initializes the message fields of a structure pointer asynchronously.
func (mf *MessageFactory) InitAsync(structPtr interface{}) (interface{}, *Synchronizer) {
	var initializers sync.WaitGroup
	synchronizer := &Synchronizer{tasks: &initializers}

	initializers.Add(1)
	go func() {
		mf.initializeStruct(structPtr)
		initializers.Done()

		synchronizer.Lock()
		defer synchronizer.Unlock()

		synchronizer.completed = true
	}()

	return structPtr, synchronizer
}

// initializeStruct initializes the message fields of a struct pointer.
func (mf *MessageFactory) initializeStruct(structPtr interface{}) {
	instance := reflect.ValueOf(structPtr).Elem()
	concreteType := instance.Type()

	// Initialize each message func of the struct.
	for i := 0; i < concreteType.NumField(); i++ {
		field := concreteType.Field(i)
		instanceField := instance.FieldByName(field.Name)

		// Extract default message.
		messagePattern := field.Tag.Get(defaultMessageTag)

		// Extract localized message.
		if locale, ok := mf.locales[mf.activeLocale]; ok {
			messageKey := fmt.Sprintf("%v.%v", concreteType.Name(), field.Name)
			if message, ok := locale[messageKey]; ok {
				messagePattern = message
			}
		}

		// Check if return type of the message func is correct.
		if field.Type.NumOut() != 1 {
			panic(fmt.Sprintf(wrongResultsCountMessage, field.Type.NumOut()))
		}
		resultType := field.Type.Out(0)

		// Create proxy function for handling the message.
		messageProxyFunc := reflect.MakeFunc(
			field.Type, messageHandler(messagePattern, resultType))

		instanceField.Set(messageProxyFunc)
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"net/url"
	"strings"

	aauth "github.com/apigee/apigee-remote-service-golib/auth"
	libAuth "github.com/apigee/apigee-remote-service-golib/auth"
	"github.com/apigee/apigee-remote-service-golib/log"
	"github.com/apigee/apigee-remote-service-golib/quota"
	core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
	auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
	envoy_type "github.com/envoyproxy/go-control-plane/envoy/type"
	"github.com/gogo/googleapis/google/rpc"
	"golang.org/x/net/context"
	rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc"
)

// AuthorizationServer server
type AuthorizationServer struct {
	handler *Handler
}

// Register registers
func (a *AuthorizationServer) Register(s *grpc.Server, handler *Handler) {
	auth.RegisterAuthorizationServer(s, a)
	a.handler = handler
}

const (
	apiKeyKey            = "x-api-key"
	jwtFilterMetadataKey = "envoy.filters.http.jwt_authn"
)

// Check does check
func (a *AuthorizationServer) Check(ctx context.Context, req *auth.CheckRequest) (*auth.CheckResponse, error) {

	// check for JWT from Envoy filter
	protoBufStruct := req.Attributes.GetMetadataContext().GetFilterMetadata()[jwtFilterMetadataKey]
	fieldsMap := protoBufStruct.GetFields()
	var claims map[string]interface{}
	// TODO: just using the first for now, should configure and/or support multiple
	for k, v := range fieldsMap {
		log.Debugf("Using JWT at key: %s", k)
		claims = DecodeToMap(v.GetStructValue())
	}

	splits := strings.SplitN(req.Attributes.Request.Http.Path, "?", 2)
	path := splits[0]

	apiKey := req.Attributes.Request.Http.Headers[apiKeyKey] // grab from header

	if apiKey == "" && len(splits) > 1 { // look in query if not in header
		if qs, err := url.ParseQuery(splits[1]); err != nil {
			if keys, ok := qs[apiKeyKey]; ok {
				apiKey = keys[0]
			}
		}
	}

	authContext, err := a.handler.authMan.Authenticate(a.handler, apiKey, claims, a.handler.apiKeyClaimKey)
	switch err {
	case libAuth.ErrNoAuth:
		return unauthenticated(), nil
	case libAuth.ErrBadAuth:
		return unauthorized(), nil
	case libAuth.ErrInternalError:
		return internalError(err), nil
	}

	if len(authContext.APIProducts) == 0 {
		return unauthorized(), nil
	}

	// match products
	api := req.Attributes.Request.Http.GetHost()
	products := a.handler.productMan.Resolve(authContext, api, path)
	if len(products) == 0 {
		return unauthorized(), nil
	}

	// apply quotas to all matched products
	var quotaArgs = quota.Args{QuotaAmount: 1}
	var exceeded bool
	var anyError error
	for _, p := range products {
		if p.QuotaLimitInt > 0 {
			result, err := a.handler.quotaMan.Apply(authContext, p, quotaArgs)
			if err != nil {
				log.Errorf("quota check: %v", err)
				anyError = err
			} else if result.Exceeded > 0 {
				log.Debugf("quota exceeded: %v", p.Name)
				exceeded = true
			}
		}
	}
	if anyError != nil {
		return internalError(anyError), nil
	}
	if exceeded {
		log.Debugf("quota exceeded: %v", err)
		return quotaExceeded(), nil
	}

	return authOK(authContext, api, path), nil
}

func authOK(authContext *aauth.Context, api, path string) *auth.CheckResponse {

	hc := makeHeaderContext(api, authContext)
	data := hc.encode()

	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.OK),
		},
		HttpResponse: &auth.CheckResponse_OkResponse{
			OkResponse: &auth.OkHttpResponse{
				Headers: []*core.HeaderValueOption{
					{
						Header: &core.HeaderValue{
							Key:   headerContextKey,
							Value: data,
						},
					},
				},
			},
		},
	}
}

func unauthenticated() *auth.CheckResponse {
	log.Debugf("unauthenticated")
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.UNAUTHENTICATED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_Unauthorized,
				},
				Body: "Authorization malformed or not provided",
			},
		},
	}
}

func unauthorized() *auth.CheckResponse {
	log.Debugf("unauthorized")
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.PERMISSION_DENIED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_Unauthorized,
				},
				Body: "Authenticated caller is not authorized for this action",
			},
		},
	}
}

func quotaExceeded() *auth.CheckResponse {
	log.Debugf("quota exceeded")
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.RESOURCE_EXHAUSTED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_TooManyRequests,
				},
				Body: "Quota exceeded",
			},
		},
	}
}

func internalError(err error) *auth.CheckResponse {
	log.Errorf("internal error: %v", err)
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.INTERNAL),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_InternalServerError,
				},
				Body: "Server error",
			},
		},
	}
}
<commit_msg>fix apikey in qs<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"net/url"
	"strings"

	aauth "github.com/apigee/apigee-remote-service-golib/auth"
	libAuth "github.com/apigee/apigee-remote-service-golib/auth"
	"github.com/apigee/apigee-remote-service-golib/log"
	"github.com/apigee/apigee-remote-service-golib/quota"
	core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
	auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
	envoy_type "github.com/envoyproxy/go-control-plane/envoy/type"
	"github.com/gogo/googleapis/google/rpc"
	"golang.org/x/net/context"
	rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc"
)

// AuthorizationServer server
type AuthorizationServer struct {
	handler *Handler
}

// Register registers
func (a *AuthorizationServer) Register(s *grpc.Server, handler *Handler) {
	auth.RegisterAuthorizationServer(s, a)
	a.handler = handler
}

const (
	apiKeyKey            = "x-api-key"
	jwtFilterMetadataKey = "envoy.filters.http.jwt_authn"
)

// Check does check
func (a *AuthorizationServer) Check(ctx context.Context, req *auth.CheckRequest) (*auth.CheckResponse, error) {

	// check for JWT from Envoy filter
	protoBufStruct := req.Attributes.GetMetadataContext().GetFilterMetadata()[jwtFilterMetadataKey]
	fieldsMap := protoBufStruct.GetFields()
	var claims map[string]interface{}
	// TODO: just using the first for now, should configure and/or support multiple
	for k, v := range fieldsMap {
		log.Debugf("Using JWT at key: %s", k)
		claims = DecodeToMap(v.GetStructValue())
	}

	splits := strings.SplitN(req.Attributes.Request.Http.Path, "?", 2)
	path := splits[0]

	apiKey := req.Attributes.Request.Http.Headers[apiKeyKey] // grab from header

	if apiKey == "" && len(splits) > 1 { // look in query if not in header
		if qs, err := url.ParseQuery(splits[1]); err == nil {
			if keys, ok := qs[apiKeyKey]; ok {
				apiKey = keys[0]
			}
		}
	}

	authContext, err := a.handler.authMan.Authenticate(a.handler, apiKey, claims, a.handler.apiKeyClaimKey)
	switch err {
	case libAuth.ErrNoAuth:
		return unauthenticated(), nil
	case libAuth.ErrBadAuth:
		return unauthorized(), nil
	case libAuth.ErrInternalError:
		return internalError(err), nil
	}

	if len(authContext.APIProducts) == 0 {
		return unauthorized(), nil
	}

	// match products
	api := req.Attributes.Request.Http.GetHost()
	products := a.handler.productMan.Resolve(authContext, api, path)
	if len(products) == 0 {
		return unauthorized(), nil
	}

	// apply quotas to all matched products
	var quotaArgs = quota.Args{QuotaAmount: 1}
	var exceeded bool
	var anyError error
	for _, p := range products {
		if p.QuotaLimitInt > 0 {
			result, err := a.handler.quotaMan.Apply(authContext, p, quotaArgs)
			if err != nil {
				log.Errorf("quota check: %v", err)
				anyError = err
			} else if result.Exceeded > 0 {
				log.Debugf("quota exceeded: %v", p.Name)
				exceeded = true
			}
		}
	}
	if anyError != nil {
		return internalError(anyError), nil
	}
	if exceeded {
		log.Debugf("quota exceeded: %v", err)
		return quotaExceeded(), nil
	}

	return authOK(authContext, api, path), nil
}

func authOK(authContext *aauth.Context, api, path string) *auth.CheckResponse {

	hc := makeHeaderContext(api, authContext)
	data := hc.encode()

	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.OK),
		},
		HttpResponse: &auth.CheckResponse_OkResponse{
			OkResponse: &auth.OkHttpResponse{
				Headers: []*core.HeaderValueOption{
					{
						Header: &core.HeaderValue{
							Key:   headerContextKey,
							Value: data,
						},
					},
				},
			},
		},
	}
}

func unauthenticated() *auth.CheckResponse {
	log.Debugf("unauthenticated")
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.UNAUTHENTICATED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_Unauthorized,
				},
				Body: "Authorization malformed or not provided",
			},
		},
	}
}

func unauthorized() *auth.CheckResponse {
	log.Debugf("unauthorized")
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.PERMISSION_DENIED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_Unauthorized,
				},
				Body: "Authenticated caller is not authorized for this action",
			},
		},
	}
}

func quotaExceeded() *auth.CheckResponse {
	log.Debugf("quota exceeded")
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.RESOURCE_EXHAUSTED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_TooManyRequests,
				},
				Body: "Quota exceeded",
			},
		},
	}
}

func internalError(err error) *auth.CheckResponse {
	log.Errorf("internal error: %v", err)
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.INTERNAL),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_InternalServerError,
				},
				Body: "Server error",
			},
		},
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"time"

	"github.com/go-errors/errors"
	"github.com/itchio/butler/comm"
	"github.com/winlabs/gowin32"
)

func msiInfo(msiPath string) {
	must(doMsiInfo(msiPath))
}

/**
 * MSIInfoResult describes an MSI package's properties
 */
type MSIInfoResult struct {
	ProductCode  string `json:"productCode"`
	InstallState string `json:"installState"`
}

func doMsiInfo(msiPath string) error {
	initMsi()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	pkg, err := gowin32.OpenInstallerPackage(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	defer pkg.Close()

	productCode, err := pkg.GetProductProperty("ProductCode")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	comm.Debugf("Product code for %s: %s", msiPath, productCode)

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	}

	return nil
}

func msiProductInfo(productCode string) {
	must(doMsiProductInfo(productCode))
}

func doMsiProductInfo(productCode string) error {
	initMsi()

	state := gowin32.GetInstalledProductState(productCode)

	comm.Logf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	}

	return nil
}

func msiInstall(msiPath string, logPath string, target string) {
	must(doMsiInstall(msiPath, logPath, target))
}

func doMsiInstall(msiPath string, logPath string, target string) error {
	initMsi()

	startTime := time.Now()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Assessing state of %s", msiPath)

	var productCode string

	err = func() error {
		pkg, err := gowin32.OpenInstallerPackage(msiPath)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		defer pkg.Close()

		productCode, err = pkg.GetProductProperty("ProductCode")
		if err != nil {
			return errors.Wrap(err, 0)
		}
		comm.Debugf("Product code for %s: %s", msiPath, productCode)
		return nil
	}()
	if err != nil {
		return err
	}

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	repair := false
	if state == gowin32.InstallStateDefault {
		comm.Opf("Already installed, repairing from %s", msiPath)
		repair = true
	} else {
		comm.Opf("Installing %s", msiPath)
	}

	if logPath != "" {
		// equivalent to "/lv"
		logMode := gowin32.InstallLogModeVerbose
		logAttr := gowin32.InstallLogAttributesFlushEachLine
		err := gowin32.EnableInstallerLog(logMode, logPath, logAttr)
		if err != nil {
			return errors.Wrap(err, 0)
		}
		defer func() {
			gowin32.DisableInstallerLog()
		}()

		comm.Debugf("...will write log to %s", logPath)
	}

	// s = Recreate all shortcuts
	// m = Rewrite all HKLM and HKCR registry entries
	// u = Rewrite all HKCU and HKU registry entries
	// p = Reinstall only if the file is missing
	// We can't use "o", "e", "d", "c" (reinstall older, equal-or-older, different, checksum)
	// because they require the source package to be available
	// (which isn't always true, see https://github.com/itchio/itch/issues/1304)
	// We won't use "a" (reinstall all) because it's overkill.
	commandLine := "REINSTALLMODE=smup REBOOT=reallysuppress"

	if target != "" {
		// throw everything we got to try and get a local install
		commandLine += " ALLUSERS=2 MSIINSTALLPERUSER=1"
		commandLine += fmt.Sprintf(" TARGETDIR=\"%s\" INSTALLDIR=\"%s\" APPDIR=\"%s\"", target, target, target)
		comm.Debugf("...will install in folder %s", target)
	}

	comm.Debugf("Final command line: %s", commandLine)

	if repair {
		ilvl := gowin32.InstallLevelDefault
		istate := gowin32.InstallStateDefault
		err = gowin32.ConfigureInstalledProduct(productCode, ilvl, istate, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Repaired in %s", time.Since(startTime))
	} else {
		err = gowin32.InstallProduct(msiPath, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Installed in %s", time.Since(startTime))
	}

	return nil
}

func msiUninstall(productCode string) {
	must(doMsiUninstall(productCode))
}

func doMsiUninstall(productCode string) error {
	initMsi()

	comm.Opf("Uninstalling product %s", productCode)

	startTime := time.Now()

	err := gowin32.UninstallProduct(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Statf("Uninstalled in %s", time.Since(startTime))

	return nil
}
<commit_msg>msi-info: print something even when not in json mode<commit_after>package main

import (
	"fmt"
	"path/filepath"
	"time"

	"github.com/go-errors/errors"
	"github.com/itchio/butler/comm"
	"github.com/winlabs/gowin32"
)

func msiInfo(msiPath string) {
	must(doMsiInfo(msiPath))
}

/**
 * MSIInfoResult describes an MSI package's properties
 */
type MSIInfoResult struct {
	ProductCode  string `json:"productCode"`
	InstallState string `json:"installState"`
}

func doMsiInfo(msiPath string) error {
	initMsi()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	pkg, err := gowin32.OpenInstallerPackage(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	defer pkg.Close()

	productCode, err := pkg.GetProductProperty("ProductCode")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	comm.Debugf("Product code for %s: %s", msiPath, productCode)

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	} else {
		comm.Statf("MSI product code: %s", productCode)
		comm.Statf("Install state: %s", installStateToString(state))
	}

	return nil
}

func msiProductInfo(productCode string) {
	must(doMsiProductInfo(productCode))
}

func doMsiProductInfo(productCode string) error {
	initMsi()

	state := gowin32.GetInstalledProductState(productCode)

	comm.Logf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	}

	return nil
}

func msiInstall(msiPath string, logPath string, target string) {
	must(doMsiInstall(msiPath, logPath, target))
}

func doMsiInstall(msiPath string, logPath string, target string) error {
	initMsi()

	startTime := time.Now()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Assessing state of %s", msiPath)

	var productCode string

	err = func() error {
		pkg, err := gowin32.OpenInstallerPackage(msiPath)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		defer pkg.Close()

		productCode, err = pkg.GetProductProperty("ProductCode")
		if err != nil {
			return errors.Wrap(err, 0)
		}
		comm.Debugf("Product code for %s: %s", msiPath, productCode)
		return nil
	}()
	if err != nil {
		return err
	}

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	repair := false
	if state == gowin32.InstallStateDefault {
		comm.Opf("Already installed, repairing from %s", msiPath)
		repair = true
	} else {
		comm.Opf("Installing %s", msiPath)
	}

	if logPath != "" {
		// equivalent to "/lv"
		logMode := gowin32.InstallLogModeVerbose
		logAttr := gowin32.InstallLogAttributesFlushEachLine
		err := gowin32.EnableInstallerLog(logMode, logPath, logAttr)
		if err != nil {
			return errors.Wrap(err, 0)
		}
		defer func() {
			gowin32.DisableInstallerLog()
		}()

		comm.Debugf("...will write log to %s", logPath)
	}

	// s = Recreate all shortcuts
	// m = Rewrite all HKLM and HKCR registry entries
	// u = Rewrite all HKCU and HKU registry entries
	// p = Reinstall only if the file is missing
	// We can't use "o", "e", "d", "c" (reinstall older, equal-or-older, different, checksum)
	// because they require the source package to be available
	// (which isn't always true, see https://github.com/itchio/itch/issues/1304)
	// We won't use "a" (reinstall all) because it's overkill.
	commandLine := "REINSTALLMODE=smup REBOOT=reallysuppress"

	if target != "" {
		// throw everything we got to try and get a local install
		commandLine += " ALLUSERS=2 MSIINSTALLPERUSER=1"
		commandLine += fmt.Sprintf(" TARGETDIR=\"%s\" INSTALLDIR=\"%s\" APPDIR=\"%s\"", target, target, target)
		comm.Debugf("...will install in folder %s", target)
	}

	comm.Debugf("Final command line: %s", commandLine)

	if repair {
		ilvl := gowin32.InstallLevelDefault
		istate := gowin32.InstallStateDefault
		err = gowin32.ConfigureInstalledProduct(productCode, ilvl, istate, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Repaired in %s", time.Since(startTime))
	} else {
		err = gowin32.InstallProduct(msiPath, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Installed in %s", time.Since(startTime))
	}

	return nil
}

func msiUninstall(productCode string) {
	must(doMsiUninstall(productCode))
}

func doMsiUninstall(productCode string) error {
	initMsi()

	comm.Opf("Uninstalling product %s", productCode)

	startTime := time.Now()

	err := gowin32.UninstallProduct(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Statf("Uninstalled in %s", time.Since(startTime))

	return nil
}
<|endoftext|>


<commit_before>package mstate

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"launchpad.net/juju-core/trivial"
)

// ResolvedMode describes the way state transition errors
// are resolved.
type ResolvedMode int

const (
	ResolvedNone ResolvedMode = iota
	ResolvedRetryHooks
	ResolvedNoHooks
	nResolvedModes
)

// AssignmentPolicy controls what machine a unit will be assigned to.
type AssignmentPolicy string

const (
	// AssignLocal indicates that all service units should be assigned
	// to machine 0.
	AssignLocal AssignmentPolicy = "local"
	// AssignUnused indicates that every service unit should be assigned
	// to a dedicated machine, and that new machines should be launched
	// if required.
	AssignUnused AssignmentPolicy = "unused"
)

// NeedsUpgrade describes if a unit needs an
// upgrade and if this is forced.
type NeedsUpgrade struct {
	Upgrade bool
	Force   bool
}

// Port identifies a network port number for a particular protocol.
type Port struct {
	Protocol string `yaml:"proto"`
	Number   int    `yaml:"port"`
}

// UnitSettings holds information about a service unit's settings within a
// relation.
type UnitSettings struct {
	Version  int
	Settings map[string]interface{}
}

// unitDoc represents the internal state of a unit in MongoDB.
type unitDoc struct {
	Name           string `bson:"_id"`
	Service        string
	Principal      string
	PublicAddress  string
	PrivateAddress string
	MachineId      *int
	Resolved       ResolvedMode
	NeedsUpgrade   *NeedsUpgrade
	Life           Life
}

// Unit represents the state of a service unit.
type Unit struct {
	st  *State
	doc unitDoc
}

func newUnit(st *State, udoc *unitDoc) *Unit {
	return &Unit{
		st:  st,
		doc: *udoc,
	}
}

// ServiceName returns the service name.
func (u *Unit) ServiceName() string {
	return u.doc.Service
}

// String returns the unit as string.
func (u *Unit) String() string {
	return u.doc.Name
}

// Name returns the unit name.
func (u *Unit) Name() string {
	return u.doc.Name
}

// Resolved returns the resolved mode for the unit.
func (u *Unit) Resolved() (mode ResolvedMode, err error) {
	return u.doc.Resolved, nil
}

// IsPrincipal returns whether the unit is deployed in its own container,
// and can therefore have subordinate services deployed alongside it.
func (u *Unit) IsPrincipal() bool {
	return u.doc.Principal == ""
}

// PublicAddress returns the public address of the unit.
func (u *Unit) PublicAddress() (string, error) {
	if u.doc.PublicAddress == "" {
		return "", fmt.Errorf("public address of unit %q not found", u)
	}
	return u.doc.PublicAddress, nil
}

// PrivateAddress returns the public address of the unit.
func (u *Unit) PrivateAddress() (string, error) {
	if u.doc.PrivateAddress == "" {
		return "", fmt.Errorf("private address of unit %q not found", u)
	}
	return u.doc.PrivateAddress, nil
}

func (u *Unit) Refresh() error {
	err := u.st.units.FindId(u.doc.Name).One(&u.doc)
	if err != nil {
		return fmt.Errorf("cannot refresh unit %q: %v", u, err)
	}
	return nil
}

// AssignedMachineId returns the id of the assigned machine.
func (u *Unit) AssignedMachineId() (id int, err error) {
	defer trivial.ErrorContextf(&err, "cannot get machine id of unit %q", u)
	if u.IsPrincipal() {
		if u.doc.MachineId == nil {
			return 0, errors.New("unit not assigned to machine")
		}
		return *u.doc.MachineId, nil
	}
	pudoc := unitDoc{}
	sel := bson.D{{"_id", u.doc.Principal}, {"life", Alive}}
	err = u.st.units.Find(sel).One(&pudoc)
	if err != nil {
		return 0, err
	}
	if pudoc.MachineId == nil {
		return 0, errors.New("unit not assigned to machine")
	}
	return *pudoc.MachineId, nil
}

// AssignToMachine assigns this unit to a given machine.
func (u *Unit) AssignToMachine(m *Machine) (err error) {
	change := bson.D{{"$set", bson.D{{"machineid", m.Id()}}}}
	sel := bson.D{
		{"_id", u.doc.Name},
		{"$or", []bson.D{
			bson.D{{"machineid", nil}},
			bson.D{{"machineid", m.Id()}},
		}},
	}
	err = u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot assign unit %q to machine %s: %v", u, m, err)
	}
	u.doc.MachineId = &m.doc.Id
	return nil
}

// UnassignFromMachine removes the assignment between this unit and the
// machine it's assigned to.
func (u *Unit) UnassignFromMachine() (err error) {
	change := bson.D{{"$set", bson.D{{"machineid", nil}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err = u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot unassign unit %q from machine: %v", u, err)
	}
	u.doc.MachineId = nil
	return nil
}

// SetPublicAddress sets the public address of the unit.
func (u *Unit) SetPublicAddress(address string) error {
	change := bson.D{{"$set", bson.D{{"publicaddress", address}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot set public address of unit %q: %v", u, err)
	}
	u.doc.PublicAddress = address
	return nil
}

// SetPrivateAddress sets the public address of the unit.
func (u *Unit) SetPrivateAddress(address string) error {
	change := bson.D{{"$set", bson.D{{"privateaddress", address}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot set private address of unit %q: %v", u, err)
	}
	u.doc.PrivateAddress = address
	return nil
}

// SetResolved marks the unit as having had any previous state transition
// problems resolved, and informs the unit that it may attempt to
// reestablish normal workflow. The resolved mode parameter informs
// whether to attempt to reexecute previous failed hooks or to continue
// as if they had succeeded before.
func (u *Unit) SetResolved(mode ResolvedMode) (err error) {
	defer trivial.ErrorContextf(&err, "cannot set resolved mode for unit %q", u)
	if !(0 <= mode && mode < nResolvedModes) {
		return fmt.Errorf("invalid error resolution mode: %v", mode)
	}
	change := bson.D{{"$set", bson.D{{"resolved", mode}}}}
	sel := bson.D{
		{"_id", u.doc.Name},
		{"resolved", ResolvedNone},
	}
	err = u.st.units.Update(sel, change)
	if err == mgo.ErrNotFound {
		return errors.New("flag already set")
	}
	if err != nil {
		return err
	}
	u.doc.Resolved = mode
	return nil
}

// ClearResolved removes any resolved setting on the unit.
func (u *Unit) ClearResolved() error {
	change := bson.D{{"$set", bson.D{{"resolved", ResolvedNone}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot clear resolved mode for unit %q: %v", u, err)
	}
	u.doc.Resolved = ResolvedNone
	return nil
}

// NeedsUpgrade returns whether the unit needs an upgrade
// and if it does, if this is forced.
func (u *Unit) NeedsUpgrade() (*NeedsUpgrade, error) {
	if u.doc.NeedsUpgrade == nil {
		return &NeedsUpgrade{Upgrade: false, Force: false}, nil
	}
	return u.doc.NeedsUpgrade, nil
}

// SetNeedsUpgrade informs the unit that it should perform
// a regular or forced upgrade.
func (u *Unit) SetNeedsUpgrade(force bool) (err error) {
	defer trivial.ErrorContextf(&err, "cannot inform unit %q about upgrade", u)
	nu := &NeedsUpgrade{Upgrade: true, Force: force}
	change := bson.D{{"$set", bson.D{{"needsupgrade", nu}}}}
	sel := bson.D{
		{"_id", u.doc.Name},
		{"$or", []bson.D{
			bson.D{{"needsupgrade", nil}},
			bson.D{{"needsupgrade", nu}},
		}},
	}
	err = u.st.units.Update(sel, change)
	if err == mgo.ErrNotFound {
		return errors.New("upgrade already enabled")
	}
	if err != nil {
		return err
	}
	u.doc.NeedsUpgrade = nu
	return nil
}

// ClearNeedsUpgrade resets the upgrade notification. It is typically
// done by the unit agent before beginning the upgrade.
func (u *Unit) ClearNeedsUpgrade() error {
	change := bson.D{{"$set", bson.D{{"needsupgrade", nil}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("upgrade notification for unit %q cannot be reset: %v", u, err)
	}
	u.doc.NeedsUpgrade = nil
	return nil
}
<commit_msg>mstate: use txn for AssignToMachine<commit_after>package mstate

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/trivial"
)

// ResolvedMode describes the way state transition errors
// are resolved.
type ResolvedMode int

const (
	ResolvedNone ResolvedMode = iota
	ResolvedRetryHooks
	ResolvedNoHooks
	nResolvedModes
)

// AssignmentPolicy controls what machine a unit will be assigned to.
type AssignmentPolicy string

const (
	// AssignLocal indicates that all service units should be assigned
	// to machine 0.
	AssignLocal AssignmentPolicy = "local"
	// AssignUnused indicates that every service unit should be assigned
	// to a dedicated machine, and that new machines should be launched
	// if required.
	AssignUnused AssignmentPolicy = "unused"
)

// NeedsUpgrade describes if a unit needs an
// upgrade and if this is forced.
type NeedsUpgrade struct {
	Upgrade bool
	Force   bool
}

// Port identifies a network port number for a particular protocol.
type Port struct {
	Protocol string `yaml:"proto"`
	Number   int    `yaml:"port"`
}

// UnitSettings holds information about a service unit's settings within a
// relation.
type UnitSettings struct {
	Version  int
	Settings map[string]interface{}
}

// unitDoc represents the internal state of a unit in MongoDB.
type unitDoc struct {
	Name           string `bson:"_id"`
	Service        string
	Principal      string
	PublicAddress  string
	PrivateAddress string
	MachineId      *int
	Resolved       ResolvedMode
	NeedsUpgrade   *NeedsUpgrade
	Life           Life
}

// Unit represents the state of a service unit.
type Unit struct {
	st  *State
	doc unitDoc
}

func newUnit(st *State, udoc *unitDoc) *Unit {
	return &Unit{
		st:  st,
		doc: *udoc,
	}
}

// ServiceName returns the service name.
func (u *Unit) ServiceName() string {
	return u.doc.Service
}

// String returns the unit as string.
func (u *Unit) String() string {
	return u.doc.Name
}

// Name returns the unit name.
func (u *Unit) Name() string {
	return u.doc.Name
}

// Resolved returns the resolved mode for the unit.
func (u *Unit) Resolved() (mode ResolvedMode, err error) {
	return u.doc.Resolved, nil
}

// IsPrincipal returns whether the unit is deployed in its own container,
// and can therefore have subordinate services deployed alongside it.
func (u *Unit) IsPrincipal() bool {
	return u.doc.Principal == ""
}

// PublicAddress returns the public address of the unit.
func (u *Unit) PublicAddress() (string, error) {
	if u.doc.PublicAddress == "" {
		return "", fmt.Errorf("public address of unit %q not found", u)
	}
	return u.doc.PublicAddress, nil
}

// PrivateAddress returns the public address of the unit.
func (u *Unit) PrivateAddress() (string, error) {
	if u.doc.PrivateAddress == "" {
		return "", fmt.Errorf("private address of unit %q not found", u)
	}
	return u.doc.PrivateAddress, nil
}

func (u *Unit) Refresh() error {
	err := u.st.units.FindId(u.doc.Name).One(&u.doc)
	if err != nil {
		return fmt.Errorf("cannot refresh unit %q: %v", u, err)
	}
	return nil
}

// AssignedMachineId returns the id of the assigned machine.
func (u *Unit) AssignedMachineId() (id int, err error) {
	defer trivial.ErrorContextf(&err, "cannot get machine id of unit %q", u)
	if u.IsPrincipal() {
		if u.doc.MachineId == nil {
			return 0, errors.New("unit not assigned to machine")
		}
		return *u.doc.MachineId, nil
	}
	pudoc := unitDoc{}
	sel := bson.D{{"_id", u.doc.Principal}, {"life", Alive}}
	err = u.st.units.Find(sel).One(&pudoc)
	if err != nil {
		return 0, err
	}
	if pudoc.MachineId == nil {
		return 0, errors.New("unit not assigned to machine")
	}
	return *pudoc.MachineId, nil
}

// AssignToMachine assigns this unit to a given machine.
func (u *Unit) AssignToMachine(m *Machine) (err error) {
	sel := bson.D{
		{"_id", u.doc.Name},
		{"$or", []bson.D{
			bson.D{{"machineid", nil}},
			bson.D{{"machineid", m.Id()}},
		}},
	}
	op := []txn.Operation{{
		Collection: u.st.units.Name,
		DocId:      u.doc.Name,
		Assert:     sel,
		Change:     bson.D{{"$set", bson.D{{"machineid", m.Id()}}}},
	}}
	err = u.st.runner.Run(op, "", nil)
	if err != nil {
		return fmt.Errorf("cannot assign unit %q to machine %s: %v", u, m, err)
	}
	u.doc.MachineId = &m.doc.Id
	return nil
}

// UnassignFromMachine removes the assignment between this unit and the
// machine it's assigned to.
func (u *Unit) UnassignFromMachine() (err error) {
	change := bson.D{{"$set", bson.D{{"machineid", nil}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err = u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot unassign unit %q from machine: %v", u, err)
	}
	u.doc.MachineId = nil
	return nil
}

// SetPublicAddress sets the public address of the unit.
func (u *Unit) SetPublicAddress(address string) error {
	change := bson.D{{"$set", bson.D{{"publicaddress", address}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot set public address of unit %q: %v", u, err)
	}
	u.doc.PublicAddress = address
	return nil
}

// SetPrivateAddress sets the public address of the unit.
func (u *Unit) SetPrivateAddress(address string) error {
	change := bson.D{{"$set", bson.D{{"privateaddress", address}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot set private address of unit %q: %v", u, err)
	}
	u.doc.PrivateAddress = address
	return nil
}

// SetResolved marks the unit as having had any previous state transition
// problems resolved, and informs the unit that it may attempt to
// reestablish normal workflow. The resolved mode parameter informs
// whether to attempt to reexecute previous failed hooks or to continue
// as if they had succeeded before.
func (u *Unit) SetResolved(mode ResolvedMode) (err error) {
	defer trivial.ErrorContextf(&err, "cannot set resolved mode for unit %q", u)
	if !(0 <= mode && mode < nResolvedModes) {
		return fmt.Errorf("invalid error resolution mode: %v", mode)
	}
	change := bson.D{{"$set", bson.D{{"resolved", mode}}}}
	sel := bson.D{
		{"_id", u.doc.Name},
		{"resolved", ResolvedNone},
	}
	err = u.st.units.Update(sel, change)
	if err == mgo.ErrNotFound {
		return errors.New("flag already set")
	}
	if err != nil {
		return err
	}
	u.doc.Resolved = mode
	return nil
}

// ClearResolved removes any resolved setting on the unit.
func (u *Unit) ClearResolved() error {
	change := bson.D{{"$set", bson.D{{"resolved", ResolvedNone}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("cannot clear resolved mode for unit %q: %v", u, err)
	}
	u.doc.Resolved = ResolvedNone
	return nil
}

// NeedsUpgrade returns whether the unit needs an upgrade
// and if it does, if this is forced.
func (u *Unit) NeedsUpgrade() (*NeedsUpgrade, error) {
	if u.doc.NeedsUpgrade == nil {
		return &NeedsUpgrade{Upgrade: false, Force: false}, nil
	}
	return u.doc.NeedsUpgrade, nil
}

// SetNeedsUpgrade informs the unit that it should perform
// a regular or forced upgrade.
func (u *Unit) SetNeedsUpgrade(force bool) (err error) {
	defer trivial.ErrorContextf(&err, "cannot inform unit %q about upgrade", u)
	nu := &NeedsUpgrade{Upgrade: true, Force: force}
	change := bson.D{{"$set", bson.D{{"needsupgrade", nu}}}}
	sel := bson.D{
		{"_id", u.doc.Name},
		{"$or", []bson.D{
			bson.D{{"needsupgrade", nil}},
			bson.D{{"needsupgrade", nu}},
		}},
	}
	err = u.st.units.Update(sel, change)
	if err == mgo.ErrNotFound {
		return errors.New("upgrade already enabled")
	}
	if err != nil {
		return err
	}
	u.doc.NeedsUpgrade = nu
	return nil
}

// ClearNeedsUpgrade resets the upgrade notification. It is typically
// done by the unit agent before beginning the upgrade.
func (u *Unit) ClearNeedsUpgrade() error {
	change := bson.D{{"$set", bson.D{{"needsupgrade", nil}}}}
	sel := bson.D{{"_id", u.doc.Name}}
	err := u.st.units.Update(sel, change)
	if err != nil {
		return fmt.Errorf("upgrade notification for unit %q cannot be reset: %v", u, err)
	}
	u.doc.NeedsUpgrade = nil
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"strconv"
	"unsafe"

	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-telldus-events/sensormonitor"
	"github.com/stampzilla/stampzilla-go/pkg/notifier"
	"github.com/stampzilla/stampzilla-go/protocol"
	"github.com/stampzilla/stampzilla-go/protocol/devices"
)

/*
#cgo LDFLAGS: -ltelldus-core

#include <telldus-core.h>

extern void registerCallbacks();
extern void unregisterCallbacks();
extern int updateDevices();

*/
import "C"

var VERSION string = "dev"
var BUILD_DATE string = ""

var node *protocol.Node
var state *State = &State{make(map[string]*Device), make(map[string]*Sensor, 0)}
var serverConnection basenode.Connection
var sensorMonitor *sensormonitor.Monitor

type Config struct {
	MonitorSensors []sensormonitor.SensorConfig
}

func main() {
	flag.Parse()

	//Get a config with the correct parameters
	config := basenode.NewConfig()
	basenode.SetConfig(config)
	nc := &Config{}
	err := config.NodeSpecific(&nc)
	if err != nil {
		log.Println(err)
		return
	}

	log.Println("Starting TELLDUS-events node")

	C.registerCallbacks()
	defer C.unregisterCallbacks()

	// Create new node description
	node = protocol.NewNode("telldus-events")
	node.Version = VERSION
	node.BuildDate = BUILD_DATE
	node.SetState(state)

	// Add devices
	cnt := C.updateDevices()
	log.Println("Updated devices (", cnt, " in total)")

	for _, dev := range state.Devices {
		node.AddElement(&protocol.Element{
			Type: protocol.ElementTypeToggle,
			Name: dev.Name,
			Command: &protocol.Command{
				Cmd:  "toggle",
				Args: []string{dev.Id},
			},
			Feedback: `Devices[` + dev.Id + `].State.On`,
		})

		node.Devices().Add(&devices.Device{
			Type:   "lamp",
			Name:   dev.Name,
			Id:     dev.Id,
			Online: true,
			StateMap: map[string]string{
				"on": "Devices[" + dev.Id + "]" + ".State.On",
			},
		})
	}

	for _, dev := range nc.MonitorSensors {
		id := strconv.Itoa(dev.Id)
		node.Devices_["s"+id] = &devices.Device{
			Type:   "sensor",
			Name:   dev.Name,
			Id:     "s" + id,
			Online: true,
			Node:   config.Uuid,
			StateMap: map[string]string{
				"temp":     "Sensors[" + id + "]" + ".Temp",
				"humidity": "Sensors[" + id + "]" + ".Humidity",
			},
		}
	}

	// Start the connection
	//go connection(host, port, node)

	serverConnection = basenode.Connect()
	notify := notifier.New(serverConnection)
	notify.SetSource(node)

	sensorMonitor = sensormonitor.New(notify)
	sensorMonitor.MonitorSensors = nc.MonitorSensors
	sensorMonitor.Start()
	log.Println("Monitoring Sensors: ", nc.MonitorSensors)

	go monitorState(serverConnection)

	// This worker recives all incomming commands
	go serverRecv(serverConnection)

	select {}
}

// WORKER that monitors the current connection state
func monitorState(connection basenode.Connection) {
	for s := range connection.State() {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send(node.Node())
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(connection basenode.Connection) {
	send := processCommandWorker()
	for d := range connection.Receive() {
		send <- d
	}
}

func processCommandWorker() chan protocol.Command {
	var send = make(chan protocol.Command, 100)

	go func() {
		for c := range send {
			if err := processCommand(c); err != nil {
				log.Println(err)
			}
		}
	}()

	return send
}

func processCommand(cmd protocol.Command) error {
	log.Println("Processing command", cmd)
	var result C.int = C.TELLSTICK_ERROR_UNKNOWN
	var id C.int = 0

	i, err := strconv.Atoi(cmd.Args[0])
	if err != nil {
		return fmt.Errorf("Failed to decode arg[0] to int %s %s", err, cmd.Args[0])
	}

	id = C.int(i)

	switch cmd.Cmd {
	case "on", "stampzilla-device-on":
		result = C.tdTurnOn(id)
	case "off", "stampzilla-device-off":
		result = C.tdTurnOff(id)
	case "toggle":
		s := C.tdLastSentCommand(id, C.TELLSTICK_TURNON|C.TELLSTICK_TURNOFF|C.TELLSTICK_DIM)
		switch {
		case s&C.TELLSTICK_DIM != 0:
			var state *C.char = C.tdLastSentValue(id)
			log.Println("DIM: ", C.GoString(state))
			if C.GoString(state) == "0" {
				result = C.tdTurnOn(id)
			} else {
				result = C.tdTurnOff(id)
			}
			C.tdReleaseString(state)
		case s&C.TELLSTICK_TURNON != 0:
			result = C.tdTurnOff(id)
		case s&C.TELLSTICK_TURNOFF != 0:
			result = C.tdTurnOn(id)
		}
	default:
		log.Println("Unknown command")
	}

	if result != C.TELLSTICK_SUCCESS {
		var errorString *C.char = C.tdGetErrorString(result)
		C.tdReleaseString(errorString)
		err := errors.New(C.GoString(errorString))
		log.Println("Command failed", err)
		return err
	}

	return nil
}

//export newDevice
func newDevice(id int, name *C.char, methods, s int, value *C.char) {
	//log.Println(id, C.GoString(name))

	features := []string{}
	if methods&C.TELLSTICK_TURNON != 0 {
		features = append(features, "on")
	}
	if methods&C.TELLSTICK_TURNOFF != 0 {
		features = append(features, "off")
	}
	if methods&C.TELLSTICK_BELL != 0 {
		features = append(features, "bell")
	}
	if methods&C.TELLSTICK_TOGGLE != 0 {
		features = append(features, "toggle")
	}
	if methods&C.TELLSTICK_DIM != 0 {
		features = append(features, "dim")
	}
	if methods&C.TELLSTICK_EXECUTE != 0 {
		features = append(features, "execute")
	}
	if methods&C.TELLSTICK_UP != 0 {
		features = append(features, "up")
	}
	if methods&C.TELLSTICK_DOWN != 0 {
		features = append(features, "down")
	}
	if methods&C.TELLSTICK_STOP != 0 {
		features = append(features, "stop")
	}

	if s&C.TELLSTICK_TURNON != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: true, Dim: 100})
	}
	if s&C.TELLSTICK_TURNOFF != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: false})
	}
	if s&C.TELLSTICK_DIM != 0 {
		var currentState = C.GoString(value)
		level, _ := strconv.ParseUint(currentState, 10, 16)
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: level > 0, Dim: int(level)})
	}

}

//export sensorEvent
func sensorEvent(protocol, model *C.char, sensorId, dataType int, value *C.char) {
	//log.Println("SensorEVENT: ", C.GoString(protocol), C.GoString(model), sensorId)

	var s *Sensor
	if s = state.GetSensor(sensorId); s == nil {
		s = state.AddSensor(sensorId)
	}
	sensorMonitor.Alive(s.Id)

	if dataType == C.TELLSTICK_TEMPERATURE {
		t, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Temperature %d : %f\n", s.Id, t)
		if s.Temp != t {
			//log.Println("Difference, sending to server")
			s.Temp = t
			serverConnection.Send(node.Node())
		}
	} else if dataType == C.TELLSTICK_HUMIDITY {
		h, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Humidity %d : %f\n", s.Id, h)
		if s.Humidity != h {
			//log.Println("Difference, sending to server")
			s.Humidity = h
			serverConnection.Send(node.Node())
		}
	}
}

//export deviceEvent
func deviceEvent(deviceId, method int, data *C.char, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceEVENT: ", deviceId, method, C.GoString(data))
	device := state.GetDevice(strconv.Itoa(deviceId))
	if method&C.TELLSTICK_TURNON != 0 {
		device.State.On = true
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_TURNOFF != 0 {
		device.State.On = false
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_DIM != 0 {
		level, err := strconv.ParseUint(C.GoString(data), 10, 16)
		if err != nil {
			log.Println(err)
			return
		}
		if level == 0 {
			device.State.On = false
		}
		if level > 0 {
			device.State.On = true
		}
		device.State.Dim = int(level)
		serverConnection.Send(node.Node())
	}
}

//export deviceChangeEvent
func deviceChangeEvent(deviceId, changeEvent, changeType, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceChangeEVENT: ", deviceId, changeEvent, changeType)
}

//export rawDeviceEvent
func rawDeviceEvent(data *C.char, controllerId, callbackId int, context unsafe.Pointer) {
	//log.Println("rawDeviceEVENT: ", controllerId, C.GoString(data))
}
<commit_msg>Remove node in devices for telldus sensors<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"strconv"
	"unsafe"

	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-telldus-events/sensormonitor"
	"github.com/stampzilla/stampzilla-go/pkg/notifier"
	"github.com/stampzilla/stampzilla-go/protocol"
	"github.com/stampzilla/stampzilla-go/protocol/devices"
)

/*
#cgo LDFLAGS: -ltelldus-core

#include <telldus-core.h>

extern void registerCallbacks();
extern void unregisterCallbacks();
extern int updateDevices();

*/
import "C"

var VERSION string = "dev"
var BUILD_DATE string = ""

var node *protocol.Node
var state *State = &State{make(map[string]*Device), make(map[string]*Sensor, 0)}
var serverConnection basenode.Connection
var sensorMonitor *sensormonitor.Monitor

type Config struct {
	MonitorSensors []sensormonitor.SensorConfig
}

func main() {
	flag.Parse()

	//Get a config with the correct parameters
	config := basenode.NewConfig()
	basenode.SetConfig(config)
	nc := &Config{}
	err := config.NodeSpecific(&nc)
	if err != nil {
		log.Println(err)
		return
	}

	log.Println("Starting TELLDUS-events node")

	C.registerCallbacks()
	defer C.unregisterCallbacks()

	// Create new node description
	node = protocol.NewNode("telldus-events")
	node.Version = VERSION
	node.BuildDate = BUILD_DATE
	node.SetState(state)

	// Add devices
	cnt := C.updateDevices()
	log.Println("Updated devices (", cnt, " in total)")

	for _, dev := range state.Devices {
		node.AddElement(&protocol.Element{
			Type: protocol.ElementTypeToggle,
			Name: dev.Name,
			Command: &protocol.Command{
				Cmd:  "toggle",
				Args: []string{dev.Id},
			},
			Feedback: `Devices[` + dev.Id + `].State.On`,
		})

		node.Devices().Add(&devices.Device{
			Type:   "lamp",
			Name:   dev.Name,
			Id:     dev.Id,
			Online: true,
			StateMap: map[string]string{
				"on": "Devices[" + dev.Id + "]" + ".State.On",
			},
		})
	}

	for _, dev := range nc.MonitorSensors {
		id := strconv.Itoa(dev.Id)
		node.Devices_["s"+id] = &devices.Device{
			Type:   "sensor",
			Name:   dev.Name,
			Id:     "s" + id,
			Online: true,
			StateMap: map[string]string{
				"temp":     "Sensors[" + id + "]" + ".Temp",
				"humidity": "Sensors[" + id + "]" + ".Humidity",
			},
		}
	}

	// Start the connection
	//go connection(host, port, node)

	serverConnection = basenode.Connect()
	notify := notifier.New(serverConnection)
	notify.SetSource(node)

	sensorMonitor = sensormonitor.New(notify)
	sensorMonitor.MonitorSensors = nc.MonitorSensors
	sensorMonitor.Start()
	log.Println("Monitoring Sensors: ", nc.MonitorSensors)

	go monitorState(serverConnection)

	// This worker recives all incomming commands
	go serverRecv(serverConnection)

	select {}
}

// WORKER that monitors the current connection state
func monitorState(connection basenode.Connection) {
	for s := range connection.State() {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send(node.Node())
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(connection basenode.Connection) {
	send := processCommandWorker()
	for d := range connection.Receive() {
		send <- d
	}
}

func processCommandWorker() chan protocol.Command {
	var send = make(chan protocol.Command, 100)

	go func() {
		for c := range send {
			if err := processCommand(c); err != nil {
				log.Println(err)
			}
		}
	}()

	return send
}

func processCommand(cmd protocol.Command) error {
	log.Println("Processing command", cmd)
	var result C.int = C.TELLSTICK_ERROR_UNKNOWN
	var id C.int = 0

	i, err := strconv.Atoi(cmd.Args[0])
	if err != nil {
		return fmt.Errorf("Failed to decode arg[0] to int %s %s", err, cmd.Args[0])
	}

	id = C.int(i)

	switch cmd.Cmd {
	case "on", "stampzilla-device-on":
		result = C.tdTurnOn(id)
	case "off", "stampzilla-device-off":
		result = C.tdTurnOff(id)
	case "toggle":
		s := C.tdLastSentCommand(id, C.TELLSTICK_TURNON|C.TELLSTICK_TURNOFF|C.TELLSTICK_DIM)
		switch {
		case s&C.TELLSTICK_DIM != 0:
			var state *C.char = C.tdLastSentValue(id)
			log.Println("DIM: ", C.GoString(state))
			if C.GoString(state) == "0" {
				result = C.tdTurnOn(id)
			} else {
				result = C.tdTurnOff(id)
			}
			C.tdReleaseString(state)
		case s&C.TELLSTICK_TURNON != 0:
			result = C.tdTurnOff(id)
		case s&C.TELLSTICK_TURNOFF != 0:
			result = C.tdTurnOn(id)
		}
	default:
		log.Println("Unknown command")
	}

	if result != C.TELLSTICK_SUCCESS {
		var errorString *C.char = C.tdGetErrorString(result)
		C.tdReleaseString(errorString)
		err := errors.New(C.GoString(errorString))
		log.Println("Command failed", err)
		return err
	}

	return nil
}

//export newDevice
func newDevice(id int, name *C.char, methods, s int, value *C.char) {
	//log.Println(id, C.GoString(name))

	features := []string{}
	if methods&C.TELLSTICK_TURNON != 0 {
		features = append(features, "on")
	}
	if methods&C.TELLSTICK_TURNOFF != 0 {
		features = append(features, "off")
	}
	if methods&C.TELLSTICK_BELL != 0 {
		features = append(features, "bell")
	}
	if methods&C.TELLSTICK_TOGGLE != 0 {
		features = append(features, "toggle")
	}
	if methods&C.TELLSTICK_DIM != 0 {
		features = append(features, "dim")
	}
	if methods&C.TELLSTICK_EXECUTE != 0 {
		features = append(features, "execute")
	}
	if methods&C.TELLSTICK_UP != 0 {
		features = append(features, "up")
	}
	if methods&C.TELLSTICK_DOWN != 0 {
		features = append(features, "down")
	}
	if methods&C.TELLSTICK_STOP != 0 {
		features = append(features, "stop")
	}

	if s&C.TELLSTICK_TURNON != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: true, Dim: 100})
	}
	if s&C.TELLSTICK_TURNOFF != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: false})
	}
	if s&C.TELLSTICK_DIM != 0 {
		var currentState = C.GoString(value)
		level, _ := strconv.ParseUint(currentState, 10, 16)
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: level > 0, Dim: int(level)})
	}

}

//export sensorEvent
func sensorEvent(protocol, model *C.char, sensorId, dataType int, value *C.char) {
	//log.Println("SensorEVENT: ", C.GoString(protocol), C.GoString(model), sensorId)

	var s *Sensor
	if s = state.GetSensor(sensorId); s == nil {
		s = state.AddSensor(sensorId)
	}
	sensorMonitor.Alive(s.Id)

	if dataType == C.TELLSTICK_TEMPERATURE {
		t, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Temperature %d : %f\n", s.Id, t)
		if s.Temp != t {
			//log.Println("Difference, sending to server")
			s.Temp = t
			serverConnection.Send(node.Node())
		}
	} else if dataType == C.TELLSTICK_HUMIDITY {
		h, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Humidity %d : %f\n", s.Id, h)
		if s.Humidity != h {
			//log.Println("Difference, sending to server")
			s.Humidity = h
			serverConnection.Send(node.Node())
		}
	}
}

//export deviceEvent
func deviceEvent(deviceId, method int, data *C.char, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceEVENT: ", deviceId, method, C.GoString(data))
	device := state.GetDevice(strconv.Itoa(deviceId))
	if method&C.TELLSTICK_TURNON != 0 {
		device.State.On = true
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_TURNOFF != 0 {
		device.State.On = false
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_DIM != 0 {
		level, err := strconv.ParseUint(C.GoString(data), 10, 16)
		if err != nil {
			log.Println(err)
			return
		}
		if level == 0 {
			device.State.On = false
		}
		if level > 0 {
			device.State.On = true
		}
		device.State.Dim = int(level)
		serverConnection.Send(node.Node())
	}
}

//export deviceChangeEvent
func deviceChangeEvent(deviceId, changeEvent, changeType, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceChangeEVENT: ", deviceId, changeEvent, changeType)
}

//export rawDeviceEvent
func rawDeviceEvent(data *C.char, controllerId, callbackId int, context unsafe.Pointer) {
	//log.Println("rawDeviceEVENT: ", controllerId, C.GoString(data))
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

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

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

package api

import (
	"net/http"
	"strings"

	"github.com/vmware/harbor/dao"
	"github.com/vmware/harbor/models"
	svc_utils "github.com/vmware/harbor/service/utils"
	"github.com/vmware/harbor/utils/log"
)

// StatisticAPI handles request to /api/statistics/
type StatisticAPI struct {
	BaseAPI
	userID   int
	username string
}

//Prepare validates the URL and the user
func (s *StatisticAPI) Prepare() {
	userID, ok := s.GetSession("userId").(int)
	if !ok {
		s.userID = dao.NonExistUserID
	} else {
		s.userID = userID
	}
	username, ok := s.GetSession("username").(string)
	if !ok {
		log.Warning("failed to get username from session")
		s.username = ""
	} else {
		s.username = username
	}
}

// Get total projects and repos of the user
func (s *StatisticAPI) Get() {
	queryProject := models.Project{UserID: s.userID}
	projectList, err := dao.QueryProject(queryProject)
	proMap := map[string]int{}
	if err != nil {
		log.Errorf("Error occured in QueryProject, error: %v", err)
		s.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	isAdmin, err0 := dao.IsAdminRole(s.userID)
	if err0 != nil {
		log.Errorf("Error occured in check admin, error: %v", err0)
		s.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	if isAdmin {
		proMap["total_project_count"] = len(projectList)
		proMap["total_repo_count"] = getTotalRepoCount()
	}
	for i := 0; i < len(projectList); i++ {
		if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER {
			proMap["my_project_count"]++
			proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
		}
		if projectList[i].Public == 1 {
			proMap["public_project_count"]++
			proMap["public_repo_count"] += getRepoCountByProject(projectList[i].Name)
		}
	}
	s.Data["json"] = proMap
	s.ServeJSON()
}

//getReposByProject returns repo numbers of specified project
func getRepoCountByProject(projectName string) int {
	repoList, err := svc_utils.GetRepoFromCache()
	if err != nil {
		log.Errorf("Failed to get repo from cache, error: %v", err)
		return 0
	}
	var resp []string
	if len(projectName) > 0 {
		for _, r := range repoList {
			if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
				resp = append(resp, r)
			}
		}
		return len(resp)
	}
	return 0
}

//getTotalRepoCount returns total repo count
func getTotalRepoCount() int {
	repoList, err := svc_utils.GetRepoFromCache()
	if err != nil {
		log.Errorf("Failed to get repo from cache, error: %v", err)
		return 0
	}
	return len(repoList)

}
<commit_msg>change code of statistic api<commit_after>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

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

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

package api

import (
	"net/http"
	"strings"

	"github.com/vmware/harbor/dao"
	"github.com/vmware/harbor/models"
	svc_utils "github.com/vmware/harbor/service/utils"
	"github.com/vmware/harbor/utils/log"
)

// StatisticAPI handles request to /api/statistics/
type StatisticAPI struct {
	BaseAPI
	userID   int
	username string
}

//Prepare validates the URL and the user
func (s *StatisticAPI) Prepare() {
	userID, ok := s.GetSession("userId").(int)
	if !ok {
		s.userID = dao.NonExistUserID
	} else {
		s.userID = userID
	}
	username, ok := s.GetSession("username").(string)
	if !ok {
		log.Warning("failed to get username from session")
		s.username = ""
	} else {
		s.username = username
	}
}

// Get total projects and repos of the user
func (s *StatisticAPI) Get() {
	queryProject := models.Project{UserID: s.userID}
	projectList, err := dao.QueryProject(queryProject)
	if err != nil {
		log.Errorf("Error occured in QueryProject, error: %v", err)
		s.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	proMap := map[string]int{}
	isAdmin, err := dao.IsAdminRole(s.userID)
	if err != nil {
		log.Errorf("Error occured in check admin, error: %v", err)
		s.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	if isAdmin {
		proMap["total_project_count"] = len(projectList)
		proMap["total_repo_count"] = getTotalRepoCount()
	}
	for i := 0; i < len(projectList); i++ {
		if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER ||
			projectList[i].Role == models.GUEST {
			proMap["my_project_count"]++
			proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
		}
		if projectList[i].Public == 1 {
			proMap["public_project_count"]++
			proMap["public_repo_count"] += getRepoCountByProject(projectList[i].Name)
		}
	}
	s.Data["json"] = proMap
	s.ServeJSON()
}

//getReposByProject returns repo numbers of specified project
func getRepoCountByProject(projectName string) int {
	repoList, err := svc_utils.GetRepoFromCache()
	if err != nil {
		log.Errorf("Failed to get repo from cache, error: %v", err)
		return 0
	}
	var resp int
	if len(projectName) > 0 {
		for _, r := range repoList {
			if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
				resp += 1
			}
		}
		return resp
	}
	return 0
}

//getTotalRepoCount returns total repo count
func getTotalRepoCount() int {
	repoList, err := svc_utils.GetRepoFromCache()
	if err != nil {
		log.Errorf("Failed to get repo from cache, error: %v", err)
		return 0
	}
	return len(repoList)

}
<|endoftext|>


<commit_before>package pstoreds

import (
	"context"
	"fmt"
	"io"
	"time"

	base32 "github.com/multiformats/go-base32"

	ds "github.com/ipfs/go-datastore"
	query "github.com/ipfs/go-datastore/query"

	peer "github.com/libp2p/go-libp2p-core/peer"
	peerstore "github.com/libp2p/go-libp2p-core/peerstore"
	pstore "github.com/libp2p/go-libp2p-peerstore"
)

// Configuration object for the peerstore.
type Options struct {
	// The size of the in-memory cache. A value of 0 or lower disables the cache.
	CacheSize uint

	// Sweep interval to purge expired addresses from the datastore. If this is a zero value, GC will not run
	// automatically, but it'll be available on demand via explicit calls.
	GCPurgeInterval time.Duration

	// Interval to renew the GC lookahead window. If this is a zero value, lookahead will be disabled and we'll
	// traverse the entire datastore for every purge cycle.
	GCLookaheadInterval time.Duration

	// Initial delay before GC processes start. Intended to give the system breathing room to fully boot
	// before starting GC.
	GCInitialDelay time.Duration
}

// DefaultOpts returns the default options for a persistent peerstore, with the full-purge GC algorithm:
//
// * Cache size: 1024.
// * GC purge interval: 2 hours.
// * GC lookahead interval: disabled.
// * GC initial delay: 60 seconds.
func DefaultOpts() Options {
	return Options{
		CacheSize:           1024,
		GCPurgeInterval:     2 * time.Hour,
		GCLookaheadInterval: 0,
		GCInitialDelay:      60 * time.Second,
	}
}

type pstoreds struct {
	peerstore.Metrics

	dsKeyBook
	dsAddrBook
	dsProtoBook
	dsPeerMetadata
}

// NewPeerstore creates a peerstore backed by the provided persistent datastore.
func NewPeerstore(ctx context.Context, store ds.Batching, opts Options) (*pstoreds, error) {
	addrBook, err := NewAddrBook(ctx, store, opts)
	if err != nil {
		return nil, err
	}

	keyBook, err := NewKeyBook(ctx, store, opts)
	if err != nil {
		return nil, err
	}

	peerMetadata, err := NewPeerMetadata(ctx, store, opts)
	if err != nil {
		return nil, err
	}

	protoBook := NewProtoBook(peerMetadata)

	ps := &pstoreds{
		Metrics:        pstore.NewMetrics(),
		dsKeyBook:      *keyBook,
		dsAddrBook:     *addrBook,
		dsPeerMetadata: *peerMetadata,
		dsProtoBook:    *protoBook,
	}
	return ps, nil
}

// uniquePeerIds extracts and returns unique peer IDs from database keys.
func uniquePeerIds(ds ds.Datastore, prefix ds.Key, extractor func(result query.Result) string) (peer.IDSlice, error) {
	var (
		q       = query.Query{Prefix: prefix.String(), KeysOnly: true}
		results query.Results
		err     error
	)

	if results, err = ds.Query(q); err != nil {
		log.Error(err)
		return nil, err
	}

	defer results.Close()

	idset := make(map[string]struct{})
	for result := range results.Next() {
		k := extractor(result)
		idset[k] = struct{}{}
	}

	if len(idset) == 0 {
		return peer.IDSlice{}, nil
	}

	ids := make(peer.IDSlice, 0, len(idset))
	for id := range idset {
		pid, _ := base32.RawStdEncoding.DecodeString(id)
		id, _ := peer.IDFromBytes(pid)
		ids = append(ids, id)
	}
	return ids, nil
}

func (ps *pstoreds) Close() (err error) {
	var errs []error
	weakClose := func(name string, c interface{}) {
		if cl, ok := c.(io.Closer); ok {
			if err = cl.Close(); err != nil {
				errs = append(errs, fmt.Errorf("%s error: %s", name, err))
			}
		}
	}

	weakClose("keybook", ps.dsKeyBook)
	weakClose("addressbook", ps.dsAddrBook)
	weakClose("protobook", ps.dsProtoBook)
	weakClose("peermetadata", ps.dsPeerMetadata)

	if len(errs) > 0 {
		return fmt.Errorf("failed while closing peerstore; err(s): %q", errs)
	}
	return nil
}

func (ps *pstoreds) Peers() peer.IDSlice {
	set := map[peer.ID]struct{}{}
	for _, p := range ps.PeersWithKeys() {
		set[p] = struct{}{}
	}
	for _, p := range ps.PeersWithAddrs() {
		set[p] = struct{}{}
	}

	pps := make(peer.IDSlice, 0, len(set))
	for p := range set {
		pps = append(pps, p)
	}
	return pps
}

func (ps *pstoreds) PeerInfo(p peer.ID) peer.AddrInfo {
	return peer.AddrInfo{
		ID:    p,
		Addrs: ps.dsAddrBook.Addrs(p),
	}
}
<commit_msg>fix: avoid copying locks/waitgroups<commit_after>package pstoreds

import (
	"context"
	"fmt"
	"io"
	"time"

	base32 "github.com/multiformats/go-base32"

	ds "github.com/ipfs/go-datastore"
	query "github.com/ipfs/go-datastore/query"

	peer "github.com/libp2p/go-libp2p-core/peer"
	peerstore "github.com/libp2p/go-libp2p-core/peerstore"
	pstore "github.com/libp2p/go-libp2p-peerstore"
)

// Configuration object for the peerstore.
type Options struct {
	// The size of the in-memory cache. A value of 0 or lower disables the cache.
	CacheSize uint

	// Sweep interval to purge expired addresses from the datastore. If this is a zero value, GC will not run
	// automatically, but it'll be available on demand via explicit calls.
	GCPurgeInterval time.Duration

	// Interval to renew the GC lookahead window. If this is a zero value, lookahead will be disabled and we'll
	// traverse the entire datastore for every purge cycle.
	GCLookaheadInterval time.Duration

	// Initial delay before GC processes start. Intended to give the system breathing room to fully boot
	// before starting GC.
	GCInitialDelay time.Duration
}

// DefaultOpts returns the default options for a persistent peerstore, with the full-purge GC algorithm:
//
// * Cache size: 1024.
// * GC purge interval: 2 hours.
// * GC lookahead interval: disabled.
// * GC initial delay: 60 seconds.
func DefaultOpts() Options {
	return Options{
		CacheSize:           1024,
		GCPurgeInterval:     2 * time.Hour,
		GCLookaheadInterval: 0,
		GCInitialDelay:      60 * time.Second,
	}
}

type pstoreds struct {
	peerstore.Metrics

	*dsKeyBook
	*dsAddrBook
	*dsProtoBook
	*dsPeerMetadata
}

// NewPeerstore creates a peerstore backed by the provided persistent datastore.
func NewPeerstore(ctx context.Context, store ds.Batching, opts Options) (*pstoreds, error) {
	addrBook, err := NewAddrBook(ctx, store, opts)
	if err != nil {
		return nil, err
	}

	keyBook, err := NewKeyBook(ctx, store, opts)
	if err != nil {
		return nil, err
	}

	peerMetadata, err := NewPeerMetadata(ctx, store, opts)
	if err != nil {
		return nil, err
	}

	protoBook := NewProtoBook(peerMetadata)

	ps := &pstoreds{
		Metrics:        pstore.NewMetrics(),
		dsKeyBook:      keyBook,
		dsAddrBook:     addrBook,
		dsPeerMetadata: peerMetadata,
		dsProtoBook:    protoBook,
	}
	return ps, nil
}

// uniquePeerIds extracts and returns unique peer IDs from database keys.
func uniquePeerIds(ds ds.Datastore, prefix ds.Key, extractor func(result query.Result) string) (peer.IDSlice, error) {
	var (
		q       = query.Query{Prefix: prefix.String(), KeysOnly: true}
		results query.Results
		err     error
	)

	if results, err = ds.Query(q); err != nil {
		log.Error(err)
		return nil, err
	}

	defer results.Close()

	idset := make(map[string]struct{})
	for result := range results.Next() {
		k := extractor(result)
		idset[k] = struct{}{}
	}

	if len(idset) == 0 {
		return peer.IDSlice{}, nil
	}

	ids := make(peer.IDSlice, 0, len(idset))
	for id := range idset {
		pid, _ := base32.RawStdEncoding.DecodeString(id)
		id, _ := peer.IDFromBytes(pid)
		ids = append(ids, id)
	}
	return ids, nil
}

func (ps *pstoreds) Close() (err error) {
	var errs []error
	weakClose := func(name string, c interface{}) {
		if cl, ok := c.(io.Closer); ok {
			if err = cl.Close(); err != nil {
				errs = append(errs, fmt.Errorf("%s error: %s", name, err))
			}
		}
	}

	weakClose("keybook", ps.dsKeyBook)
	weakClose("addressbook", ps.dsAddrBook)
	weakClose("protobook", ps.dsProtoBook)
	weakClose("peermetadata", ps.dsPeerMetadata)

	if len(errs) > 0 {
		return fmt.Errorf("failed while closing peerstore; err(s): %q", errs)
	}
	return nil
}

func (ps *pstoreds) Peers() peer.IDSlice {
	set := map[peer.ID]struct{}{}
	for _, p := range ps.PeersWithKeys() {
		set[p] = struct{}{}
	}
	for _, p := range ps.PeersWithAddrs() {
		set[p] = struct{}{}
	}

	pps := make(peer.IDSlice, 0, len(set))
	for p := range set {
		pps = append(pps, p)
	}
	return pps
}

func (ps *pstoreds) PeerInfo(p peer.ID) peer.AddrInfo {
	return peer.AddrInfo{
		ID:    p,
		Addrs: ps.dsAddrBook.Addrs(p),
	}
}
<|endoftext|>