<commit_before>package stats

import (
	"fmt"
	"math"
	"regexp"
	"sync"

	"github.com/tkuchiki/alp/errors"

	"github.com/tkuchiki/alp/helpers"

	"github.com/tkuchiki/alp/parsers"

	"github.com/tkuchiki/alp/options"
)

type hints struct {
	values map[string]int
	len    int
	mu     sync.RWMutex
}

func newHints() *hints {
	return &hints{
		values: make(map[string]int),
	}
}

func (h *hints) loadOrStore(key string) int {
	h.mu.Lock()
	defer h.mu.Unlock()
	_, ok := h.values[key]
	if !ok {
		h.values[key] = h.len
		h.len++
	}

	return h.values[key]
}

type HTTPStats struct {
	hints                          *hints
	stats                          httpStats
	useResponseTimePercentile      bool
	useRequestBodyBytesPercentile  bool
	useResponseBodyBytesPercentile bool
	filter                         *Filter
	options                        *options.Options
	uriMatchingGroups              []*regexp.Regexp
}

func NewHTTPStats(useResTimePercentile, useRequestBodyBytesPercentile, useResponseBodyBytesPercentile bool) *HTTPStats {
	return &HTTPStats{
		hints:                          newHints(),
		stats:                          make([]*HTTPStat, 0),
		useResponseTimePercentile:      useResTimePercentile,
		useResponseBodyBytesPercentile: useResponseBodyBytesPercentile,
	}
}

func (hs *HTTPStats) Set(uri, method string, status int, restime, resBodyBytes, reqBodyBytes float64) {
	if len(hs.uriMatchingGroups) > 0 {
		for _, re := range hs.uriMatchingGroups {
			if ok := re.Match([]byte(uri)); ok {
				pattern := re.String()
				uri = pattern
			}
		}
	}

	key := fmt.Sprintf("%s_%s", method, uri)

	idx := hs.hints.loadOrStore(key)

	if idx >= len(hs.stats) {
		hs.stats = append(hs.stats, newHTTPStat(uri, method, hs.useResponseTimePercentile, hs.useRequestBodyBytesPercentile, hs.useResponseBodyBytesPercentile))
	}

	hs.stats[idx].Set(status, restime, resBodyBytes, reqBodyBytes)
}

func (hs *HTTPStats) Stats() []*HTTPStat {
	return hs.stats
}

func (hs *HTTPStats) CountUris() int {
	return hs.hints.len
}

func (hs *HTTPStats) SetOptions(options *options.Options) {
	hs.options = options
}

func (hs *HTTPStats) SetURIMatchingGroups(groups []string) error {
	uriGroups, err := helpers.CompileUriMatchingGroups(groups)
	if err != nil {
		return err
	}

	hs.uriMatchingGroups = uriGroups

	return nil
}

func (hs *HTTPStats) InitFilter(options *options.Options) error {
	hs.filter = NewFilter(options)
	return hs.filter.Init()
}

func (hs *HTTPStats) DoFilter(pstat *parsers.ParsedHTTPStat) (bool, error) {
	err := hs.filter.Do(pstat)
	if err == errors.SkipReadLineErr {
		return false, nil
	} else if err != nil {
		return false, err
	}

	return true, nil
}

func (hs *HTTPStats) CountAll() map[string]int {
	counts := make(map[string]int, 6)

	for _, s := range hs.stats {
		counts["count"] += s.Cnt
		counts["1xx"] += s.Status1xx
		counts["2xx"] += s.Status2xx
		counts["3xx"] += s.Status3xx
		counts["4xx"] += s.Status4xx
		counts["5xx"] += s.Status5xx
	}

	return counts
}

func (hs *HTTPStats) SortWithOptions() {
	hs.Sort(hs.options.Sort, hs.options.Reverse)
}

type HTTPStat struct {
	Uri               string        `yaml:uri`
	Cnt               int           `yaml:count`
	Status1xx         int           `yaml:status1xx`
	Status2xx         int           `yaml:status2xx`
	Status3xx         int           `yaml:status3xx`
	Status4xx         int           `yaml:status4xx`
	Status5xx         int           `yaml:status5xx`
	Method            string        `yaml:method`
	ResponseTime      *responseTime `yaml:response_time`
	RequestBodyBytes  *bodyBytes    `yaml:request_body_bytes`
	ResponseBodyBytes *bodyBytes    `yaml:response_body_bytes`
	Time              string
}

type httpStats []*HTTPStat

func newHTTPStat(uri, method string, useResTimePercentile, useRequestBodyBytesPercentile, useResponseBodyBytesPercentile bool) *HTTPStat {
	return &HTTPStat{
		Uri:               uri,
		Method:            method,
		ResponseTime:      newResponseTime(useResTimePercentile),
		RequestBodyBytes:  newBodyBytes(useRequestBodyBytesPercentile),
		ResponseBodyBytes: newBodyBytes(useResponseBodyBytesPercentile),
	}
}

func (hs *HTTPStat) Set(status int, restime, reqBodyBytes, resBodyBytes float64) {
	hs.Cnt++
	hs.setStatus(status)
	hs.ResponseTime.Set(restime)
	hs.RequestBodyBytes.Set(reqBodyBytes)
	hs.ResponseBodyBytes.Set(resBodyBytes)
}

func (hs *HTTPStat) setStatus(status int) {
	if status >= 100 && status <= 199 {
		hs.Status1xx++
	} else if status >= 200 && status <= 299 {
		hs.Status2xx++
	} else if status >= 300 && status <= 399 {
		hs.Status3xx++
	} else if status >= 400 && status <= 499 {
		hs.Status4xx++
	} else if status >= 500 && status <= 599 {
		hs.Status5xx++
	}
}

func (hs *HTTPStat) StrStatus1xx() string {
	return fmt.Sprint(hs.Status1xx)
}

func (hs *HTTPStat) StrStatus2xx() string {
	return fmt.Sprint(hs.Status2xx)
}

func (hs *HTTPStat) StrStatus3xx() string {
	return fmt.Sprint(hs.Status3xx)
}

func (hs *HTTPStat) StrStatus4xx() string {
	return fmt.Sprint(hs.Status4xx)
}

func (hs *HTTPStat) StrStatus5xx() string {
	return fmt.Sprint(hs.Status5xx)
}

func (hs *HTTPStat) Count() int {
	return hs.Cnt
}

func (hs *HTTPStat) StrCount() string {
	return fmt.Sprint(hs.Cnt)
}

func (hs *HTTPStat) MaxResponseTime() float64 {
	return hs.ResponseTime.Max
}

func (hs *HTTPStat) MinResponseTime() float64 {
	return hs.ResponseTime.Min
}

func (hs *HTTPStat) SumResponseTime() float64 {
	return hs.ResponseTime.Sum
}

func (hs *HTTPStat) AvgResponseTime() float64 {
	return hs.ResponseTime.Avg(hs.Cnt)
}

func (hs *HTTPStat) P1ResponseTime() float64 {
	return hs.ResponseTime.P1(hs.Cnt)
}

func (hs *HTTPStat) P50ResponseTime() float64 {
	return hs.ResponseTime.P50(hs.Cnt)
}

func (hs *HTTPStat) P90ResponseTime() float64 {
	return hs.ResponseTime.P90(hs.Cnt)
}

func (hs *HTTPStat) P99ResponseTime() float64 {
	return hs.ResponseTime.P99(hs.Cnt)
}

func (hs *HTTPStat) StddevResponseTime() float64 {
	return hs.ResponseTime.Stddev(hs.Cnt)
}

// request
func (hs *HTTPStat) MaxRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Max
}

func (hs *HTTPStat) MinRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Min
}

func (hs *HTTPStat) SumRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Sum
}

func (hs *HTTPStat) AvgRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Avg(hs.Cnt)
}

func (hs *HTTPStat) P1RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P1(hs.Cnt)
}

func (hs *HTTPStat) P50RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P50(hs.Cnt)
}

func (hs *HTTPStat) P90RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P90(hs.Cnt)
}

func (hs *HTTPStat) P99RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P99(hs.Cnt)
}

func (hs *HTTPStat) StddevRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Stddev(hs.Cnt)
}

// response
func (hs *HTTPStat) MaxResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Max
}

func (hs *HTTPStat) MinResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Min
}

func (hs *HTTPStat) SumResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Sum
}

func (hs *HTTPStat) AvgResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Avg(hs.Cnt)
}

func (hs *HTTPStat) P1ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P1(hs.Cnt)
}

func (hs *HTTPStat) P50ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P50(hs.Cnt)
}

func (hs *HTTPStat) P90ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P90(hs.Cnt)
}

func (hs *HTTPStat) P99ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P99(hs.Cnt)
}

func (hs *HTTPStat) StddevResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Stddev(hs.Cnt)
}

func percentRank(l int, n int) int {
	pLen := (l * n / 100) - 1
	if pLen < 0 {
		pLen = 0
	}

	return pLen
}

type responseTime struct {
	Max           float64
	Min           float64
	Sum           float64
	usePercentile bool
	Percentiles   []float64
}

func newResponseTime(usePercentile bool) *responseTime {
	return &responseTime{
		usePercentile: usePercentile,
		Percentiles:   make([]float64, 0),
	}
}

func (res *responseTime) Set(val float64) {
	if res.Max < val {
		res.Max = val
	}

	if res.Min >= val || res.Min == 0 {
		res.Min = val
	}

	res.Sum += val

	if res.usePercentile {
		res.Percentiles = append(res.Percentiles, val)
	}
}

func (res *responseTime) Avg(cnt int) float64 {
	return res.Sum / float64(cnt)
}

func (res *responseTime) P1(cnt int) float64 {
	if !res.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 1)
	return res.Percentiles[plen]
}

func (res *responseTime) P50(cnt int) float64 {
	if !res.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 50)
	return res.Percentiles[plen]
}

func (res *responseTime) P90(cnt int) float64 {
	if !res.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 90)
	return res.Percentiles[plen]
}

func (res *responseTime) P99(cnt int) float64 {
	if !res.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 99)
	return res.Percentiles[plen]
}

func (res *responseTime) Stddev(cnt int) float64 {
	if !res.usePercentile {
		return 0.0
	}

	var stdd float64
	avg := res.Avg(cnt)
	n := float64(cnt)

	for _, v := range res.Percentiles {
		stdd += (v - avg) * (v - avg)
	}

	return math.Sqrt(stdd / n)
}

type bodyBytes struct {
	Max           float64
	Min           float64
	Sum           float64
	usePercentile bool
	percentiles   []float64
}

func newBodyBytes(usePercentile bool) *bodyBytes {
	return &bodyBytes{
		usePercentile: usePercentile,
		percentiles:   make([]float64, 0),
	}
}

func (body *bodyBytes) Set(val float64) {
	if body.Max < val {
		body.Max = val
	}

	if body.Min >= val || body.Min == 0.0 {
		body.Min = val
	}

	body.Sum += val

	if body.usePercentile {
		body.percentiles = append(body.percentiles, val)
	}
}

func (body *bodyBytes) Avg(cnt int) float64 {
	return body.Sum / float64(cnt)
}

func (body *bodyBytes) P1(cnt int) float64 {
	if !body.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 1)
	return body.percentiles[plen]
}

func (body *bodyBytes) P50(cnt int) float64 {
	if !body.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 50)
	return body.percentiles[plen]
}

func (body *bodyBytes) P90(cnt int) float64 {
	if !body.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 90)
	return body.percentiles[plen]
}

func (body *bodyBytes) P99(cnt int) float64 {
	if !body.usePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 99)
	return body.percentiles[plen]
}

func (body *bodyBytes) Stddev(cnt int) float64 {
	if !body.usePercentile {
		return 0.0
	}

	var stdd float64
	avg := body.Avg(cnt)
	n := float64(cnt)

	for _, v := range body.percentiles {
		stdd += (v - avg) * (v - avg)
	}

	return math.Sqrt(stdd / n)
}
<commit_msg>Fix --dump / --load<commit_after>package stats

import (
	"fmt"
	"math"
	"regexp"
	"sync"

	"github.com/tkuchiki/alp/errors"

	"github.com/tkuchiki/alp/helpers"

	"github.com/tkuchiki/alp/parsers"

	"github.com/tkuchiki/alp/options"
)

type hints struct {
	values map[string]int
	len    int
	mu     sync.RWMutex
}

func newHints() *hints {
	return &hints{
		values: make(map[string]int),
	}
}

func (h *hints) loadOrStore(key string) int {
	h.mu.Lock()
	defer h.mu.Unlock()
	_, ok := h.values[key]
	if !ok {
		h.values[key] = h.len
		h.len++
	}

	return h.values[key]
}

type HTTPStats struct {
	hints                          *hints
	stats                          httpStats
	useResponseTimePercentile      bool
	useRequestBodyBytesPercentile  bool
	useResponseBodyBytesPercentile bool
	filter                         *Filter
	options                        *options.Options
	uriMatchingGroups              []*regexp.Regexp
}

func NewHTTPStats(useResTimePercentile, useRequestBodyBytesPercentile, useResponseBodyBytesPercentile bool) *HTTPStats {
	return &HTTPStats{
		hints:                          newHints(),
		stats:                          make([]*HTTPStat, 0),
		useResponseTimePercentile:      useResTimePercentile,
		useResponseBodyBytesPercentile: useResponseBodyBytesPercentile,
	}
}

func (hs *HTTPStats) Set(uri, method string, status int, restime, resBodyBytes, reqBodyBytes float64) {
	if len(hs.uriMatchingGroups) > 0 {
		for _, re := range hs.uriMatchingGroups {
			if ok := re.Match([]byte(uri)); ok {
				pattern := re.String()
				uri = pattern
			}
		}
	}

	key := fmt.Sprintf("%s_%s", method, uri)

	idx := hs.hints.loadOrStore(key)

	if idx >= len(hs.stats) {
		hs.stats = append(hs.stats, newHTTPStat(uri, method, hs.useResponseTimePercentile, hs.useRequestBodyBytesPercentile, hs.useResponseBodyBytesPercentile))
	}

	hs.stats[idx].Set(status, restime, resBodyBytes, reqBodyBytes)
}

func (hs *HTTPStats) Stats() []*HTTPStat {
	return hs.stats
}

func (hs *HTTPStats) CountUris() int {
	return hs.hints.len
}

func (hs *HTTPStats) SetOptions(options *options.Options) {
	hs.options = options
}

func (hs *HTTPStats) SetURIMatchingGroups(groups []string) error {
	uriGroups, err := helpers.CompileUriMatchingGroups(groups)
	if err != nil {
		return err
	}

	hs.uriMatchingGroups = uriGroups

	return nil
}

func (hs *HTTPStats) InitFilter(options *options.Options) error {
	hs.filter = NewFilter(options)
	return hs.filter.Init()
}

func (hs *HTTPStats) DoFilter(pstat *parsers.ParsedHTTPStat) (bool, error) {
	err := hs.filter.Do(pstat)
	if err == errors.SkipReadLineErr {
		return false, nil
	} else if err != nil {
		return false, err
	}

	return true, nil
}

func (hs *HTTPStats) CountAll() map[string]int {
	counts := make(map[string]int, 6)

	for _, s := range hs.stats {
		counts["count"] += s.Cnt
		counts["1xx"] += s.Status1xx
		counts["2xx"] += s.Status2xx
		counts["3xx"] += s.Status3xx
		counts["4xx"] += s.Status4xx
		counts["5xx"] += s.Status5xx
	}

	return counts
}

func (hs *HTTPStats) SortWithOptions() {
	hs.Sort(hs.options.Sort, hs.options.Reverse)
}

type HTTPStat struct {
	Uri               string        `yaml:"uri"`
	Cnt               int           `yaml:"count"`
	Status1xx         int           `yaml:"status1xx"`
	Status2xx         int           `yaml:"status2xx"`
	Status3xx         int           `yaml:"status3xx"`
	Status4xx         int           `yaml:"status4xx"`
	Status5xx         int           `yaml:"status5xx"`
	Method            string        `yaml:"method"`
	ResponseTime      *responseTime `yaml:"response_time"`
	RequestBodyBytes  *bodyBytes    `yaml:"request_body_bytes"`
	ResponseBodyBytes *bodyBytes    `yaml:"response_body_bytes"`
	Time              string
}

type httpStats []*HTTPStat

func newHTTPStat(uri, method string, useResTimePercentile, useRequestBodyBytesPercentile, useResponseBodyBytesPercentile bool) *HTTPStat {
	return &HTTPStat{
		Uri:               uri,
		Method:            method,
		ResponseTime:      newResponseTime(useResTimePercentile),
		RequestBodyBytes:  newBodyBytes(useRequestBodyBytesPercentile),
		ResponseBodyBytes: newBodyBytes(useResponseBodyBytesPercentile),
	}
}

func (hs *HTTPStat) Set(status int, restime, reqBodyBytes, resBodyBytes float64) {
	hs.Cnt++
	hs.setStatus(status)
	hs.ResponseTime.Set(restime)
	hs.RequestBodyBytes.Set(reqBodyBytes)
	hs.ResponseBodyBytes.Set(resBodyBytes)
}

func (hs *HTTPStat) setStatus(status int) {
	if status >= 100 && status <= 199 {
		hs.Status1xx++
	} else if status >= 200 && status <= 299 {
		hs.Status2xx++
	} else if status >= 300 && status <= 399 {
		hs.Status3xx++
	} else if status >= 400 && status <= 499 {
		hs.Status4xx++
	} else if status >= 500 && status <= 599 {
		hs.Status5xx++
	}
}

func (hs *HTTPStat) StrStatus1xx() string {
	return fmt.Sprint(hs.Status1xx)
}

func (hs *HTTPStat) StrStatus2xx() string {
	return fmt.Sprint(hs.Status2xx)
}

func (hs *HTTPStat) StrStatus3xx() string {
	return fmt.Sprint(hs.Status3xx)
}

func (hs *HTTPStat) StrStatus4xx() string {
	return fmt.Sprint(hs.Status4xx)
}

func (hs *HTTPStat) StrStatus5xx() string {
	return fmt.Sprint(hs.Status5xx)
}

func (hs *HTTPStat) Count() int {
	return hs.Cnt
}

func (hs *HTTPStat) StrCount() string {
	return fmt.Sprint(hs.Cnt)
}

func (hs *HTTPStat) MaxResponseTime() float64 {
	return hs.ResponseTime.Max
}

func (hs *HTTPStat) MinResponseTime() float64 {
	return hs.ResponseTime.Min
}

func (hs *HTTPStat) SumResponseTime() float64 {
	return hs.ResponseTime.Sum
}

func (hs *HTTPStat) AvgResponseTime() float64 {
	return hs.ResponseTime.Avg(hs.Cnt)
}

func (hs *HTTPStat) P1ResponseTime() float64 {
	return hs.ResponseTime.P1(hs.Cnt)
}

func (hs *HTTPStat) P50ResponseTime() float64 {
	return hs.ResponseTime.P50(hs.Cnt)
}

func (hs *HTTPStat) P90ResponseTime() float64 {
	return hs.ResponseTime.P90(hs.Cnt)
}

func (hs *HTTPStat) P99ResponseTime() float64 {
	return hs.ResponseTime.P99(hs.Cnt)
}

func (hs *HTTPStat) StddevResponseTime() float64 {
	return hs.ResponseTime.Stddev(hs.Cnt)
}

// request
func (hs *HTTPStat) MaxRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Max
}

func (hs *HTTPStat) MinRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Min
}

func (hs *HTTPStat) SumRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Sum
}

func (hs *HTTPStat) AvgRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Avg(hs.Cnt)
}

func (hs *HTTPStat) P1RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P1(hs.Cnt)
}

func (hs *HTTPStat) P50RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P50(hs.Cnt)
}

func (hs *HTTPStat) P90RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P90(hs.Cnt)
}

func (hs *HTTPStat) P99RequestBodyBytes() float64 {
	return hs.RequestBodyBytes.P99(hs.Cnt)
}

func (hs *HTTPStat) StddevRequestBodyBytes() float64 {
	return hs.RequestBodyBytes.Stddev(hs.Cnt)
}

// response
func (hs *HTTPStat) MaxResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Max
}

func (hs *HTTPStat) MinResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Min
}

func (hs *HTTPStat) SumResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Sum
}

func (hs *HTTPStat) AvgResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Avg(hs.Cnt)
}

func (hs *HTTPStat) P1ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P1(hs.Cnt)
}

func (hs *HTTPStat) P50ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P50(hs.Cnt)
}

func (hs *HTTPStat) P90ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P90(hs.Cnt)
}

func (hs *HTTPStat) P99ResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.P99(hs.Cnt)
}

func (hs *HTTPStat) StddevResponseBodyBytes() float64 {
	return hs.RequestBodyBytes.Stddev(hs.Cnt)
}

func percentRank(l int, n int) int {
	pLen := (l * n / 100) - 1
	if pLen < 0 {
		pLen = 0
	}

	return pLen
}

type responseTime struct {
	Max           float64 `yaml:"max"`
	Min           float64 `yaml:"min"`
	Sum           float64 `yaml:"sum"`
	UsePercentile bool
	Percentiles   []float64 `yaml:"percentiles"`
}

func newResponseTime(usePercentile bool) *responseTime {
	return &responseTime{
		UsePercentile: usePercentile,
		Percentiles:   make([]float64, 0),
	}
}

func (res *responseTime) Set(val float64) {
	if res.Max < val {
		res.Max = val
	}

	if res.Min >= val || res.Min == 0 {
		res.Min = val
	}

	res.Sum += val

	if res.UsePercentile {
		res.Percentiles = append(res.Percentiles, val)
	}
}

func (res *responseTime) Avg(cnt int) float64 {
	return res.Sum / float64(cnt)
}

func (res *responseTime) P1(cnt int) float64 {
	if !res.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 1)
	return res.Percentiles[plen]
}

func (res *responseTime) P50(cnt int) float64 {
	if !res.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 50)
	return res.Percentiles[plen]
}

func (res *responseTime) P90(cnt int) float64 {
	if !res.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 90)
	return res.Percentiles[plen]
}

func (res *responseTime) P99(cnt int) float64 {
	if !res.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 99)
	return res.Percentiles[plen]
}

func (res *responseTime) Stddev(cnt int) float64 {
	if !res.UsePercentile {
		return 0.0
	}

	var stdd float64
	avg := res.Avg(cnt)
	n := float64(cnt)

	for _, v := range res.Percentiles {
		stdd += (v - avg) * (v - avg)
	}

	return math.Sqrt(stdd / n)
}

type bodyBytes struct {
	Max           float64 `yaml:"max"`
	Min           float64 `yaml:"min"`
	Sum           float64 `yaml:"sum"`
	UsePercentile bool
	Percentiles   []float64 `yaml:"percentiles"`
}

func newBodyBytes(usePercentile bool) *bodyBytes {
	return &bodyBytes{
		UsePercentile: usePercentile,
		Percentiles:   make([]float64, 0),
	}
}

func (body *bodyBytes) Set(val float64) {
	if body.Max < val {
		body.Max = val
	}

	if body.Min >= val || body.Min == 0.0 {
		body.Min = val
	}

	body.Sum += val

	if body.UsePercentile {
		body.Percentiles = append(body.Percentiles, val)
	}
}

func (body *bodyBytes) Avg(cnt int) float64 {
	return body.Sum / float64(cnt)
}

func (body *bodyBytes) P1(cnt int) float64 {
	if !body.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 1)
	return body.Percentiles[plen]
}

func (body *bodyBytes) P50(cnt int) float64 {
	if !body.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 50)
	return body.Percentiles[plen]
}

func (body *bodyBytes) P90(cnt int) float64 {
	if !body.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 90)
	return body.Percentiles[plen]
}

func (body *bodyBytes) P99(cnt int) float64 {
	if !body.UsePercentile {
		return 0.0
	}

	plen := percentRank(cnt, 99)
	return body.Percentiles[plen]
}

func (body *bodyBytes) Stddev(cnt int) float64 {
	if !body.UsePercentile {
		return 0.0
	}

	var stdd float64
	avg := body.Avg(cnt)
	n := float64(cnt)

	for _, v := range body.Percentiles {
		stdd += (v - avg) * (v - avg)
	}

	return math.Sqrt(stdd / n)
}
<|endoftext|>


<commit_before>package precache

import (
	"log"
	"time"

	"github.com/brnstz/bus/internal/conf"
	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/bus/internal/models"
	"github.com/brnstz/bus/internal/partners"
)

var (

	// max workers per a single agency
	maxWorkersAgency = 10

	// delay is the time to wait between requests for the same route
	delay = time.Duration(60) * time.Second

	// error delay is the time we wait before hitting the API again
	// if we get an error
	errDelay = time.Duration(10) * time.Second

	// size of each precacheRequest's channel
	size = 50000
)

type precacheRequest struct {
	partner     partners.P
	agencyID    string
	routeID     string
	directionID int
	result      chan error
}

// routeWorker runs forever for this partner/agency/route/direction, sending
// a new precacheRequest to the channel ch, delaying between each request.
// The goal is to make a request from the partner before the TTL
// runs out.
func routeWorker(ch chan precacheRequest, p partners.P, agencyID string, routeID string, directionID int) {
	var err error

	// Assume last success was now
	lastSuccess := time.Now()

	// Convert RedisTTL to a duration
	ttlDur := time.Duration(conf.Cache.RedisTTL) * time.Second

	// Loop forever, constantly getting new updates
	for {

		// Create a request
		req := precacheRequest{
			partner:     p,
			agencyID:    agencyID,
			routeID:     routeID,
			directionID: directionID,
			result:      make(chan error, 1),
		}

		// Send it to an agencyWorker
		ch <- req

		// Wait for the response
		err = <-req.result

		// Record current time and difference
		now := time.Now()
		diff := now.Sub(lastSuccess)

		// If the time between successes is greater than cache duration,
		// we should warn that we have a problem
		if diff > ttlDur {
			log.Printf("%v %v %v worker took %v to run, longer than ttl of %v",
				agencyID, routeID, directionID, diff, ttlDur,
			)
		}

		if err != nil {
			log.Println("error getting response", err)
			time.Sleep(errDelay)
		} else {
			lastSuccess = now
			time.Sleep(delay)
		}
	}
}

// agencyWorker calls Precache on each incoming request
func agencyWorker(ch chan precacheRequest) {
	for req := range ch {
		req.result <- req.partner.Precache(req.agencyID, req.routeID, req.directionID)
	}
}

func Precache() {
	// Go through each agency we support
	for _, agencyID := range conf.Partner.AgencyIDs {

		// Create a channel for this agency
		ch := make(chan precacheRequest, size)

		// Create a number of workers for this agency
		for i := 0; i < maxWorkersAgency; i++ {
			go agencyWorker(ch)
		}

		// Get all the routes for this agency
		routes, err := models.GetAllRoutes(etc.DBConn, agencyID)
		if err != nil {
			log.Println("can't get routes", err)
			continue
		}

		for _, route := range routes {

			// Get the partner for each route
			p, err := partners.Find(*route)
			if err == partners.ErrNoPartner {
				// If there's no partner, just ignore it
				continue
			}
			if err != nil {
				// It's a fatal error the precacher if it can't
				// one of its configured partners
				log.Fatal("error getting partner", err)
			}

			for dir := 0; dir <= 1; dir++ {
				// Create a routeWorker for every combination of
				// route / direction. For some APIs, the response for each
				// direction is no different. It's up to the partner to ignore
				// one of the requests.

				// FIXME: problem with this is... we'll never
				// create new goroutines when they are updated in db
				go routeWorker(ch, p, route.AgencyID, route.RouteID, dir)
			}
		}
	}

	// FIXME: wait forever
	select {}
}
<commit_msg>reduce max workers<commit_after>package precache

import (
	"log"
	"time"

	"github.com/brnstz/bus/internal/conf"
	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/bus/internal/models"
	"github.com/brnstz/bus/internal/partners"
)

var (

	// max workers per a single agency
	maxWorkersAgency = 5

	// delay is the time to wait between requests for the same route
	delay = time.Duration(60) * time.Second

	// error delay is the time we wait before hitting the API again
	// if we get an error
	errDelay = time.Duration(10) * time.Second

	// size of each precacheRequest's channel
	size = 50000
)

type precacheRequest struct {
	partner     partners.P
	agencyID    string
	routeID     string
	directionID int
	result      chan error
}

// routeWorker runs forever for this partner/agency/route/direction, sending
// a new precacheRequest to the channel ch, delaying between each request.
// The goal is to make a request from the partner before the TTL
// runs out.
func routeWorker(ch chan precacheRequest, p partners.P, agencyID string, routeID string, directionID int) {
	var err error

	// Assume last success was now
	lastSuccess := time.Now()

	// Convert RedisTTL to a duration
	ttlDur := time.Duration(conf.Cache.RedisTTL) * time.Second

	// Loop forever, constantly getting new updates
	for {

		// Create a request
		req := precacheRequest{
			partner:     p,
			agencyID:    agencyID,
			routeID:     routeID,
			directionID: directionID,
			result:      make(chan error, 1),
		}

		// Send it to an agencyWorker
		ch <- req

		// Wait for the response
		err = <-req.result

		// Record current time and difference
		now := time.Now()
		diff := now.Sub(lastSuccess)

		// If the time between successes is greater than cache duration,
		// we should warn that we have a problem
		if diff > ttlDur {
			log.Printf("%v %v %v worker took %v to run, longer than ttl of %v",
				agencyID, routeID, directionID, diff, ttlDur,
			)
		}

		if err != nil {
			log.Println("error getting response", err)
			time.Sleep(errDelay)
		} else {
			lastSuccess = now
			time.Sleep(delay)
		}
	}
}

// agencyWorker calls Precache on each incoming request
func agencyWorker(ch chan precacheRequest) {
	for req := range ch {
		req.result <- req.partner.Precache(req.agencyID, req.routeID, req.directionID)
	}
}

func Precache() {
	// Go through each agency we support
	for _, agencyID := range conf.Partner.AgencyIDs {

		// Create a channel for this agency
		ch := make(chan precacheRequest, size)

		// Create a number of workers for this agency
		for i := 0; i < maxWorkersAgency; i++ {
			go agencyWorker(ch)
		}

		// Get all the routes for this agency
		routes, err := models.GetAllRoutes(etc.DBConn, agencyID)
		if err != nil {
			log.Println("can't get routes", err)
			continue
		}

		for _, route := range routes {

			// Get the partner for each route
			p, err := partners.Find(*route)
			if err == partners.ErrNoPartner {
				// If there's no partner, just ignore it
				continue
			}
			if err != nil {
				// It's a fatal error the precacher if it can't
				// one of its configured partners
				log.Fatal("error getting partner", err)
			}

			for dir := 0; dir <= 1; dir++ {
				// Create a routeWorker for every combination of
				// route / direction. For some APIs, the response for each
				// direction is no different. It's up to the partner to ignore
				// one of the requests.

				// FIXME: problem with this is... we'll never
				// create new goroutines when they are updated in db
				go routeWorker(ch, p, route.AgencyID, route.RouteID, dir)
			}
		}
	}

	// FIXME: wait forever
	select {}
}
<|endoftext|>


<commit_before>package base

import (
	"fmt"
	"net/http"
	"testing"

	th "github.com/rackspace/gophercloud/testhelper"
	fake "github.com/rackspace/gophercloud/testhelper/client"
)

// HandleGetSuccessfully creates an HTTP handler at `/` on the test handler mux
// that responds with a `Get` response.
func HandleGetSuccessfully(t *testing.T) {
	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		th.TestHeader(t, r, "Accept", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `
    {
        "resources": {
            "rel/cdn": {
                "href-template": "services{?marker,limit}",
                "href-vars": {
                    "marker": "param/marker",
                    "limit": "param/limit"
                },
                "hints": {
                    "allow": [
                        "GET"
                    ],
                    "formats": {
                        "application/json": {}
                    }
                }
            }
        }
    }
    `)

	})
}

// HandlePingSuccessfully creates an HTTP handler at `/ping` on the test handler
// mux that responds with a `Ping` response.
func HandlePingSuccessfully(t *testing.T) {
	th.Mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		th.TestHeader(t, r, "Accept", "application/json")
		w.WriteHeader(http.StatusNoContent)
	})
}
<commit_msg>fix ping unit tests to not check for accept header<commit_after>package base

import (
	"fmt"
	"net/http"
	"testing"

	th "github.com/rackspace/gophercloud/testhelper"
	fake "github.com/rackspace/gophercloud/testhelper/client"
)

// HandleGetSuccessfully creates an HTTP handler at `/` on the test handler mux
// that responds with a `Get` response.
func HandleGetSuccessfully(t *testing.T) {
	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		th.TestHeader(t, r, "Accept", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `
    {
        "resources": {
            "rel/cdn": {
                "href-template": "services{?marker,limit}",
                "href-vars": {
                    "marker": "param/marker",
                    "limit": "param/limit"
                },
                "hints": {
                    "allow": [
                        "GET"
                    ],
                    "formats": {
                        "application/json": {}
                    }
                }
            }
        }
    }
    `)

	})
}

// HandlePingSuccessfully creates an HTTP handler at `/ping` on the test handler
// mux that responds with a `Ping` response.
func HandlePingSuccessfully(t *testing.T) {
	th.Mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		w.WriteHeader(http.StatusNoContent)
	})
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccAWSCloudTrailServiceAccount_basic(t *testing.T) {
	expectedAccountID := cloudTrailServiceAccountPerRegionMap[testAccGetRegion()]

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsCloudTrailServiceAccountConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("data.aws_cloudtrail_service_account.main", "id", expectedAccountID),
					resource.TestCheckResourceAttr("data.aws_cloudtrail_service_account.main", "arn", fmt.Sprintf("arn:%s:iam::%s:root", testAccGetPartition(), expectedAccountID)),
				),
			},
		},
	})
}

func TestAccAWSCloudTrailServiceAccount_Region(t *testing.T) {
	expectedAccountID := cloudTrailServiceAccountPerRegionMap[testAccGetRegion()]

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsCloudTrailServiceAccountConfigRegion,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("data.aws_cloudtrail_service_account.regional", "id", expectedAccountID),
					resource.TestCheckResourceAttr("data.aws_cloudtrail_service_account.regional", "arn", fmt.Sprintf("arn:%s:iam::%s:root", testAccGetPartition(), expectedAccountID)),
				),
			},
		},
	})
}

const testAccCheckAwsCloudTrailServiceAccountConfig = `
data "aws_cloudtrail_service_account" "main" { }
`

const testAccCheckAwsCloudTrailServiceAccountConfigRegion = `
data "aws_region" "current" {}

data "aws_cloudtrail_service_account" "regional" {
  region = "${data.aws_region.current.name}"
}
`
<commit_msg>Uses ARN testing check functions for Cloudtrail service account data source<commit_after>package aws

import (
	"testing"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccAWSCloudTrailServiceAccount_basic(t *testing.T) {
	expectedAccountID := cloudTrailServiceAccountPerRegionMap[testAccGetRegion()]

	dataSourceName := "data.aws_cloudtrail_service_account.main"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsCloudTrailServiceAccountConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(dataSourceName, "id", expectedAccountID),
					testAccCheckResourceAttrGlobalARNAccountID(dataSourceName, "arn", expectedAccountID, "iam", "root"),
				),
			},
		},
	})
}

func TestAccAWSCloudTrailServiceAccount_Region(t *testing.T) {
	expectedAccountID := cloudTrailServiceAccountPerRegionMap[testAccGetRegion()]

	dataSourceName := "data.aws_cloudtrail_service_account.regional"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsCloudTrailServiceAccountConfigRegion,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(dataSourceName, "id", expectedAccountID),
					testAccCheckResourceAttrGlobalARNAccountID(dataSourceName, "arn", expectedAccountID, "iam", "root"),
				),
			},
		},
	})
}

const testAccCheckAwsCloudTrailServiceAccountConfig = `
data "aws_cloudtrail_service_account" "main" { }
`

const testAccCheckAwsCloudTrailServiceAccountConfigRegion = `
data "aws_region" "current" {}

data "aws_cloudtrail_service_account" "regional" {
  region = "${data.aws_region.current.name}"
}
`
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/service/s3control"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSS3BucketPublicAccessBlock_basic(t *testing.T) {
	var config s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"),
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config),
					resource.TestCheckResourceAttr(resourceName, "bucket", name),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "false"),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "false"),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "false"),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "false"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_disappears(t *testing.T) {
	var config s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config),
					testAccCheckAWSS3BucketPublicAccessBlockDisappears(resourceName),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_BlockPublicAcls(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "true", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "true", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "true"),
				),
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_BlockPublicPolicy(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "true", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "true", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "true"),
				),
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_IgnorePublicAcls(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "true", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "true", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "true"),
				),
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_RestrictPublicBuckets(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "true"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "true"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "true"),
				),
			},
		},
	})
}

func testAccCheckAWSS3BucketPublicAccessBlockExists(n string, config *s3.PublicAccessBlockConfiguration) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No S3 Bucket ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).s3conn

		input := &s3.GetPublicAccessBlockInput{
			Bucket: aws.String(rs.Primary.ID),
		}

		var output *s3.GetPublicAccessBlockOutput
		err := resource.Retry(1*time.Minute, func() *resource.RetryError {
			var err error
			output, err = conn.GetPublicAccessBlock(input)

			if isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
				return resource.RetryableError(err)
			}

			if err != nil {
				return resource.NonRetryableError(err)
			}

			return nil
		})

		if err != nil {
			return err
		}

		if output == nil || output.PublicAccessBlockConfiguration == nil {
			return fmt.Errorf("S3 Bucket Public Access Block not found")
		}

		*config = *output.PublicAccessBlockConfiguration

		return nil
	}
}

func testAccCheckAWSS3BucketPublicAccessBlockDisappears(n string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No S3 Bucket ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).s3conn

		deleteInput := &s3.DeletePublicAccessBlockInput{
			Bucket: aws.String(rs.Primary.ID),
		}

		if _, err := conn.DeletePublicAccessBlock(deleteInput); err != nil {
			return err
		}

		getInput := &s3.GetPublicAccessBlockInput{
			Bucket: aws.String(rs.Primary.ID),
		}

		return resource.Retry(1*time.Minute, func() *resource.RetryError {
			_, err := conn.GetPublicAccessBlock(getInput)

			if isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
				return nil
			}

			if err != nil {
				return resource.NonRetryableError(err)
			}

			return resource.RetryableError(fmt.Errorf("S3 Bucket Public Access Block (%s) still exists", rs.Primary.ID))
		})
	}
}

func testAccAWSS3BucketPublicAccessBlockConfig(bucketName, blockPublicAcls, blockPublicPolicy, ignorePublicAcls, restrictPublicBuckets string) string {
	return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" {
	bucket = "%s"
	tags {
		TestName = "TestACCAWSS3BucketPublicAccessBlock_basic"
	}
}

resource "aws_s3_bucket_public_access_block" "bucket" {
	bucket = "${aws_s3_bucket.bucket.bucket}"

	block_public_acls		= "%s"
	block_public_policy		= "%s"
	ignore_public_acls		= "%s"
	restrict_public_buckets = "%s"
}
`, bucketName, blockPublicAcls, blockPublicPolicy, ignorePublicAcls, restrictPublicBuckets)
}
<commit_msg>tests/resource/aws_s3_bucket_public_access_block: Ensure tags configurations use equals<commit_after>package aws

import (
	"fmt"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/service/s3control"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSS3BucketPublicAccessBlock_basic(t *testing.T) {
	var config s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"),
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config),
					resource.TestCheckResourceAttr(resourceName, "bucket", name),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "false"),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "false"),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "false"),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "false"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_disappears(t *testing.T) {
	var config s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config),
					testAccCheckAWSS3BucketPublicAccessBlockDisappears(resourceName),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_BlockPublicAcls(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "true", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "true", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "block_public_acls", "true"),
				),
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_BlockPublicPolicy(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "true", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "true", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "block_public_policy", "true"),
				),
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_IgnorePublicAcls(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "true", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "true", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "ignore_public_acls", "true"),
				),
			},
		},
	})
}

func TestAccAWSS3BucketPublicAccessBlock_RestrictPublicBuckets(t *testing.T) {
	var config1, config2, config3 s3.PublicAccessBlockConfiguration
	name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
	resourceName := "aws_s3_bucket_public_access_block.bucket"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSS3BucketDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "true"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config1),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "true"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "false"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config2),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "false"),
				),
			},
			{
				Config: testAccAWSS3BucketPublicAccessBlockConfig(name, "false", "false", "false", "true"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSS3BucketPublicAccessBlockExists(resourceName, &config3),
					resource.TestCheckResourceAttr(resourceName, "restrict_public_buckets", "true"),
				),
			},
		},
	})
}

func testAccCheckAWSS3BucketPublicAccessBlockExists(n string, config *s3.PublicAccessBlockConfiguration) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No S3 Bucket ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).s3conn

		input := &s3.GetPublicAccessBlockInput{
			Bucket: aws.String(rs.Primary.ID),
		}

		var output *s3.GetPublicAccessBlockOutput
		err := resource.Retry(1*time.Minute, func() *resource.RetryError {
			var err error
			output, err = conn.GetPublicAccessBlock(input)

			if isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
				return resource.RetryableError(err)
			}

			if err != nil {
				return resource.NonRetryableError(err)
			}

			return nil
		})

		if err != nil {
			return err
		}

		if output == nil || output.PublicAccessBlockConfiguration == nil {
			return fmt.Errorf("S3 Bucket Public Access Block not found")
		}

		*config = *output.PublicAccessBlockConfiguration

		return nil
	}
}

func testAccCheckAWSS3BucketPublicAccessBlockDisappears(n string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No S3 Bucket ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).s3conn

		deleteInput := &s3.DeletePublicAccessBlockInput{
			Bucket: aws.String(rs.Primary.ID),
		}

		if _, err := conn.DeletePublicAccessBlock(deleteInput); err != nil {
			return err
		}

		getInput := &s3.GetPublicAccessBlockInput{
			Bucket: aws.String(rs.Primary.ID),
		}

		return resource.Retry(1*time.Minute, func() *resource.RetryError {
			_, err := conn.GetPublicAccessBlock(getInput)

			if isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
				return nil
			}

			if err != nil {
				return resource.NonRetryableError(err)
			}

			return resource.RetryableError(fmt.Errorf("S3 Bucket Public Access Block (%s) still exists", rs.Primary.ID))
		})
	}
}

func testAccAWSS3BucketPublicAccessBlockConfig(bucketName, blockPublicAcls, blockPublicPolicy, ignorePublicAcls, restrictPublicBuckets string) string {
	return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" {
	bucket = "%s"
	tags = {
		TestName = "TestACCAWSS3BucketPublicAccessBlock_basic"
	}
}

resource "aws_s3_bucket_public_access_block" "bucket" {
	bucket = "${aws_s3_bucket.bucket.bucket}"

	block_public_acls		= "%s"
	block_public_policy		= "%s"
	ignore_public_acls		= "%s"
	restrict_public_buckets = "%s"
}
`, bucketName, blockPublicAcls, blockPublicPolicy, ignorePublicAcls, restrictPublicBuckets)
}
<|endoftext|>


<commit_before>package resolver

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
	"context"

	"github.com/morikuni/failure"

	"github.com/oinume/lekcije/backend/context_data"
	lerrors "github.com/oinume/lekcije/backend/errors"
	"github.com/oinume/lekcije/backend/interface/graphql/model"
)

// UpdateViewer is the resolver for the updateViewer field.
func (r *mutationResolver) UpdateViewer(ctx context.Context, input model.UpdateViewerInput) (*model.User, error) {
	user, err := context_data.GetLoggedInUser(ctx)
	if err != nil {
		return nil, err
	}
	if input.Email == nil {
		return toGraphQLUser(user), nil
	}

	email := *input.Email
	if !r.userUsecase.IsValidEmail(email) {
		return nil, failure.New(lerrors.InvalidArgument, failure.Messagef("invalid email format"))
	}
	duplicate, err := r.userUsecase.IsDuplicateEmail(ctx, email)
	if err != nil {
		return nil, err
	}
	if duplicate {
		return nil, failure.New(lerrors.InvalidArgument, failure.Messagef("email exists"))
	}

	if err := r.userUsecase.UpdateEmail(ctx, uint(user.ID), email); err != nil {
		return nil, err
	}

	user.Email = email
	return toGraphQLUser(user), nil
}

// Viewer is the resolver for the viewer field.
func (r *queryResolver) Viewer(ctx context.Context) (*model.User, error) {
	user, err := context_data.GetLoggedInUser(ctx)
	if err != nil {
		return nil, err
	}
	return toGraphQLUser(user), nil
}
<commit_msg>Update message to japanese one<commit_after>package resolver

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
	"context"

	"github.com/morikuni/failure"

	"github.com/oinume/lekcije/backend/context_data"
	lerrors "github.com/oinume/lekcije/backend/errors"
	"github.com/oinume/lekcije/backend/interface/graphql/model"
)

// UpdateViewer is the resolver for the updateViewer field.
func (r *mutationResolver) UpdateViewer(ctx context.Context, input model.UpdateViewerInput) (*model.User, error) {
	user, err := context_data.GetLoggedInUser(ctx)
	if err != nil {
		return nil, err
	}
	if input.Email == nil {
		return toGraphQLUser(user), nil
	}

	email := *input.Email
	if !r.userUsecase.IsValidEmail(email) {
		// TODO: i18n
		return nil, failure.New(lerrors.InvalidArgument, failure.Messagef("正しいメールアドレスを入力してください。"))
	}
	duplicate, err := r.userUsecase.IsDuplicateEmail(ctx, email)
	if err != nil {
		return nil, err
	}
	if duplicate {
		// TODO: i18n
		return nil, failure.New(lerrors.InvalidArgument, failure.Messagef("メールアドレスはすでに登録されています。"))
	}

	if err := r.userUsecase.UpdateEmail(ctx, uint(user.ID), email); err != nil {
		return nil, err
	}

	user.Email = email
	return toGraphQLUser(user), nil
}

// Viewer is the resolver for the viewer field.
func (r *queryResolver) Viewer(ctx context.Context) (*model.User, error) {
	user, err := context_data.GetLoggedInUser(ctx)
	if err != nil {
		return nil, err
	}
	return toGraphQLUser(user), nil
}
<|endoftext|>


<commit_before>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   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 mvc

import (
	"errors"
	"fmt"
	"reflect"
	"runtime"
	"strings"
	"time"

	"github.com/webx-top/echo"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
)

func NewModule(name string, domain string, s *Application, middlewares ...interface{}) (a *Module) {
	a = &Module{
		Application:                s,
		Name:               name,
		Domain:             domain,
		wrappers:           make(map[string]*Wrapper),
		cachedHandlerNames: make(map[string]string),
		Middlewares:        middlewares,
		Config:             &ModuleConfig{},
	}
	if s.Renderer != nil {
		a.Renderer = s.Renderer
	}
	if a.Domain == `` {
		var prefix string
		if name != s.RootModuleName {
			prefix = `/` + name
			a.Dir = prefix + `/`
		} else {
			a.Dir = `/`
		}
		a.URL = a.Dir
		if s.URL != `/` {
			a.URL = strings.TrimSuffix(s.URL, `/`) + a.URL
		}
		a.Group = s.Core.Group(prefix)
		a.Group.Use(middlewares...)
	} else {
		e := echo.NewWithContext(s.ContextCreator)
		e.Use(s.DefaultMiddlewares...)
		e.Use(middlewares...)
		a.Handler = e
		scheme := `http`
		if s.SessionOptions.Secure {
			scheme = `https`
		}
		a.URL = scheme + `://` + a.Domain + `/`
		a.Dir = `/`
	}
	if s.RootModuleName == name {
		a.Installed = int(time.Now().Unix())
	}
	return
}

type Module struct {
	*Application               `json:"-" xml:"-"`
	Group              *echo.Group   `json:"-" xml:"-"`
	Handler            *echo.Echo    `json:"-" xml:"-"` //指定域名时有效
	Middlewares        []interface{} `json:"-" xml:"-"`
	Renderer           driver.Driver `json:"-" xml:"-"`
	Name               string
	Domain             string
	wrappers           map[string]*Wrapper
	cachedHandlerNames map[string]string
	URL                string
	Dir                string

	// 模块附加信息
	Disabled    int    // 禁用时间戳，为0时为启用状态
	Installed   int    // 安装时间戳，为0时为未安装
	Expired     int    // 过期时间戳，为0时为永不过期
	Author      string // 作者名称
	Website     string // 作者网址
	Email       string // 作者邮箱
	Description string // 简介
	Config      ModuleConfiger

	// 安装和卸载逻辑
	Install   func() error `json:"-" xml:"-"`
	Uninstall func() error `json:"-" xml:"-"`
}

func (a *Module) Valid() error {
	if a.Installed == 0 {
		return ErrAppHasNotBeenInstalled
	}
	if a.Disabled > 0 {
		return ErrAppHasBeenDisabled
	}
	if a.Expired > 0 && int64(a.Expired) < time.Now().Unix() {
		return ErrAppHasExpired
	}
	return nil
}

// Register 注册路由：module.Register(`/index`,Index.Index,"GET","POST")
func (a *Module) Register(p string, v interface{}, methods ...string) *Module {
	if len(methods) < 1 {
		methods = append(methods, "GET")
	}
	a.Application.URLs.Set(v)
	h := a.Core.ValidHandler(v)
	a.Router().Match(methods, p, echo.HandlerFunc(func(ctx echo.Context) error {
		if c, y := ctx.(Initer); y {
			if err := c.Init(ctx); err != nil {
				return err
			}
		}
		return h.Handle(ctx)
	}))
	return a
}

func (a *Module) Router() echo.ICore {
	if a.Group != nil {
		return a.Group
	}
	return a.Handler
}

// C 获取控制器
func (a *Module) C(name string) (c interface{}) {
	if wp, ok := a.wrappers[name]; ok {
		c = wp.Controller
	}
	return
}

// Wrapper 获取封装器
func (a *Module) Wrapper(name string) (wp *Wrapper) {
	wp, _ = a.wrappers[name]
	return
}

// AddHandler 登记控制器
func (a *Module) AddHandler(c interface{}) *Wrapper {
	name := fmt.Sprintf("%T", c) //example: *controller.Index
	if len(name) > 0 && name[0] == '*' {
		name = name[1:]
	}
	wr := &Wrapper{
		Controller:    c,
		RouteRegister: a.Router(),
		Module:        a,
	}
	if _, ok := c.(Initer); ok {
		_, wr.hasBefore = c.(Before)
		_, wr.hasAfter = c.(After)
		_, wr.hasMain = c.(Main)
	} else {
		if hf, ok := c.(BeforeHandler); ok {
			wr.beforeHandler = hf.Before
		}
		if hf, ok := c.(AfterHandler); ok {
			wr.afterHandler = hf.After
		}
	}
	//controller.Index
	a.wrappers[name] = wr
	return wr
}

// Use 批量注册控制器路由
func (a *Module) Use(args ...interface{}) *Module {
	for _, c := range args {
		a.AddHandler(c).Auto()
	}
	return a
}

// InitRenderer 初始化渲染接口(用于单独对app指定renderer，如不指定，默认会使用Server中Renderer)
func (a *Module) InitRenderer(conf *render.Config, funcMap map[string]interface{}) *Module {
	a.Renderer = a.Application.NewRenderer(conf, a, funcMap)
	return a
}

// SafelyCall invokes `function` in recover block
func (a *Module) SafelyCall(fn reflect.Value, args []reflect.Value) (resp []reflect.Value, err error) {
	defer func() {
		if e := recover(); e != nil {
			resp = nil
			var content string
			content = fmt.Sprintf(`Handler crashed with error: %v`, e)
			for i := 1; ; i++ {
				_, file, line, ok := runtime.Caller(i)
				if !ok {
					break
				}
				content += "\n" + fmt.Sprintf(`%v %v`, file, line)
			}
			a.Application.Core.Logger().Error(content)
			err = errors.New(content)
		}
	}()
	if fn.Type().NumIn() > 0 {
		return fn.Call(args), err
	}
	return fn.Call(nil), err
}

func (a *Module) ClearCachedHandlerNames() {
	a.cachedHandlerNames = map[string]string{}
}

// ExecAction 执行Action的通用方式
func (a *Module) ExecAction(action string, t reflect.Type, v reflect.Value, c echo.Context) (err error) {
	k := t.PkgPath() + `.` + t.Name() + `.` + action + `_` + c.Method() + `_` + c.Format()
	var m reflect.Value
	if methodName, ok := a.cachedHandlerNames[k]; ok {
		m = v.MethodByName(methodName)
	} else {
		format := strings.ToUpper(c.Format())
		actions := []string{
			action + `_` + c.Method() + `_` + format,
			action + `_` + c.Method(),
			action + `_` + format,
			action,
		}
		var valid bool
		for _, act := range actions {
			methodName = act
			m = v.MethodByName(act)
			valid = m.IsValid()
			if valid {
				break
			}
		}
		if !valid {
			err = echo.NewHTTPError(404, `invalid action: `+t.Name()+`#`+action)
			return
		}
		a.cachedHandlerNames[k] = methodName
	}
	r, err := a.SafelyCall(m, []reflect.Value{})
	if err != nil {
		return err
	}
	size := len(r)
	switch size {
	case 1:
		rs := r[0].Interface()
		if err, ok := rs.(error); ok {
			return err
		}
	}
	return nil
}
<commit_msg>fixed bug<commit_after>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   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 mvc

import (
	"errors"
	"fmt"
	"reflect"
	"runtime"
	"strings"
	"time"

	"sync"

	"github.com/webx-top/echo"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
)

func NewModule(name string, domain string, s *Application, middlewares ...interface{}) (a *Module) {
	a = &Module{
		Application:        s,
		Name:               name,
		Domain:             domain,
		wrappers:           make(map[string]*Wrapper),
		cachedHandlerNames: make(map[string]string),
		Middlewares:        middlewares,
		Config:             &ModuleConfig{},
		lock:               &sync.RWMutex{},
	}
	if s.Renderer != nil {
		a.Renderer = s.Renderer
	}
	if a.Domain == `` {
		var prefix string
		if name != s.RootModuleName {
			prefix = `/` + name
			a.Dir = prefix + `/`
		} else {
			a.Dir = `/`
		}
		a.URL = a.Dir
		if s.URL != `/` {
			a.URL = strings.TrimSuffix(s.URL, `/`) + a.URL
		}
		a.Group = s.Core.Group(prefix)
		a.Group.Use(middlewares...)
	} else {
		e := echo.NewWithContext(s.ContextCreator)
		e.Use(s.DefaultMiddlewares...)
		e.Use(middlewares...)
		a.Handler = e
		scheme := `http`
		if s.SessionOptions.Secure {
			scheme = `https`
		}
		a.URL = scheme + `://` + a.Domain + `/`
		a.Dir = `/`
	}
	if s.RootModuleName == name {
		a.Installed = int(time.Now().Unix())
	}
	return
}

type Module struct {
	*Application       `json:"-" xml:"-"`
	Group              *echo.Group   `json:"-" xml:"-"`
	Handler            *echo.Echo    `json:"-" xml:"-"` //指定域名时有效
	Middlewares        []interface{} `json:"-" xml:"-"`
	Renderer           driver.Driver `json:"-" xml:"-"`
	Name               string
	Domain             string
	wrappers           map[string]*Wrapper
	cachedHandlerNames map[string]string
	URL                string
	Dir                string

	// 模块附加信息
	Disabled    int    // 禁用时间戳，为0时为启用状态
	Installed   int    // 安装时间戳，为0时为未安装
	Expired     int    // 过期时间戳，为0时为永不过期
	Author      string // 作者名称
	Website     string // 作者网址
	Email       string // 作者邮箱
	Description string // 简介
	Config      ModuleConfiger

	// 安装和卸载逻辑
	Install   func() error `json:"-" xml:"-"`
	Uninstall func() error `json:"-" xml:"-"`

	lock *sync.RWMutex
}

func (a *Module) Valid() error {
	if a.Installed == 0 {
		return ErrAppHasNotBeenInstalled
	}
	if a.Disabled > 0 {
		return ErrAppHasBeenDisabled
	}
	if a.Expired > 0 && int64(a.Expired) < time.Now().Unix() {
		return ErrAppHasExpired
	}
	return nil
}

// Register 注册路由：module.Register(`/index`,Index.Index,"GET","POST")
func (a *Module) Register(p string, v interface{}, methods ...string) *Module {
	if len(methods) < 1 {
		methods = append(methods, "GET")
	}
	a.Application.URLs.Set(v)
	h := a.Core.ValidHandler(v)
	a.Router().Match(methods, p, echo.HandlerFunc(func(ctx echo.Context) error {
		if c, y := ctx.(Initer); y {
			if err := c.Init(ctx); err != nil {
				return err
			}
		}
		return h.Handle(ctx)
	}))
	return a
}

func (a *Module) Router() echo.ICore {
	if a.Group != nil {
		return a.Group
	}
	return a.Handler
}

// C 获取控制器
func (a *Module) C(name string) (c interface{}) {
	if wp, ok := a.wrappers[name]; ok {
		c = wp.Controller
	}
	return
}

// Wrapper 获取封装器
func (a *Module) Wrapper(name string) (wp *Wrapper) {
	wp, _ = a.wrappers[name]
	return
}

// AddHandler 登记控制器
func (a *Module) AddHandler(c interface{}) *Wrapper {
	name := fmt.Sprintf("%T", c) //example: *controller.Index
	if len(name) > 0 && name[0] == '*' {
		name = name[1:]
	}
	wr := &Wrapper{
		Controller:    c,
		RouteRegister: a.Router(),
		Module:        a,
	}
	if _, ok := c.(Initer); ok {
		_, wr.hasBefore = c.(Before)
		_, wr.hasAfter = c.(After)
		_, wr.hasMain = c.(Main)
	} else {
		if hf, ok := c.(BeforeHandler); ok {
			wr.beforeHandler = hf.Before
		}
		if hf, ok := c.(AfterHandler); ok {
			wr.afterHandler = hf.After
		}
	}
	//controller.Index
	a.wrappers[name] = wr
	return wr
}

// Use 批量注册控制器路由
func (a *Module) Use(args ...interface{}) *Module {
	for _, c := range args {
		a.AddHandler(c).Auto()
	}
	return a
}

// InitRenderer 初始化渲染接口(用于单独对app指定renderer，如不指定，默认会使用Server中Renderer)
func (a *Module) InitRenderer(conf *render.Config, funcMap map[string]interface{}) *Module {
	a.Renderer = a.Application.NewRenderer(conf, a, funcMap)
	return a
}

// SafelyCall invokes `function` in recover block
func (a *Module) SafelyCall(fn reflect.Value, args []reflect.Value) (resp []reflect.Value, err error) {
	defer func() {
		if e := recover(); e != nil {
			resp = nil
			var content string
			content = fmt.Sprintf(`Handler crashed with error: %v`, e)
			for i := 1; ; i++ {
				_, file, line, ok := runtime.Caller(i)
				if !ok {
					break
				}
				content += "\n" + fmt.Sprintf(`%v %v`, file, line)
			}
			a.Application.Core.Logger().Error(content)
			err = errors.New(content)
		}
	}()
	if fn.Type().NumIn() > 0 {
		return fn.Call(args), err
	}
	return fn.Call(nil), err
}

func (a *Module) ClearCachedHandlerNames() {
	a.cachedHandlerNames = map[string]string{}
}

// ExecAction 执行Action的通用方式
func (a *Module) ExecAction(action string, t reflect.Type, v reflect.Value, c echo.Context) (err error) {
	a.lock.Lock()
	defer a.lock.Unlock()
	k := t.PkgPath() + `.` + t.Name() + `.` + action + `_` + c.Method() + `_` + c.Format()
	var m reflect.Value
	if methodName, ok := a.cachedHandlerNames[k]; ok {
		m = v.MethodByName(methodName)
	} else {
		format := strings.ToUpper(c.Format())
		actions := []string{
			action + `_` + c.Method() + `_` + format,
			action + `_` + c.Method(),
			action + `_` + format,
			action,
		}
		var valid bool
		for _, act := range actions {
			methodName = act
			m = v.MethodByName(act)
			valid = m.IsValid()
			if valid {
				break
			}
		}
		if !valid {
			err = echo.NewHTTPError(404, `invalid action: `+t.Name()+`#`+action)
			return
		}
		a.cachedHandlerNames[k] = methodName
	}
	r, err := a.SafelyCall(m, []reflect.Value{})
	if err != nil {
		return err
	}
	size := len(r)
	switch size {
	case 1:
		rs := r[0].Interface()
		if err, ok := rs.(error); ok {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/go-martini/martini"
	"github.com/gorilla/websocket"
	"github.com/martini-contrib/sessions"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sync"
	"time"
	"strconv"
)

const CLEAR string = "\\33[H\\33[2J"

const (
	WSTerm = 1
	WSClick = 2
)

type Config struct {
	Secret  string
	Address string
}

type LockingWebsockets struct {
	mutex sync.RWMutex
	byId  map[int64]*websocket.Conn
}

var websockets *LockingWebsockets

func (c *LockingWebsockets) deleteWebsocket(id int64) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	delete(c.byId, id)
}

func (c *LockingWebsockets) addWebsocket(id int64, ws *websocket.Conn) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.byId[id] = ws
}

func main() {
	websockets = &LockingWebsockets{
		byId: make(map[int64]*websocket.Conn),
	}
	consoleReadChannel = make(chan ConsoleChunk)
	go consoleDispatch()
	vzcontrol := ConnectVZControl()
	defer vzcontrol.Close()

	file, _ := os.Open("game_server.cfg")
	decoder := json.NewDecoder(file)
	config := Config{}
	decoder.Decode(&config)

	m := martini.Classic()
	store := sessions.NewCookieStore([]byte(config.Secret))
	m.Use(sessions.Sessions("session", store))

	generating := false
	gr := NewGraph()
	m.Get("/reset/:secret", func(w http.ResponseWriter, r *http.Request, params martini.Params, session sessions.Session) string {
		if params["secret"] != config.Secret {
			return ""
		}
		err := vzcontrol.Reset()
		if err != nil {
			return err.Error()
		}
		generating = false
		gr = NewGraph()
		return "Done"
	})

	m.Get("/gen", func(w http.ResponseWriter, r *http.Request, session sessions.Session) string {
		if generating {
			return "Already generating"
		}
		generating = true
		maxNodes := 5
		maxEdges := 5
		startNodeId := 100

		startNode := Node{Id: NodeId(startNodeId)}
		gr.AddNode(startNode)
		err := vzcontrol.ContainerCreate(int64(startNode.Id))

		nodes := make([]Node, 0)
		nodes = append(nodes, startNode)

		steps := 1
		for len(nodes) != 0 && steps < maxNodes {
			node, nodes := nodes[len(nodes)-1], nodes[:len(nodes)-1]

			numEdges := random(1, maxEdges)
			for i := 1; i <= numEdges; i++ {
				targetNode := Node{Id: NodeId(i*steps + startNodeId)}
				if gr.AddNode(targetNode) {
					err = vzcontrol.ContainerCreate(int64(targetNode.Id))
					if err != nil {
						return fmt.Sprintf("Container Create: %d, %d, %d\n%s", targetNode.Id, i*steps, numEdges, err.Error())
					}
					nodes = append(nodes, targetNode)
					edgeid := int64(i * steps)
					if gr.AddEdge(Edge{Id: EdgeId(edgeid), Head: node.Id, Tail: targetNode.Id}) {
						err = vzcontrol.NetworkCreate(edgeid)
						if err != nil {
							return fmt.Sprintf("Network Create: %d\n%s", edgeid, err.Error())
						}
						err = vzcontrol.NetworkAdd(int64(node.Id), edgeid)
						if err != nil {
							return fmt.Sprintf("Network Add Node: %d, %d\n%s", node.Id, edgeid, err.Error())
						}
						err = vzcontrol.NetworkAdd(int64(targetNode.Id), edgeid)
						if err != nil {
							return fmt.Sprintf("Network Add Target: %d, %d\n%s", targetNode.Id, edgeid, err.Error())
						}
					}
				}

			}
			steps += 1
		}
		return gr.String()
	})

	m.Get("/graph", func(w http.ResponseWriter, r *http.Request, session sessions.Session) string {
		output, err := json.Marshal(gr)
		if err != nil {
			return err.Error()
		}
		return string(output)
	})

	m.Get("/ws", func(w http.ResponseWriter, r *http.Request, session sessions.Session) {
		currentVm := 0
		ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if _, ok := err.(websocket.HandshakeError); ok {
			http.Error(w, "Not a websocket handshake", 400)
			log.Println(err)
			return
		} else if err != nil {
			log.Println(err)
			return
		}
		defer ws.Close()
		ws.WriteMessage(websocket.TextMessage, []byte("Welcome to ginux!\r\n"))
		for {
			_, message, err := ws.ReadMessage()
			if err != nil {
				log.Println(err)
				return
			} else {
				msgType := message[0]
				msgData := message[1:len(message)]
				switch msgType {
				case WSTerm:
					if currentVm != 0 {
						vzcontrol.ConsoleWrite(int64(currentVm), msgData)
					}
				case WSClick:
					currentVm, _ := strconv.Atoi(string(msgData))
					log.Println(message, currentVm, msgData, string(msgData))
				}
			}
		}
	})
	log.Println("Game Server started on", config.Address)
	log.Fatal(http.ListenAndServe(config.Address, m))
}

func random(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

func consoleDispatch() {
	for chunk := range consoleReadChannel {
		websockets.mutex.RLock()
		if socket, ok := websockets.byId[chunk.Id]; ok {
			socket.WriteMessage(websocket.TextMessage, chunk.Data)
		}
		websockets.mutex.RUnlock()
	}
}
<commit_msg>Added console writing<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/go-martini/martini"
	"github.com/gorilla/websocket"
	"github.com/martini-contrib/sessions"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sync"
	"time"
	"strconv"
)

const CLEAR string = "\\33[H\\33[2J"

const (
	WSTerm = 1
	WSClick = 2
)

type Config struct {
	Secret  string
	Address string
}

type LockingWebsockets struct {
	sync.RWMutex
	byId  map[int64]*websocket.Conn
	consoleToId  map[int64][]int64
	currentId int64
}

var websockets *LockingWebsockets

func (c *LockingWebsockets) deleteWebsocket(id int64) {
	c.Lock()
	defer c.Unlock()
	delete(c.byId, id)
}

func (c *LockingWebsockets) addWebsocket(ws *websocket.Conn) int64 {
	c.Lock()
	defer c.Unlock()
	c.currentId += 1
	retid := c.currentId
	c.byId[retid] = ws
	return retid
}

func main() {
	websockets = &LockingWebsockets{
		byId: make(map[int64]*websocket.Conn),
		consoleToId: make(map[int64][]int64),
		currentId: 0,
	}
	consoleReadChannel = make(chan ConsoleChunk)
	go consoleDispatch()
	vzcontrol := ConnectVZControl()
	defer vzcontrol.Close()

	file, _ := os.Open("game_server.cfg")
	decoder := json.NewDecoder(file)
	config := Config{}
	decoder.Decode(&config)

	m := martini.Classic()
	store := sessions.NewCookieStore([]byte(config.Secret))
	m.Use(sessions.Sessions("session", store))

	generating := false
	gr := NewGraph()
	m.Get("/reset/:secret", func(w http.ResponseWriter, r *http.Request, params martini.Params, session sessions.Session) string {
		if params["secret"] != config.Secret {
			return ""
		}
		err := vzcontrol.Reset()
		if err != nil {
			return err.Error()
		}
		generating = false
		gr = NewGraph()
		return "Done"
	})

	m.Get("/gen", func(w http.ResponseWriter, r *http.Request, session sessions.Session) string {
		if generating {
			return "Already generating"
		}
		generating = true
		maxNodes := 5
		maxEdges := 5
		startNodeId := 100

		startNode := Node{Id: NodeId(startNodeId)}
		gr.AddNode(startNode)
		err := vzcontrol.ContainerCreate(int64(startNode.Id))

		nodes := make([]Node, 0)
		nodes = append(nodes, startNode)

		steps := 1
		for len(nodes) != 0 && steps < maxNodes {
			node, nodes := nodes[len(nodes)-1], nodes[:len(nodes)-1]

			numEdges := random(1, maxEdges)
			for i := 1; i <= numEdges; i++ {
				targetNode := Node{Id: NodeId(i*steps + startNodeId)}
				if gr.AddNode(targetNode) {
					err = vzcontrol.ContainerCreate(int64(targetNode.Id))
					if err != nil {
						return fmt.Sprintf("Container Create: %d, %d, %d\n%s", targetNode.Id, i*steps, numEdges, err.Error())
					}
					nodes = append(nodes, targetNode)
					edgeid := int64(i * steps)
					if gr.AddEdge(Edge{Id: EdgeId(edgeid), Head: node.Id, Tail: targetNode.Id}) {
						err = vzcontrol.NetworkCreate(edgeid)
						if err != nil {
							return fmt.Sprintf("Network Create: %d\n%s", edgeid, err.Error())
						}
						err = vzcontrol.NetworkAdd(int64(node.Id), edgeid)
						if err != nil {
							return fmt.Sprintf("Network Add Node: %d, %d\n%s", node.Id, edgeid, err.Error())
						}
						err = vzcontrol.NetworkAdd(int64(targetNode.Id), edgeid)
						if err != nil {
							return fmt.Sprintf("Network Add Target: %d, %d\n%s", targetNode.Id, edgeid, err.Error())
						}
					}
				}

			}
			steps += 1
		}
		return gr.String()
	})

	m.Get("/graph", func(w http.ResponseWriter, r *http.Request, session sessions.Session) string {
		output, err := json.Marshal(gr)
		if err != nil {
			return err.Error()
		}
		return string(output)
	})

	m.Get("/ws", func(w http.ResponseWriter, r *http.Request, session sessions.Session) {
		var currentVm int64 = -1
		ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if _, ok := err.(websocket.HandshakeError); ok {
			http.Error(w, "Not a websocket handshake", 400)
			log.Println(err)
			return
		} else if err != nil {
			log.Println(err)
			return
		}
		defer ws.Close()
		websocketId := websockets.addWebsocket(ws)
 		defer websockets.deleteWebsocket(websocketId)
		ws.WriteMessage(websocket.TextMessage, []byte("Welcome to ginux!\r\n"))
		for {
			_, message, err := ws.ReadMessage()
			if err != nil {
				log.Println(err)
				return
			} else {
				msgType := message[0]
				msgData := message[1:len(message)]
				switch msgType {
				case WSTerm:
					if currentVm != -1 {
						vzcontrol.ConsoleWrite(currentVm, msgData)
					}
				case WSClick:
					prevVm := currentVm
					tmp, _ := strconv.Atoi(string(msgData))
					currentVm = int64(tmp)
					websockets.Lock()
					if prevVm != -1 {
						for index, wsId := range websockets.consoleToId[prevVm] {
							if wsId == websocketId{
								websockets.consoleToId[prevVm] = append(websockets.consoleToId[prevVm][:index], websockets.consoleToId[prevVm][index+1:]...)
							}
						}
					}
					websockets.consoleToId[currentVm] = append(websockets.consoleToId[currentVm], websocketId)
					websockets.Unlock()
				}
			}
		}
	})
	log.Println("Game Server started on", config.Address)
	log.Fatal(http.ListenAndServe(config.Address, m))
}

func random(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

func consoleDispatch() {
	for chunk := range consoleReadChannel {
		websockets.RLock()
		for _, wsId := range websockets.consoleToId[chunk.Id]{
			if socket, ok := websockets.byId[wsId]; ok {
				socket.WriteMessage(websocket.TextMessage, chunk.Data)
			}
		}
		websockets.RUnlock()
	}
}
<|endoftext|>


<commit_before>package topicfeed

import (
	"encoding/json"
	"fmt"
	"socialapi/models"

	"github.com/koding/bongo"
	"github.com/koding/logging"
	"github.com/kylemcc/twitter-text-go/extract"
	"github.com/streadway/amqp"

	verbalexpressions "github.com/VerbalExpressions/GoVerbalExpressions"
)

// s := "naber #foo hede #bar dede gel # baz #123 #-`3sdf"
// will find [foo, bar, 123]
// will not find [' baz', '-`3sdf']
// here is the regex -> (?m)(?:#)(\w+)
var topicRegex = verbalexpressions.New().
	Find("#").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

// extend this regex with https://github.com/twitter/twitter-text-rb/blob/eacf388136891eb316f1c110da8898efb8b54a38/lib/twitter-text/regex.rb
// to support all languages

type Controller struct {
	log logging.Logger
}

func New(log logging.Logger) *Controller {
	return &Controller{
		log: log,
	}
}

func (t *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	if delivery.Redelivered {
		t.log.Error("Redelivered message gave error again, putting to maintenance queue", err)
		delivery.Ack(false)
		return true
	}

	t.log.Error("an error occured putting message back to queue", err)
	delivery.Nack(false, true)
	return false
}

func (f *Controller) MessageSaved(data *models.ChannelMessage) error {
	if res, _ := isEligible(data); !res {
		return nil
	}

	topics := extractTopics(data.Body)
	if len(topics) == 0 {
		return nil
	}

	c, err := models.ChannelById(data.InitialChannelId)
	if err != nil {
		f.log.Error("Error on models.ChannelById", data.InitialChannelId, err)
		return err
	}

	return ensureChannelMessages(c, data, topics)
}

func ensureChannelMessages(parentChannel *models.Channel, data *models.ChannelMessage, topics []string) error {
	for _, topic := range topics {
		tc, err := fetchTopicChannel(parentChannel.GroupName, topic)
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		if err == bongo.RecordNotFound {
			tc, err = createTopicChannel(data.AccountId, parentChannel.GroupName, topic, parentChannel.PrivacyConstant)
			if err != nil {
				return err
			}
		}

		_, err = tc.EnsureMessage(data.Id, true)
		// safely skip
		if err == models.ErrMessageAlreadyInTheChannel {
			continue
		}

		if err != nil {
			return err
		}
	}

	return nil
}

func extractTopics(body string) []string {
	flattened := make([]string, 0)

	res := extract.ExtractHashtags(body)
	if res == nil {
		return flattened
	}

	topics := map[string]struct{}{}
	// remove duplicate tag usages
	for _, e := range res {
		if hashTag, ok := e.Hashtag(); ok {
			topics[hashTag] = struct{}{}
		}
	}

	for topic := range topics {
		flattened = append(flattened, topic)
	}

	return flattened
}

func (f *Controller) MessageUpdated(data *models.ChannelMessage) error {
	if res, _ := isEligible(data); !res {
		return nil
	}

	f.log.Debug("udpate message %s", data.Id)
	// fetch message's current topics from the db
	channels, err := fetchMessageChannels(data.Id)
	if err != nil {
		return err
	}

	// get current topics from
	topics := extractTopics(data.Body)
	if topics == nil {
		return nil
	}

	// if message and the topics dont have any item, we can safely return
	if len(channels) == 0 && len(topics) == 0 {
		return nil
	}

	excludedChannelId := data.InitialChannelId

	res := getTopicDiff(channels, topics, excludedChannelId)

	// add messages
	if len(res["added"]) > 0 {
		initialChannel, err := models.ChannelById(data.InitialChannelId)
		if err != nil {
			return err
		}

		if err := ensureChannelMessages(initialChannel, data, res["added"]); err != nil {
			return err
		}
	}

	// delete messages
	if len(res["deleted"]) > 0 {
		if err := deleteChannelMessages(channels, data, res["deleted"]); err != nil {
			return err
		}
	}

	return nil
}

func deleteChannelMessages(channels []models.Channel, data *models.ChannelMessage, toBeDeletedTopics []string) error {
	for _, channel := range channels {
		for _, topic := range toBeDeletedTopics {
			if channel.Name != topic {
				continue
			}

			cml := models.NewChannelMessageList()
			selector := map[string]interface{}{
				"message_id": data.Id,
				"channel_id": channel.Id,
			}

			if err := cml.DeleteMessagesBySelector(selector); err != nil {
				return err
			}
		}
	}
	return nil
}

func fetchMessageChannels(messageId int64) ([]models.Channel, error) {
	cml := models.NewChannelMessageList()
	return cml.FetchMessageChannels(messageId)
}

func getTopicDiff(channels []models.Channel, topics []string, excludedChannelId int64) map[string][]string {
	res := make(map[string][]string)

	// aggregate all channel names into map
	channelNames := map[string]struct{}{}
	for _, channel := range channels {
		if excludedChannelId != channel.GetId() {
			channelNames[channel.Name] = struct{}{}
		}
	}

	// range over new topics, bacause we are gonna remove
	// unused channels
	for _, topic := range topics {
		found := false
		for channelName := range channelNames {
			if channelName == topic {
				found = true
			}
		}
		if !found {
			res["added"] = append(res["added"], topic)
		} else {
			// if we have topic in channels
			// do remove it because at the end we are gonna mark
			// channels as deleted which are still in channelNames
			delete(channelNames, topic)
		}
	}
	// flatten the deleted channel names
	for channelName := range channelNames {
		res["deleted"] = append(res["deleted"], channelName)
	}

	return res
}

func (f *Controller) MessageDeleted(data *models.ChannelMessage) error {
	if res, _ := isEligible(data); !res {
		return nil
	}

	cml := models.NewChannelMessageList()
	selector := map[string]interface{}{
		"message_id": data.Id,
	}

	if err := cml.DeleteMessagesBySelector(selector); err != nil {
		return err
	}
	return nil
}

func mapMessage(data []byte) (*models.ChannelMessage, error) {
	cm := models.NewChannelMessage()
	if err := json.Unmarshal(data, cm); err != nil {
		return nil, err
	}

	return cm, nil
}

func isEligible(cm *models.ChannelMessage) (bool, error) {
	if cm.InitialChannelId == 0 {
		return false, nil
	}

	if cm.TypeConstant != models.ChannelMessage_TYPE_POST {
		return false, nil
	}

	return true, nil
}

// todo add caching here
func fetchTopicChannel(groupName, channelName string) (*models.Channel, error) {
	c := models.NewChannel()

	selector := map[string]interface{}{
		"group_name":    groupName,
		"name":          channelName,
		"type_constant": models.Channel_TYPE_TOPIC,
	}

	err := c.One(bongo.NewQS(selector))
	if err != nil {
		return nil, err
	}

	return c, nil
}

func createTopicChannel(creatorId int64, groupName, channelName, privacy string) (*models.Channel, error) {
	c := models.NewChannel()
	c.Name = channelName
	c.CreatorId = creatorId
	c.GroupName = groupName
	c.Purpose = fmt.Sprintf("Channel for %s topic", channelName)
	c.TypeConstant = models.Channel_TYPE_TOPIC
	c.PrivacyConstant = privacy
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}
<commit_msg>Social: do not add blacklisted topics into channel list<commit_after>package topicfeed

import (
	"encoding/json"
	"fmt"
	"socialapi/models"

	"github.com/koding/bongo"
	"github.com/koding/logging"
	"github.com/kylemcc/twitter-text-go/extract"
	"github.com/streadway/amqp"

	verbalexpressions "github.com/VerbalExpressions/GoVerbalExpressions"
)

// s := "naber #foo hede #bar dede gel # baz #123 #-`3sdf"
// will find [foo, bar, 123]
// will not find [' baz', '-`3sdf']
// here is the regex -> (?m)(?:#)(\w+)
var topicRegex = verbalexpressions.New().
	Find("#").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

// extend this regex with https://github.com/twitter/twitter-text-rb/blob/eacf388136891eb316f1c110da8898efb8b54a38/lib/twitter-text/regex.rb
// to support all languages

type Controller struct {
	log logging.Logger
}

func New(log logging.Logger) *Controller {
	return &Controller{
		log: log,
	}
}

func (t *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	if delivery.Redelivered {
		t.log.Error("Redelivered message gave error again, putting to maintenance queue", err)
		delivery.Ack(false)
		return true
	}

	t.log.Error("an error occured putting message back to queue", err)
	delivery.Nack(false, true)
	return false
}

func (f *Controller) MessageSaved(data *models.ChannelMessage) error {
	if res, _ := isEligible(data); !res {
		return nil
	}

	topics := extractTopics(data.Body)
	if len(topics) == 0 {
		return nil
	}

	c, err := models.ChannelById(data.InitialChannelId)
	if err != nil {
		f.log.Error("Error on models.ChannelById", data.InitialChannelId, err)
		return err
	}

	return ensureChannelMessages(c, data, topics)
}

func ensureChannelMessages(parentChannel *models.Channel, data *models.ChannelMessage, topics []string) error {
	for _, topic := range topics {
		tc, err := fetchTopicChannel(parentChannel.GroupName, topic)
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		if err == bongo.RecordNotFound {
			tc, err = createTopicChannel(data.AccountId, parentChannel.GroupName, topic, parentChannel.PrivacyConstant)
			if err != nil {
				return err
			}
		}

		_, err = tc.EnsureMessage(data.Id, true)
		// safely skip
		if err == models.ErrMessageAlreadyInTheChannel {
			continue
		}

		if err != nil {
			return err
		}
	}

	return nil
}

func extractTopics(body string) []string {
	flattened := make([]string, 0)

	// extract twitter style hashtags
	res := extract.ExtractHashtags(body)
	if res == nil {
		return flattened
	}

	topics := map[string]struct{}{}
	// remove duplicate tag usages
	for _, e := range res {
		if hashTag, ok := e.Hashtag(); ok {
			topics[hashTag] = struct{}{}
		}
	}

	// filter unwanted topics
	topics = filterTopics(topics)

	for topic := range topics {
		flattened = append(flattened, topic)
	}

	return flattened
}

func filterTopics(topics map[string]struct{}) map[string]struct{} {
	blacklistedTopics := []string{
		// public topic is used for group channel, if user adds `public` tag
		// into the message, do not try to add it to the group channel again
		"public",
	}

	filteredTopics := make(map[string]struct{})

	for topic, _ := range topics {
		blacklisted := false
		// check if the topic is in blacklisted topics
		for _, blacklistedTopic := range blacklistedTopics {
			if topic == blacklistedTopic {
				blacklisted = true
				break
			}
		}

		// merge -not blacklisted- topics
		if !blacklisted {
			filteredTopics[topic] = struct{}{}
		}
	}

	return filteredTopics

}
func (f *Controller) MessageUpdated(data *models.ChannelMessage) error {
	if res, _ := isEligible(data); !res {
		return nil
	}

	f.log.Debug("udpate message %s", data.Id)
	// fetch message's current topics from the db
	channels, err := fetchMessageChannels(data.Id)
	if err != nil {
		return err
	}

	// get current topics from
	topics := extractTopics(data.Body)
	if topics == nil {
		return nil
	}

	// if message and the topics dont have any item, we can safely return
	if len(channels) == 0 && len(topics) == 0 {
		return nil
	}

	excludedChannelId := data.InitialChannelId

	res := getTopicDiff(channels, topics, excludedChannelId)

	// add messages
	if len(res["added"]) > 0 {
		initialChannel, err := models.ChannelById(data.InitialChannelId)
		if err != nil {
			return err
		}

		if err := ensureChannelMessages(initialChannel, data, res["added"]); err != nil {
			return err
		}
	}

	// delete messages
	if len(res["deleted"]) > 0 {
		if err := deleteChannelMessages(channels, data, res["deleted"]); err != nil {
			return err
		}
	}

	return nil
}

func deleteChannelMessages(channels []models.Channel, data *models.ChannelMessage, toBeDeletedTopics []string) error {
	for _, channel := range channels {
		for _, topic := range toBeDeletedTopics {
			if channel.Name != topic {
				continue
			}

			cml := models.NewChannelMessageList()
			selector := map[string]interface{}{
				"message_id": data.Id,
				"channel_id": channel.Id,
			}

			if err := cml.DeleteMessagesBySelector(selector); err != nil {
				return err
			}
		}
	}
	return nil
}

func fetchMessageChannels(messageId int64) ([]models.Channel, error) {
	cml := models.NewChannelMessageList()
	return cml.FetchMessageChannels(messageId)
}

func getTopicDiff(channels []models.Channel, topics []string, excludedChannelId int64) map[string][]string {
	res := make(map[string][]string)

	// aggregate all channel names into map
	channelNames := map[string]struct{}{}
	for _, channel := range channels {
		if excludedChannelId != channel.GetId() {
			channelNames[channel.Name] = struct{}{}
		}
	}

	// range over new topics, bacause we are gonna remove
	// unused channels
	for _, topic := range topics {
		found := false
		for channelName := range channelNames {
			if channelName == topic {
				found = true
			}
		}
		if !found {
			res["added"] = append(res["added"], topic)
		} else {
			// if we have topic in channels
			// do remove it because at the end we are gonna mark
			// channels as deleted which are still in channelNames
			delete(channelNames, topic)
		}
	}
	// flatten the deleted channel names
	for channelName := range channelNames {
		res["deleted"] = append(res["deleted"], channelName)
	}

	return res
}

func (f *Controller) MessageDeleted(data *models.ChannelMessage) error {
	if res, _ := isEligible(data); !res {
		return nil
	}

	cml := models.NewChannelMessageList()
	selector := map[string]interface{}{
		"message_id": data.Id,
	}

	if err := cml.DeleteMessagesBySelector(selector); err != nil {
		return err
	}
	return nil
}

func mapMessage(data []byte) (*models.ChannelMessage, error) {
	cm := models.NewChannelMessage()
	if err := json.Unmarshal(data, cm); err != nil {
		return nil, err
	}

	return cm, nil
}

func isEligible(cm *models.ChannelMessage) (bool, error) {
	if cm.InitialChannelId == 0 {
		return false, nil
	}

	if cm.TypeConstant != models.ChannelMessage_TYPE_POST {
		return false, nil
	}

	return true, nil
}

// todo add caching here
func fetchTopicChannel(groupName, channelName string) (*models.Channel, error) {
	c := models.NewChannel()

	selector := map[string]interface{}{
		"group_name":    groupName,
		"name":          channelName,
		"type_constant": models.Channel_TYPE_TOPIC,
	}

	err := c.One(bongo.NewQS(selector))
	if err != nil {
		return nil, err
	}

	return c, nil
}

func createTopicChannel(creatorId int64, groupName, channelName, privacy string) (*models.Channel, error) {
	c := models.NewChannel()
	c.Name = channelName
	c.CreatorId = creatorId
	c.GroupName = groupName
	c.Purpose = fmt.Sprintf("Channel for %s topic", channelName)
	c.TypeConstant = models.Channel_TYPE_TOPIC
	c.PrivacyConstant = privacy
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}
<|endoftext|>