<commit_before>/*
fperf allows you to build your performace tools easily

Three steps to create your own testcase

1. Create the "NewClient" function

	package demo

	import (
		"fmt"
		"github.com/shafreeck/fperf/client"
		"time"
	)

	type DemoClient struct{}

	func NewDemoClient(flag *client.FlagSet) client.Client {
		return &DemoClient{}
	}

2. Implement the UnaryClient or StreamClient

	func (c *DemoClient) Dial(addr string) error {
		fmt.Println("Dial to", addr)
		return nil
	}

	func (c *DemoClient) Request() error {
		time.Sleep(100 * time.Millisecond)
		return nil
	}

3. Register to fperf

	func init() {
		client.Register("demo", NewDemoClient, "This is a demo client discription")
	}

Run the buildin testcase

http_testing is a simple builtin testcase to benchmark http servers

	fperf -cpu 8 -connection 10 http_testing -url http://example.com
*/
package main

import (
	"flag"
	"fmt"
	db "github.com/influxdb/influxdb/client/v2"
	"github.com/shafreeck/fperf/client"
	hist "github.com/shafreeck/fperf/stats"
	"golang.org/x/net/context"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"
)

type setting struct {
	Connection int
	Stream     int
	Goroutine  int
	CPU        int
	Burst      int
	N          int //number of requests
	Tick       time.Duration
	Address    string
	Send       bool
	Recv       bool
	Delay      time.Duration
	Async      bool
	Target     string
	CallType   string
	InfluxDB   string
}

type statistics struct {
	latencies []time.Duration
	histogram *hist.Histogram
}

type roundtrip struct {
	start time.Time
	end   time.Time
	ack   bool
}

func createClients(n int, addr string) []client.Client {
	clients := make([]client.Client, n)
	for i := 0; i < n; i++ {
		cli := client.NewClient(s.Target)
		if cli == nil {
			log.Fatalf("Can not find client %q for benchmark\n", s.Target)
		}

		cli.Dial(addr)
		clients[i] = cli
	}
	return clients
}
func createStreams(n int, clients []client.Client) []client.Stream {
	streams := make([]client.Stream, n*len(clients))
	for cur, cli := range clients {
		for i := 0; i < n; i++ {
			if cli, ok := cli.(client.StreamClient); ok {
				stream, err := cli.CreateStream(context.Background())
				if err != nil {
					log.Fatalf("StreamCall faile to create new stream, %v", err)
				}
				streams[cur*n+i] = stream
			} else {
				log.Fatalln(s.Target, " do not implement the client.StreamClient")
			}
		}
	}
	return streams
}

func benchmarkStream(n int, streams []client.Stream) {
	var wg sync.WaitGroup
	for _, stream := range streams {
		for i := 0; i < n; i++ {
			//Notice here. we must pass stream as a parameter because the varibale stream
			//would be changed after the goroutine created
			if s.Async {
				wg.Add(2)
				go func(stream client.Stream) { send(nil, stream); wg.Done() }(stream)
				go func(stream client.Stream) { recv(nil, stream); wg.Done() }(stream)
			} else {
				wg.Add(1)
				go func(stream client.Stream) { run(nil, stream); wg.Done() }(stream)
			}
		}
	}
	go statPrint()
	wg.Wait()
}
func benchmarkUnary(n int, clients []client.Client) {
	var wg sync.WaitGroup
	for _, cli := range clients {
		for i := 0; i < n; i++ {
			wg.Add(1)
			if cli, ok := cli.(client.UnaryClient); ok {
				go func(cli client.UnaryClient) { runUnary(nil, cli); wg.Done() }(cli)
			} else {
				log.Fatalln(s.Target, " does not implement the client.UnaryClient")
			}
		}
	}
	go statPrint()
	wg.Wait()
}

func runUnary(done <-chan int, cli client.UnaryClient) {
	for i := 0; s.N == 0 || i < s.N; i++ {
		//select {
		//case <-done:
		//	log.Println("run goroutine exit done")
		//	return
		//default:
		start := time.Now()
		if err := cli.Request(); err != nil {
			log.Println(err)
		}
		eplase := time.Since(start)
		stats.latencies = append(stats.latencies, eplase)
		stats.histogram.Add(int64(eplase))
		if s.Delay > 0 {
			time.Sleep(s.Delay)
		}
		//	}
	}
}
func run(done <-chan int, stream client.Stream) {
	for i := 0; s.N == 0 || i < s.N; i++ {
		select {
		case <-done:
			log.Println("run goroutine exit done")
			return
		default:
			start := time.Now()
			if s.Send {
				stream.DoSend()
			}
			if s.Recv {
				stream.DoRecv()
			}
			eplase := time.Since(start)
			stats.latencies = append(stats.latencies, eplase)
			stats.histogram.Add(int64(eplase))
			if s.Delay > 0 {
				time.Sleep(s.Delay)
			}
		}
	}
}

func send(done <-chan int, stream client.Stream) {
	timer := time.NewTimer(time.Second)
	for i := 0; s.N == 0 || i < s.N; i++ {
		select {
		case <-done:
			log.Println("send goroutine exit, done")
			return
		default:
			select {
			case rtts <- &roundtrip{start: time.Now(), ack: false}:
				timer.Reset(time.Second)
			case <-timer.C:
				log.Println("blocked on send rtts")
			}

			if burst != nil {
				select {
				case burst <- 0:
					timer.Reset(time.Second)
				case <-timer.C:
					log.Println("blocked on send burst chan")
				}
			}

			stream.DoSend()
			if s.Delay > 0 {
				time.Sleep(s.Delay)
			}
		}
	}
}
func recv(done <-chan int, stream client.Stream) {
	timer := time.NewTimer(time.Second)
	for i := 0; s.N == 0 || i < s.N; i++ {
		select {
		case <-done:
			log.Println("recv goroutine exit, done")
			return
		default:
			if burst != nil {
				select {
				case <-burst:
					timer.Reset(time.Second)
				case <-timer.C:
					log.Println("blocked on recv burst chan")
				}
			}
			err := stream.DoRecv()
			if err != nil {
				log.Println("recv goroutine exit", err)
				return
			}
			select {
			case rtt := <-rtts:
				timer.Reset(time.Second)
				rtt.ack = true
				eplase := time.Since(rtt.start)
				stats.latencies = append(stats.latencies, eplase)
				stats.histogram.Add(int64(eplase))
			case <-timer.C:
				log.Println("blocked on recv rtts")
			}
			if s.Delay > 0 {
				time.Sleep(s.Delay)
			}
		}
	}
}

func statPrint() {
	tickc := time.Tick(s.Tick)
	var latencies []time.Duration
	total := int64(0)
	for {
		select {
		case <-tickc:
			latencies = stats.latencies
			stats.latencies = stats.latencies[:0]

			sum := time.Duration(0)
			for _, eplase := range latencies {
				total++
				sum += eplase
			}
			count := len(latencies)
			if count != 0 {
				log.Printf("latency %v qps %d total %v\n", sum/time.Duration(count), int64(float64(count)/float64(s.Tick)*float64(time.Second)), total)
				if influxdb != nil {
					bp, _ := db.NewBatchPoints(db.BatchPointsConfig{
						Database:  "fperf",
						Precision: "s",
					})
					tags := map[string]string{"latency": "latency", "qps": "qps"}
					fields := map[string]interface{}{
						"latency": float64(sum) / float64(count) / 1000.0,
						"qps":     int64(float64(count) / float64(s.Tick) * float64(time.Second)),
					}
					pt, err := db.NewPoint("benchmark", tags, fields, time.Now())
					if err != nil {
						log.Println("Error: ", err.Error())
					}
					bp.AddPoint(pt)

					// Write the batch
					err = influxdb.Write(bp)
					if err != nil {
						log.Println("Error: ", err.Error())
					}
				}
			} else {
				log.Printf("blocking...")
			}
		}
	}
}

var s setting
var stats statistics
var rtts = make(chan *roundtrip, 10*1024*1024)
var mutex sync.RWMutex
var burst chan int
var influxdb db.Client

func usage() {
	fmt.Printf("Usage: %v [options] <client>\noptions:\n", os.Args[0])
	flag.PrintDefaults()
	fmt.Println("clients:")
	for name, desc := range client.AllClients() {
		fmt.Printf(" %s", name)
		if len(desc) > 0 {
			fmt.Printf("\t: %s", desc)
		}
		fmt.Println()
	}
}

func main() {
	flag.IntVar(&s.Connection, "connection", 1, "number of connection")
	flag.IntVar(&s.Stream, "stream", 1, "number of streams per connection")
	flag.IntVar(&s.Goroutine, "goroutine", 1, "number of goroutines per stream")
	flag.IntVar(&s.CPU, "cpu", 0, "set the GOMAXPROCS, use go default if 0")
	flag.IntVar(&s.Burst, "burst", 0, "burst a number of request, use with -async=true")
	flag.IntVar(&s.N, "N", 0, "number of request per goroutine")
	flag.BoolVar(&s.Send, "send", true, "perform send action")
	flag.BoolVar(&s.Recv, "recv", true, "perform recv action")
	flag.DurationVar(&s.Delay, "delay", 0, "wait delay time before send the next request")
	flag.DurationVar(&s.Tick, "tick", 2*time.Second, "interval between statistics")
	flag.StringVar(&s.Address, "server", "127.0.0.1:8804", "address of the target server")
	flag.BoolVar(&s.Async, "async", false, "send and recv in seperate goroutines")
	flag.StringVar(&s.CallType, "type", "auto", "set the call type:unary, stream or auto. default is auto")
	flag.StringVar(&s.InfluxDB, "influxdb", "", "writing stats to influxdb, specify the address in this option")
	flag.Usage = usage
	flag.Parse()

	s.Target = flag.Arg(0)
	if len(s.Target) == 0 {
		flag.Usage()
		return
	}
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		_ = <-c
		stats.histogram.Value().Print(os.Stdout)
		os.Exit(0)
	}()

	runtime.GOMAXPROCS(s.CPU)
	go func() {
		runtime.SetBlockProfileRate(1)
		log.Println(http.ListenAndServe(":6060", nil))
	}()

	if s.Burst > 0 {
		burst = make(chan int, s.Burst)
	}

	if len(s.InfluxDB) > 0 {
		c, err := db.NewHTTPClient(db.HTTPConfig{
			Addr: s.InfluxDB,
		})
		if err != nil {
			log.Fatalf("Error creating InfluxDB Client: ", err.Error())
		}
		defer c.Close()
		q := db.NewQuery("CREATE DATABASE fperf", "", "")
		if response, err := c.Query(q); err == nil && response.Error() == nil {
			log.Println(response.Results)
		}
		influxdb = c
	}

	stats.latencies = make([]time.Duration, 0, 500000)
	histopt := hist.HistogramOptions{
		NumBuckets:         16,
		GrowthFactor:       1.8,
		SmallestBucketSize: 1000,
		MinValue:           10000,
	}
	stats.histogram = hist.NewHistogram(histopt)
	clients := createClients(s.Connection, s.Address)
	cli := clients[0]
	switch s.CallType {
	case "auto":
		switch cli.(type) {
		case client.StreamClient:
			streams := createStreams(s.Stream, clients)
			benchmarkStream(s.Goroutine, streams)
		case client.UnaryClient:
			benchmarkUnary(s.Goroutine, clients)
		}
	case "stream":
		streams := createStreams(s.Stream, clients)
		benchmarkStream(s.Goroutine, streams)
	case "unary":
		benchmarkUnary(s.Goroutine, clients)
	}
}
<commit_msg>Fix import path of influxdb<commit_after>/*
fperf allows you to build your performace tools easily

Three steps to create your own testcase

1. Create the "NewClient" function

	package demo

	import (
		"fmt"
		"github.com/shafreeck/fperf/client"
		"time"
	)

	type DemoClient struct{}

	func NewDemoClient(flag *client.FlagSet) client.Client {
		return &DemoClient{}
	}

2. Implement the UnaryClient or StreamClient

	func (c *DemoClient) Dial(addr string) error {
		fmt.Println("Dial to", addr)
		return nil
	}

	func (c *DemoClient) Request() error {
		time.Sleep(100 * time.Millisecond)
		return nil
	}

3. Register to fperf

	func init() {
		client.Register("demo", NewDemoClient, "This is a demo client discription")
	}

Run the buildin testcase

http_testing is a simple builtin testcase to benchmark http servers

	fperf -cpu 8 -connection 10 http_testing -url http://example.com
*/
package main

import (
	"flag"
	"fmt"
	db "github.com/influxdata/influxdb/client/v2"
	"github.com/shafreeck/fperf/client"
	hist "github.com/shafreeck/fperf/stats"
	"golang.org/x/net/context"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"
)

type setting struct {
	Connection int
	Stream     int
	Goroutine  int
	CPU        int
	Burst      int
	N          int //number of requests
	Tick       time.Duration
	Address    string
	Send       bool
	Recv       bool
	Delay      time.Duration
	Async      bool
	Target     string
	CallType   string
	InfluxDB   string
}

type statistics struct {
	latencies []time.Duration
	histogram *hist.Histogram
}

type roundtrip struct {
	start time.Time
	end   time.Time
	ack   bool
}

func createClients(n int, addr string) []client.Client {
	clients := make([]client.Client, n)
	for i := 0; i < n; i++ {
		cli := client.NewClient(s.Target)
		if cli == nil {
			log.Fatalf("Can not find client %q for benchmark\n", s.Target)
		}

		cli.Dial(addr)
		clients[i] = cli
	}
	return clients
}
func createStreams(n int, clients []client.Client) []client.Stream {
	streams := make([]client.Stream, n*len(clients))
	for cur, cli := range clients {
		for i := 0; i < n; i++ {
			if cli, ok := cli.(client.StreamClient); ok {
				stream, err := cli.CreateStream(context.Background())
				if err != nil {
					log.Fatalf("StreamCall faile to create new stream, %v", err)
				}
				streams[cur*n+i] = stream
			} else {
				log.Fatalln(s.Target, " do not implement the client.StreamClient")
			}
		}
	}
	return streams
}

func benchmarkStream(n int, streams []client.Stream) {
	var wg sync.WaitGroup
	for _, stream := range streams {
		for i := 0; i < n; i++ {
			//Notice here. we must pass stream as a parameter because the varibale stream
			//would be changed after the goroutine created
			if s.Async {
				wg.Add(2)
				go func(stream client.Stream) { send(nil, stream); wg.Done() }(stream)
				go func(stream client.Stream) { recv(nil, stream); wg.Done() }(stream)
			} else {
				wg.Add(1)
				go func(stream client.Stream) { run(nil, stream); wg.Done() }(stream)
			}
		}
	}
	go statPrint()
	wg.Wait()
}
func benchmarkUnary(n int, clients []client.Client) {
	var wg sync.WaitGroup
	for _, cli := range clients {
		for i := 0; i < n; i++ {
			wg.Add(1)
			if cli, ok := cli.(client.UnaryClient); ok {
				go func(cli client.UnaryClient) { runUnary(nil, cli); wg.Done() }(cli)
			} else {
				log.Fatalln(s.Target, " does not implement the client.UnaryClient")
			}
		}
	}
	go statPrint()
	wg.Wait()
}

func runUnary(done <-chan int, cli client.UnaryClient) {
	for i := 0; s.N == 0 || i < s.N; i++ {
		//select {
		//case <-done:
		//	log.Println("run goroutine exit done")
		//	return
		//default:
		start := time.Now()
		if err := cli.Request(); err != nil {
			log.Println(err)
		}
		eplase := time.Since(start)
		stats.latencies = append(stats.latencies, eplase)
		stats.histogram.Add(int64(eplase))
		if s.Delay > 0 {
			time.Sleep(s.Delay)
		}
		//	}
	}
}
func run(done <-chan int, stream client.Stream) {
	for i := 0; s.N == 0 || i < s.N; i++ {
		select {
		case <-done:
			log.Println("run goroutine exit done")
			return
		default:
			start := time.Now()
			if s.Send {
				stream.DoSend()
			}
			if s.Recv {
				stream.DoRecv()
			}
			eplase := time.Since(start)
			stats.latencies = append(stats.latencies, eplase)
			stats.histogram.Add(int64(eplase))
			if s.Delay > 0 {
				time.Sleep(s.Delay)
			}
		}
	}
}

func send(done <-chan int, stream client.Stream) {
	timer := time.NewTimer(time.Second)
	for i := 0; s.N == 0 || i < s.N; i++ {
		select {
		case <-done:
			log.Println("send goroutine exit, done")
			return
		default:
			select {
			case rtts <- &roundtrip{start: time.Now(), ack: false}:
				timer.Reset(time.Second)
			case <-timer.C:
				log.Println("blocked on send rtts")
			}

			if burst != nil {
				select {
				case burst <- 0:
					timer.Reset(time.Second)
				case <-timer.C:
					log.Println("blocked on send burst chan")
				}
			}

			stream.DoSend()
			if s.Delay > 0 {
				time.Sleep(s.Delay)
			}
		}
	}
}
func recv(done <-chan int, stream client.Stream) {
	timer := time.NewTimer(time.Second)
	for i := 0; s.N == 0 || i < s.N; i++ {
		select {
		case <-done:
			log.Println("recv goroutine exit, done")
			return
		default:
			if burst != nil {
				select {
				case <-burst:
					timer.Reset(time.Second)
				case <-timer.C:
					log.Println("blocked on recv burst chan")
				}
			}
			err := stream.DoRecv()
			if err != nil {
				log.Println("recv goroutine exit", err)
				return
			}
			select {
			case rtt := <-rtts:
				timer.Reset(time.Second)
				rtt.ack = true
				eplase := time.Since(rtt.start)
				stats.latencies = append(stats.latencies, eplase)
				stats.histogram.Add(int64(eplase))
			case <-timer.C:
				log.Println("blocked on recv rtts")
			}
			if s.Delay > 0 {
				time.Sleep(s.Delay)
			}
		}
	}
}

func statPrint() {
	tickc := time.Tick(s.Tick)
	var latencies []time.Duration
	total := int64(0)
	for {
		select {
		case <-tickc:
			latencies = stats.latencies
			stats.latencies = stats.latencies[:0]

			sum := time.Duration(0)
			for _, eplase := range latencies {
				total++
				sum += eplase
			}
			count := len(latencies)
			if count != 0 {
				log.Printf("latency %v qps %d total %v\n", sum/time.Duration(count), int64(float64(count)/float64(s.Tick)*float64(time.Second)), total)
				if influxdb != nil {
					bp, _ := db.NewBatchPoints(db.BatchPointsConfig{
						Database:  "fperf",
						Precision: "s",
					})
					tags := map[string]string{"latency": "latency", "qps": "qps"}
					fields := map[string]interface{}{
						"latency": float64(sum) / float64(count) / 1000.0,
						"qps":     int64(float64(count) / float64(s.Tick) * float64(time.Second)),
					}
					pt, err := db.NewPoint("benchmark", tags, fields, time.Now())
					if err != nil {
						log.Println("Error: ", err.Error())
					}
					bp.AddPoint(pt)

					// Write the batch
					err = influxdb.Write(bp)
					if err != nil {
						log.Println("Error: ", err.Error())
					}
				}
			} else {
				log.Printf("blocking...")
			}
		}
	}
}

var s setting
var stats statistics
var rtts = make(chan *roundtrip, 10*1024*1024)
var mutex sync.RWMutex
var burst chan int
var influxdb db.Client

func usage() {
	fmt.Printf("Usage: %v [options] <client>\noptions:\n", os.Args[0])
	flag.PrintDefaults()
	fmt.Println("clients:")
	for name, desc := range client.AllClients() {
		fmt.Printf(" %s", name)
		if len(desc) > 0 {
			fmt.Printf("\t: %s", desc)
		}
		fmt.Println()
	}
}

func main() {
	flag.IntVar(&s.Connection, "connection", 1, "number of connection")
	flag.IntVar(&s.Stream, "stream", 1, "number of streams per connection")
	flag.IntVar(&s.Goroutine, "goroutine", 1, "number of goroutines per stream")
	flag.IntVar(&s.CPU, "cpu", 0, "set the GOMAXPROCS, use go default if 0")
	flag.IntVar(&s.Burst, "burst", 0, "burst a number of request, use with -async=true")
	flag.IntVar(&s.N, "N", 0, "number of request per goroutine")
	flag.BoolVar(&s.Send, "send", true, "perform send action")
	flag.BoolVar(&s.Recv, "recv", true, "perform recv action")
	flag.DurationVar(&s.Delay, "delay", 0, "wait delay time before send the next request")
	flag.DurationVar(&s.Tick, "tick", 2*time.Second, "interval between statistics")
	flag.StringVar(&s.Address, "server", "127.0.0.1:8804", "address of the target server")
	flag.BoolVar(&s.Async, "async", false, "send and recv in seperate goroutines")
	flag.StringVar(&s.CallType, "type", "auto", "set the call type:unary, stream or auto. default is auto")
	flag.StringVar(&s.InfluxDB, "influxdb", "", "writing stats to influxdb, specify the address in this option")
	flag.Usage = usage
	flag.Parse()

	s.Target = flag.Arg(0)
	if len(s.Target) == 0 {
		flag.Usage()
		return
	}
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		_ = <-c
		stats.histogram.Value().Print(os.Stdout)
		os.Exit(0)
	}()

	runtime.GOMAXPROCS(s.CPU)
	go func() {
		runtime.SetBlockProfileRate(1)
		log.Println(http.ListenAndServe(":6060", nil))
	}()

	if s.Burst > 0 {
		burst = make(chan int, s.Burst)
	}

	if len(s.InfluxDB) > 0 {
		c, err := db.NewHTTPClient(db.HTTPConfig{
			Addr: s.InfluxDB,
		})
		if err != nil {
			log.Fatalf("Error creating InfluxDB Client: ", err.Error())
		}
		defer c.Close()
		q := db.NewQuery("CREATE DATABASE fperf", "", "")
		if response, err := c.Query(q); err == nil && response.Error() == nil {
			log.Println(response.Results)
		}
		influxdb = c
	}

	stats.latencies = make([]time.Duration, 0, 500000)
	histopt := hist.HistogramOptions{
		NumBuckets:         16,
		GrowthFactor:       1.8,
		SmallestBucketSize: 1000,
		MinValue:           10000,
	}
	stats.histogram = hist.NewHistogram(histopt)
	clients := createClients(s.Connection, s.Address)
	cli := clients[0]
	switch s.CallType {
	case "auto":
		switch cli.(type) {
		case client.StreamClient:
			streams := createStreams(s.Stream, clients)
			benchmarkStream(s.Goroutine, streams)
		case client.UnaryClient:
			benchmarkUnary(s.Goroutine, clients)
		}
	case "stream":
		streams := createStreams(s.Stream, clients)
		benchmarkStream(s.Goroutine, streams)
	case "unary":
		benchmarkUnary(s.Goroutine, clients)
	}
}
<|endoftext|>


<commit_before>// Package main provides ...
// 这里是前端页面展示相关接口
package main

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/eiblog/eiblog/setting"
	"github.com/eiblog/utils/logd"
	"github.com/gin-gonic/gin"
)

func Filter() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 过滤黑名单
		BlackFilter(c)
		// 用户cookie，用于统计
		UserCookie(c)
		c.Next()
	}
}

// 用户识别
func UserCookie(c *gin.Context) {
	cookie, err := c.Cookie("u")
	if err != nil || cookie == "" {
		c.SetCookie("u", RandUUIDv4(), 86400*730, "/", "", true, true)
	}
}

// 黑名单过滤
func BlackFilter(c *gin.Context) {
	ip := c.ClientIP()
	if setting.BlackIP[ip] {
		c.Abort()
		c.String(http.StatusForbidden, "Your IP is blacklisted.")
	}
}

// 解析静态文件版本
func StaticVersion(c *gin.Context) (version int) {
	cookie, err := c.Request.Cookie("v")
	if err != nil || cookie.Value != fmt.Sprint(setting.Conf.StaticVersion) {
		return setting.Conf.StaticVersion
	}
	return 0
}

func GetBase() gin.H {
	return gin.H{
		"Favicon":  setting.Conf.Favicon,
		"BlogName": Ei.BlogName,
		"SubTitle": Ei.SubTitle,
		"Twitter":  setting.Conf.Twitter,
		"CopyYear": time.Now().Year(),
		"BTitle":   Ei.BTitle,
		"BeiAn":    Ei.BeiAn,
		"Domain":   setting.Conf.Mode.Domain,
		"Kodo":     setting.Conf.Kodo,
	}
}

func HandleNotFound(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "Not Found"
	h["Path"] = ""
	c.Status(http.StatusNotFound)
	RenderHTMLFront(c, "notfound", h)
}

func HandleHomePage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = Ei.BTitle + " | " + Ei.SubTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "blog-home"
	pn, err := strconv.Atoi(c.Query("pn"))
	if err != nil || pn < 1 {
		pn = 1
	}
	h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.PageNum)
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "home", h)
}

func HandleSeriesPage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "专题 | " + Ei.BTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "series"
	h["Article"] = Ei.PageSeries
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "series", h)
}

func HandleArchivesPage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "归档 | " + Ei.BTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "archives"
	h["Article"] = Ei.PageArchives
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "archives", h)
}

func HandleArticlePage(c *gin.Context) {
	path := c.Param("slug")
	artc := Ei.MapArticles[path[0:strings.Index(path, ".")]]
	if artc == nil {
		HandleNotFound(c)
		return
	}
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = artc.Title + " | " + Ei.BTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "post-" + artc.Slug
	var name string
	if path == "blogroll.html" {
		name = "blogroll"
	} else if path == "about.html" {
		name = "about"
	} else {
		name = "article"
		h["Copyright"] = Ei.Copyright
		if !artc.UpdateTime.IsZero() {
			h["Days"] = int(time.Now().Sub(artc.UpdateTime).Hours()) / 24
		} else {
			h["Days"] = int(time.Now().Sub(artc.CreateTime).Hours()) / 24
		}
		if artc.SerieID > 0 {
			h["Serie"] = QuerySerie(artc.SerieID)
		}
	}
	h["Article"] = artc
	c.Status(http.StatusOK)
	RenderHTMLFront(c, name, h)
}

func HandleSearchPage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "站内搜索 | " + Ei.BTitle
	h["Path"] = ""
	h["CurrentPage"] = "search-post"

	q := strings.TrimSpace(c.Query("q"))
	if q != "" {
		start, err := strconv.Atoi(c.Query("start"))
		if start < 1 || err != nil {
			start = 1
		}
		h["Word"] = q
		var result *ESSearchResult
		vals := c.Request.URL.Query()
		result = Elasticsearch(q, setting.Conf.PageNum, start-1)
		if result != nil {
			result.Took /= 1000
			for i, v := range result.Hits.Hits {
				if artc := Ei.MapArticles[result.Hits.Hits[i].Source.Slug]; len(v.Highlight.Content) == 0 && artc != nil {
					result.Hits.Hits[i].Highlight.Content = []string{artc.Excerpt}
				}
			}
			h["SearchResult"] = result
			if start-setting.Conf.PageNum > 0 {
				vals.Set("start", fmt.Sprint(start-setting.Conf.PageNum))
				h["Prev"] = vals.Encode()
			}
			if result.Hits.Total >= start+setting.Conf.PageNum {
				vals.Set("start", fmt.Sprint(start+setting.Conf.PageNum))
				h["Next"] = vals.Encode()
			}
		}
	} else {
		h["HotWords"] = setting.Conf.HotWords
	}
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "search", h)
}

func HandleFeed(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/feed.xml")
}

func HandleOpenSearch(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
}

func HandleRobots(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/robots.txt")
}

func HandleSitemap(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
}

// 服务端推送谷歌统计
func HandleBeacon(c *gin.Context) {
	ua := c.Request.UserAgent()
	// TODO 过滤黑名单
	vals := c.Request.URL.Query()
	vals.Set("v", setting.Conf.Google.V)
	vals.Set("tid", setting.Conf.Google.Tid)
	vals.Set("t", setting.Conf.Google.T)
	cookie, _ := c.Cookie("u")
	vals.Set("cid", cookie)

	vals.Set("dl", c.Request.Referer())
	vals.Set("uip", c.ClientIP())
	go func() {
		req, err := http.NewRequest("POST", "https://www.google-analytics.com/collect", strings.NewReader(vals.Encode()))
		if err != nil {
			logd.Error(err)
			return
		}
		req.Header.Set("User-Agent", ua)
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			logd.Error(err)
			return
		}
		defer res.Body.Close()
		data, err := ioutil.ReadAll(res.Body)
		if err != nil {
			logd.Error(err)
			return
		}
		if res.StatusCode/100 != 2 {
			logd.Error(string(data))
		}
	}()
	c.String(http.StatusNoContent, "accepted")
}

// 服务端获取评论详细
type DisqusComments struct {
	ErrNo  int    `json:"errno"`
	ErrMsg string `json:"errmsg"`
	Data   struct {
		Next     string           `json:"next"`
		Total    int              `json:"total,omitempty"`
		Comments []commentsDetail `json:"comments"`
	} `json:"data"`
}

type commentsDetail struct {
	Id           string `json:"id"`
	Parent       int    `json:"parent"`
	Name         string `json:"name"`
	Url          string `json:"url"`
	Avatar       string `json:"avatar"`
	CreatedAt    string `json:"createdAt"`
	CreatedAtStr string `json:"createdAtStr"`
	Message      string `json:"message"`
}

func HandleDisqus(c *gin.Context) {
	slug := c.Param("slug")
	cursor := c.Query("cursor")
	dcs := DisqusComments{}
	postsList := PostsList(slug, cursor)
	if postsList != nil {
		dcs.ErrNo = postsList.Code
		if postsList.Cursor.HasNext {
			dcs.Data.Next = postsList.Cursor.Next
		}
		if cursor == "" {
			dcs.Data.Total = Ei.MapArticles[slug].Count
		}
		dcs.Data.Comments = make([]commentsDetail, len(postsList.Response))
		for i, v := range postsList.Response {
			dcs.Data.Comments[i] = commentsDetail{
				Id:           v.Id,
				Name:         v.Author.Name,
				Parent:       v.Parent,
				Url:          v.Author.ProfileUrl,
				Avatar:       v.Author.Avatar.Cache,
				CreatedAt:    v.CreatedAt,
				CreatedAtStr: ConvertStr(v.CreatedAt),
				Message:      v.Message,
			}
		}
	} else {
		dcs.ErrNo = FAIL
		dcs.ErrMsg = "系统错误"
	}
	c.JSON(http.StatusOK, dcs)
}

func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
	var buf bytes.Buffer
	err := Tmpl.ExecuteTemplate(&buf, name, data)
	if err != nil {
		panic(err)
	}
	data["LayoutContent"] = template.HTML(buf.String())
	err = Tmpl.ExecuteTemplate(c.Writer, "homeLayout.html", data)
	if err != nil {
		panic(err)
	}
	c.Header("Content-Type", "text/html; charset=utf-8")
}
<commit_msg>fix comments count<commit_after>// Package main provides ...
// 这里是前端页面展示相关接口
package main

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/eiblog/eiblog/setting"
	"github.com/eiblog/utils/logd"
	"github.com/gin-gonic/gin"
)

func Filter() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 过滤黑名单
		BlackFilter(c)
		// 用户cookie，用于统计
		UserCookie(c)
		c.Next()
	}
}

// 用户识别
func UserCookie(c *gin.Context) {
	cookie, err := c.Cookie("u")
	if err != nil || cookie == "" {
		c.SetCookie("u", RandUUIDv4(), 86400*730, "/", "", true, true)
	}
}

// 黑名单过滤
func BlackFilter(c *gin.Context) {
	ip := c.ClientIP()
	if setting.BlackIP[ip] {
		c.Abort()
		c.String(http.StatusForbidden, "Your IP is blacklisted.")
	}
}

// 解析静态文件版本
func StaticVersion(c *gin.Context) (version int) {
	cookie, err := c.Request.Cookie("v")
	if err != nil || cookie.Value != fmt.Sprint(setting.Conf.StaticVersion) {
		return setting.Conf.StaticVersion
	}
	return 0
}

func GetBase() gin.H {
	return gin.H{
		"Favicon":  setting.Conf.Favicon,
		"BlogName": Ei.BlogName,
		"SubTitle": Ei.SubTitle,
		"Twitter":  setting.Conf.Twitter,
		"CopyYear": time.Now().Year(),
		"BTitle":   Ei.BTitle,
		"BeiAn":    Ei.BeiAn,
		"Domain":   setting.Conf.Mode.Domain,
		"Kodo":     setting.Conf.Kodo,
	}
}

func HandleNotFound(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "Not Found"
	h["Path"] = ""
	c.Status(http.StatusNotFound)
	RenderHTMLFront(c, "notfound", h)
}

func HandleHomePage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = Ei.BTitle + " | " + Ei.SubTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "blog-home"
	pn, err := strconv.Atoi(c.Query("pn"))
	if err != nil || pn < 1 {
		pn = 1
	}
	h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.PageNum)
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "home", h)
}

func HandleSeriesPage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "专题 | " + Ei.BTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "series"
	h["Article"] = Ei.PageSeries
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "series", h)
}

func HandleArchivesPage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "归档 | " + Ei.BTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "archives"
	h["Article"] = Ei.PageArchives
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "archives", h)
}

func HandleArticlePage(c *gin.Context) {
	path := c.Param("slug")
	artc := Ei.MapArticles[path[0:strings.Index(path, ".")]]
	if artc == nil {
		HandleNotFound(c)
		return
	}
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = artc.Title + " | " + Ei.BTitle
	h["Path"] = c.Request.URL.Path
	h["CurrentPage"] = "post-" + artc.Slug
	var name string
	if path == "blogroll.html" {
		name = "blogroll"
	} else if path == "about.html" {
		name = "about"
	} else {
		name = "article"
		h["Copyright"] = Ei.Copyright
		if !artc.UpdateTime.IsZero() {
			h["Days"] = int(time.Now().Sub(artc.UpdateTime).Hours()) / 24
		} else {
			h["Days"] = int(time.Now().Sub(artc.CreateTime).Hours()) / 24
		}
		if artc.SerieID > 0 {
			h["Serie"] = QuerySerie(artc.SerieID)
		}
	}
	h["Article"] = artc
	c.Status(http.StatusOK)
	RenderHTMLFront(c, name, h)
}

func HandleSearchPage(c *gin.Context) {
	h := GetBase()
	h["Version"] = StaticVersion(c)
	h["Title"] = "站内搜索 | " + Ei.BTitle
	h["Path"] = ""
	h["CurrentPage"] = "search-post"

	q := strings.TrimSpace(c.Query("q"))
	if q != "" {
		start, err := strconv.Atoi(c.Query("start"))
		if start < 1 || err != nil {
			start = 1
		}
		h["Word"] = q
		var result *ESSearchResult
		vals := c.Request.URL.Query()
		result = Elasticsearch(q, setting.Conf.PageNum, start-1)
		if result != nil {
			result.Took /= 1000
			for i, v := range result.Hits.Hits {
				if artc := Ei.MapArticles[result.Hits.Hits[i].Source.Slug]; len(v.Highlight.Content) == 0 && artc != nil {
					result.Hits.Hits[i].Highlight.Content = []string{artc.Excerpt}
				}
			}
			h["SearchResult"] = result
			if start-setting.Conf.PageNum > 0 {
				vals.Set("start", fmt.Sprint(start-setting.Conf.PageNum))
				h["Prev"] = vals.Encode()
			}
			if result.Hits.Total >= start+setting.Conf.PageNum {
				vals.Set("start", fmt.Sprint(start+setting.Conf.PageNum))
				h["Next"] = vals.Encode()
			}
		}
	} else {
		h["HotWords"] = setting.Conf.HotWords
	}
	c.Status(http.StatusOK)
	RenderHTMLFront(c, "search", h)
}

func HandleFeed(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/feed.xml")
}

func HandleOpenSearch(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
}

func HandleRobots(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/robots.txt")
}

func HandleSitemap(c *gin.Context) {
	http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
}

// 服务端推送谷歌统计
func HandleBeacon(c *gin.Context) {
	ua := c.Request.UserAgent()
	// TODO 过滤黑名单
	vals := c.Request.URL.Query()
	vals.Set("v", setting.Conf.Google.V)
	vals.Set("tid", setting.Conf.Google.Tid)
	vals.Set("t", setting.Conf.Google.T)
	cookie, _ := c.Cookie("u")
	vals.Set("cid", cookie)

	vals.Set("dl", c.Request.Referer())
	vals.Set("uip", c.ClientIP())
	go func() {
		req, err := http.NewRequest("POST", "https://www.google-analytics.com/collect", strings.NewReader(vals.Encode()))
		if err != nil {
			logd.Error(err)
			return
		}
		req.Header.Set("User-Agent", ua)
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			logd.Error(err)
			return
		}
		defer res.Body.Close()
		data, err := ioutil.ReadAll(res.Body)
		if err != nil {
			logd.Error(err)
			return
		}
		if res.StatusCode/100 != 2 {
			logd.Error(string(data))
		}
	}()
	c.String(http.StatusNoContent, "accepted")
}

// 服务端获取评论详细
type DisqusComments struct {
	ErrNo  int    `json:"errno"`
	ErrMsg string `json:"errmsg"`
	Data   struct {
		Next     string           `json:"next"`
		Total    int              `json:"total,omitempty"`
		Comments []commentsDetail `json:"comments"`
	} `json:"data"`
}

type commentsDetail struct {
	Id           string `json:"id"`
	Parent       int    `json:"parent"`
	Name         string `json:"name"`
	Url          string `json:"url"`
	Avatar       string `json:"avatar"`
	CreatedAt    string `json:"createdAt"`
	CreatedAtStr string `json:"createdAtStr"`
	Message      string `json:"message"`
}

func HandleDisqus(c *gin.Context) {
	slug := c.Param("slug")
	cursor := c.Query("cursor")
	dcs := DisqusComments{}
	postsList := PostsList(slug, cursor)
	if postsList != nil {
		dcs.ErrNo = postsList.Code
		if postsList.Cursor.HasNext {
			dcs.Data.Next = postsList.Cursor.Next
		}
		dcs.Data.Total = Ei.MapArticles[slug].Count
		dcs.Data.Comments = make([]commentsDetail, len(postsList.Response))
		for i, v := range postsList.Response {
			dcs.Data.Comments[i] = commentsDetail{
				Id:           v.Id,
				Name:         v.Author.Name,
				Parent:       v.Parent,
				Url:          v.Author.ProfileUrl,
				Avatar:       v.Author.Avatar.Cache,
				CreatedAt:    v.CreatedAt,
				CreatedAtStr: ConvertStr(v.CreatedAt),
				Message:      v.Message,
			}
		}
	} else {
		dcs.ErrNo = FAIL
		dcs.ErrMsg = "系统错误"
	}
	c.JSON(http.StatusOK, dcs)
}

func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
	var buf bytes.Buffer
	err := Tmpl.ExecuteTemplate(&buf, name, data)
	if err != nil {
		panic(err)
	}
	data["LayoutContent"] = template.HTML(buf.String())
	err = Tmpl.ExecuteTemplate(c.Writer, "homeLayout.html", data)
	if err != nil {
		panic(err)
	}
	c.Header("Content-Type", "text/html; charset=utf-8")
}
<|endoftext|>


<commit_before>// Data structs (and functions to act on them) to hold Player information

package main

import "fmt"

type Gender int

const (
	Male Gender = iota
	Female
	Default
)

func StringToGender(s string) (Gender, error) {
	switch s {
	case "m":
		return Male, nil
	case "f":
		return Female, nil
	}
	return Default, fmt.Errorf("invalid gender '%s'", s)
}

type Player struct {
	name   string
	value  float32
	gender Gender
	team   uint8
}
<commit_msg>player: docs<commit_after>// Data structs (and functions to act on them) to hold Player information

package main

import "fmt"

type Gender int

const (
	Male Gender = iota
	Female
	Default
)

// Parses single character string into Gender
//
// The string must be either "m" or "f", or we return error.
func StringToGender(s string) (Gender, error) {
	switch s {
	case "m":
		return Male, nil
	case "f":
		return Female, nil
	}
	return Default, fmt.Errorf("invalid gender '%s'", s)
}

type Player struct {
	name   string
	value  float32
	gender Gender
	team   uint8
}
<|endoftext|>


<commit_before>package control

import (
	"log"
	"time"

	. "../utilities"

	. "../driver"
	"../queue"
)

const (
	idle = iota
	moving
	doorOpen
)

var state int = idle
var floor int = FloorInvalid // to initialize or not to initialize?
var direction int

// Need three functions in a queue.go file to work:
// ShouldStop(floor, direction)
// ChooseDirection(floor, direction)
// RemoveOrdersAt(floor)

func eventManager(newOrder chan bool, floorReached chan int,
	lightChannel chan ElevatorLight, motorChannel chan int) {
	// if restore order from file do ..., else:
	floor = GoToFloorBelow()

	doorTimeout := make(chan bool)
	doorTimerReset := make(chan bool)
	go doorTimer(doorTimeout, doorTimerReset)
	go stateIndicator() // use for debug purpose only - remove later?

	for {
		select {
		case <-newOrder:
			eventNewOrder(lightChannel, doorTimerReset)
		case floor = <-floorReached:
			eventFloorReached(lightChannel, motorChannel, doorTimerReset)
		case <-doorTimeout:
			eventDoorTimeout(lightChannel, motorChannel)
		}
	}
}

func eventNewOrder(lightChannel chan ElevatorLight, doorTimerReset chan bool) {
	switch state {
	case idle:
		direction = queue.ChooseDirection(floor, direction)
		if queue.ShouldStop(floor, direction) {
			doorTimerReset <- true
			queue.RemoveOrder(floor, direction)
			//queue.RemoveOrdersAt(floor, sendMessageChannel) // change the above function with this later
			lightChannel <- ElevatorLight{Kind: DoorIndicator, Active: true}
			state = doorOpen
		}
	case moving: // Ignore
	case doorOpen:
		if queue.ShouldStop(floor, direction) {
			doorTimerReset <- true
			queue.RemoveOrder(floor, direction)
			//queue.RemoveOrdersAt(floor, sendMessageChannel)
		}
	default:
		// Insert error handling
	}
}

func eventFloorReached(lightChannel chan ElevatorLight, motorChannel chan int, doorTimerReset chan bool) {
	SetFloorIndicator(floor)
	switch state {
	case moving:
		if queue.ShouldStop(floor, direction) { // not implemented yet
			doorTimerReset <- true
			queue.RemoveOrdersAt()
			lightChannel <- ElevatorLight{Kind: DoorIndicator, Active: true}
			direction = MotorStop
			motorChannel <- MotorStop
			state = doorOpen
		}
	default:
		// Insert error handling
	}
}

func eventDoorTimeout(lightChannel chan ElevatorLight, motorChannel chan int) {
	switch state {
	case doorOpen:
		lightChannel <- ElevatorLight{Kind: DoorIndicator, Active: false}
		direction = queue.ChooseDirection(floor, direction) // not implemented yet
		if direction == MotorStop {
			state = idle
		} else {
			state = moving
		}
	default:
		// Insert error handling here - elevator might possibly need to be restarted ()
	}
}

func doorTimer(timeout chan<- bool, reset <-chan bool) {
	const doorOpenTime = 3 * time.Second
	timer := time.NewTimer(0)
	timer.Stop()

	for {
		select {
		case <-reset:
			timer.Reset(doorOpenTime)
		case <-timer.C:
			timer.Stop()
			timeout <- true
		}
	}
}

func stateIndicator() {
	prevState := idle
	for {
		if state != prevState {
			switch state {
			case idle:
				log.Println("STATE [eventManager]: idle")
			case moving:
				log.Println("STATE [eventManager]: moving")
			case doorOpen:
				log.Println("STATE [eventManager]: doorOpen")
			}
			prevState = state
		}
	}
}
<commit_msg>Fix #5 to control package<commit_after>package control

import (
	"log"
	"time"

	. "../utilities"

	. "../driver"
	"../queue"
)

const (
	idle = iota
	moving
	doorOpen
)

var state int = idle
var floor int = FloorInvalid // to initialize or not to initialize?
var direction int

// Need three functions in a queue.go file to work:
// ShouldStop(floor, direction)
// ChooseDirection(floor, direction)
// RemoveOrdersAt(floor)

func eventManager(newOrder chan bool, floorReached chan int,
	lightChannel chan ElevatorLight, motorChannel chan int) {
	// if restore order from file do ..., else:
	const pollDelay = 5 * time.Millisecond
	floor = GoToFloorBelow(motorChannel, pollDelay)

	doorTimeout := make(chan bool)
	doorTimerReset := make(chan bool)
	go doorTimer(doorTimeout, doorTimerReset)
	go stateIndicator() // use for debug purpose only - remove later?

	for {
		select {
		case <-newOrder:
			eventNewOrder(lightChannel, doorTimerReset)
		case floor = <-floorReached:
			eventFloorReached(lightChannel, motorChannel, doorTimerReset)
		case <-doorTimeout:
			eventDoorTimeout(lightChannel, motorChannel)
		}
	}
}

func eventNewOrder(lightChannel chan ElevatorLight, doorTimerReset chan bool) {
	switch state {
	case idle:
		direction = queue.ChooseDirection(floor, direction)
		if queue.ShouldStop(floor, direction) {
			doorTimerReset <- true
			queue.RemoveOrder(floor, direction)
			//queue.RemoveOrdersAt(floor, sendMessageChannel) // change the above function with this later
			lightChannel <- ElevatorLight{Kind: DoorIndicator, Active: true}
			state = doorOpen
		}
	case moving: // Ignore
	case doorOpen:
		if queue.ShouldStop(floor, direction) {
			doorTimerReset <- true
			queue.RemoveOrder(floor, direction)
			//queue.RemoveOrdersAt(floor, sendMessageChannel)
		}
	default:
		// Insert error handling
	}
}

func eventFloorReached(lightChannel chan ElevatorLight, motorChannel chan int, doorTimerReset chan bool) {
	SetFloorIndicator(floor)
	switch state {
	case moving:
		if queue.ShouldStop(floor, direction) { // not implemented yet
			doorTimerReset <- true
			queue.RemoveOrder(floor, direction)
			//queue.RemoveOrdersAt()
			lightChannel <- ElevatorLight{Kind: DoorIndicator, Active: true}
			direction = MotorStop
			motorChannel <- MotorStop
			state = doorOpen
		}
	default:
		// Insert error handling
	}
}

func eventDoorTimeout(lightChannel chan ElevatorLight, motorChannel chan int) {
	switch state {
	case doorOpen:
		lightChannel <- ElevatorLight{Kind: DoorIndicator, Active: false}
		direction = queue.ChooseDirection(floor, direction) // not implemented yet
		if direction == MotorStop {
			state = idle
		} else {
			state = moving
		}
	default:
		// Insert error handling here - elevator might possibly need to be restarted ()
	}
}

func doorTimer(timeout chan<- bool, reset <-chan bool) {
	const doorOpenTime = 3 * time.Second
	timer := time.NewTimer(0)
	timer.Stop()

	for {
		select {
		case <-reset:
			timer.Reset(doorOpenTime)
		case <-timer.C:
			timer.Stop()
			timeout <- true
		}
	}
}

func stateIndicator() {
	prevState := idle
	for {
		if state != prevState {
			switch state {
			case idle:
				log.Println("STATE [eventManager]: idle")
			case moving:
				log.Println("STATE [eventManager]: moving")
			case doorOpen:
				log.Println("STATE [eventManager]: doorOpen")
			}
			prevState = state
		}
	}
}
<|endoftext|>


<commit_before>// package fsync keeps two files or directories in sync.
//
//         err := fsync.Sync("~/dst", ".")
//
// After the above code, if err is nil, every file and directory in the current
// directory is copied to ~/dst and has the same permissions. Consequent calls
// will only copy changed or new files.
//
// SyncTo is a helper function which helps you sync a groups of files or
// directories into a signle destination. For instance, calling
//
//     SyncTo("public", "build/app.js", "build/app.css", "images", "fonts")
//
// is equivalient to calling
//
//     Sync("public/app.js", "build/app.js")
//     Sync("public/app.css", "build/app.css")
//     Sync("public/images", "images")
//     Sync("public/fonts", "fonts")
//
// Actually, this is how SyncTo is implemented: consequent calls to Sync.
//
// By default, sync code ignores extra files in the destination that don’t have
// identicals in the source. Setting Delete field of a Syncer to true changes
// this behavior and deletes these extra files.
package fsync

import (
	"bytes"
	"errors"
	"io"
	"os"
	"path"
	"runtime"

	"github.com/spf13/afero"
)

var (
	ErrFileOverDir = errors.New(
		"fsync: trying to overwrite a non-empty directory with a file")
)

// Sync copies files and directories inside src into dst.
func Sync(dst, src string) error {
	return NewSyncer().Sync(dst, src)
}

// SyncTo syncs srcs files and directories into to directory.
func SyncTo(to string, srcs ...string) error {
	return NewSyncer().SyncTo(to, srcs...)
}

// Type Syncer provides functions for syncing files.
type Syncer struct {
	// Set this to true to delete files in the destination that don't exist
	// in the source.
	Delete bool
	// By default, modification times are synced. This can be turned off by
	// setting this to true.
	NoTimes bool
	// TODO add options for not checking content for equality

	SrcFs  afero.Fs
	DestFs afero.Fs
}

// NewSyncer creates a new instance of Syncer with default options.
func NewSyncer() *Syncer {
	return &Syncer{SrcFs: new(afero.OsFs), DestFs: new(afero.OsFs)}
}

// Sync copies files and directories inside src into dst.
func (s *Syncer) Sync(dst, src string) error {
	// make sure src exists
	if _, err := s.SrcFs.Stat(src); err != nil {
		return err
	}
	// return error instead of replacing a non-empty directory with a file
	if b, err := s.checkDir(dst, src); err != nil {
		return err
	} else if b {
		return ErrFileOverDir
	}

	return s.syncRecover(dst, src)
}

// SyncTo syncs srcs files or directories into to directory.
func (s *Syncer) SyncTo(to string, srcs ...string) error {
	for _, src := range srcs {
		dst := path.Join(to, path.Base(src))
		if err := s.Sync(dst, src); err != nil {
			return err
		}
	}
	return nil
}

// syncRecover handles errors and calls sync
func (s *Syncer) syncRecover(dst, src string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r)
			}
			err = r.(error)
		}
	}()

	s.sync(dst, src)
	return nil
}

// sync updates dst to match with src, handling both files and directories.
func (s *Syncer) sync(dst, src string) {
	// sync permissions and modification times after handling content
	defer s.syncstats(dst, src)

	// read files info
	dstat, err := s.SrcFs.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		panic(err)
	}
	sstat, err := s.SrcFs.Stat(src)
	if err != nil && os.IsNotExist(err) {
		return // src was deleted before we could copy it
	}
	check(err)

	if !sstat.IsDir() {
		// src is a file
		// delete dst if its a directory
		if dstat != nil && dstat.IsDir() {
			check(s.DestFs.RemoveAll(dst))
		}
		if !s.equal(dst, src) {
			// perform copy
			df, err := s.DestFs.Create(dst)
			check(err)
			defer df.Close()
			sf, err := s.SrcFs.Open(src)
			if os.IsNotExist(err) {
				return
			}
			check(err)
			defer sf.Close()
			_, err = io.Copy(df, sf)
			if os.IsNotExist(err) {
				return
			}
			check(err)
		}
		return
	}

	// src is a directory
	// make dst if necessary
	if dstat == nil {
		// dst does not exist; create directory
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	} else if !dstat.IsDir() {
		// dst is a file; remove and create directory
		check(s.DestFs.Remove(dst))
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	}

	// go through sf files and sync them
	files, err := afero.ReadDir(src, s.SrcFs)
	if os.IsNotExist(err) {
		return
	}
	check(err)
	// make a map of filenames for quick lookup; used in deletion
	// deletion below
	m := make(map[string]bool, len(files))
	for _, file := range files {
		dst2 := path.Join(dst, file.Name())
		src2 := path.Join(src, file.Name())
		s.sync(dst2, src2)
		m[file.Name()] = true
	}

	// delete files from dst that does not exist in src
	if s.Delete {
		files, err = afero.ReadDir(dst, s.DestFs)
		check(err)
		for _, file := range files {
			if !m[file.Name()] {
				check(s.DestFs.RemoveAll(path.Join(dst, file.Name())))
			}
		}
	}
}

// syncstats makes sure dst has the same pemissions and modification time as src
func (s *Syncer) syncstats(dst, src string) {
	// get file infos; return if not exist and panic if error
	dstat, err1 := s.DestFs.Stat(dst)
	sstat, err2 := s.SrcFs.Stat(src)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return
	}
	check(err1)
	check(err2)

	// update dst's permission bits
	if dstat.Mode().Perm() != sstat.Mode().Perm() {
		check(s.DestFs.Chmod(dst, sstat.Mode().Perm()))
		return
	}

	// update dst's modification time
	if !s.NoTimes {
		if !dstat.ModTime().Equal(sstat.ModTime()) {
			err := os.Chtimes(dst, sstat.ModTime(), sstat.ModTime())
			check(err)
		}
	}
}

// equal returns true if both files are equal
func (s *Syncer) equal(a, b string) bool {
	// get file infos
	info1, err1 := os.Stat(a)
	info2, err2 := os.Stat(b)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return false
	}
	check(err1)
	check(err2)

	// check sizes
	if info1.Size() != info2.Size() {
		return false
	}

	// both have the same size, check the contents
	f1, err := os.Open(a)
	check(err)
	defer f1.Close()
	f2, err := os.Open(b)
	check(err)
	defer f2.Close()
	buf1 := make([]byte, 1000)
	buf2 := make([]byte, 1000)
	for {
		// read from both
		n1, err := f1.Read(buf1)
		if err != nil && err != io.EOF {
			panic(err)
		}
		n2, err := f2.Read(buf2)
		if err != nil && err != io.EOF {
			panic(err)
		}

		// compare read bytes
		if !bytes.Equal(buf1[:n1], buf2[:n2]) {
			return false
		}

		// end of both files
		if n1 == 0 && n2 == 0 {
			break
		}
	}

	return true
}

// checkDir returns true if dst is a non-empty directory and src is a file
func (s *Syncer) checkDir(dst, src string) (b bool, err error) {
	// read file info
	dstat, err := os.Stat(dst)
	if os.IsNotExist(err) {
		return false, nil
	} else if err != nil {
		return false, err
	}
	sstat, err := os.Stat(src)
	if err != nil {
		return false, err
	}

	// return false is dst is not a directory or src is a directory
	if !dstat.IsDir() || sstat.IsDir() {
		return false, nil
	}

	// dst is a directory and src is a file
	// check if dst is non-empty
	// read dst directory

	files, err := afero.ReadDir(dst, s.DestFs)
	if err != nil {
		return false, err
	}
	if len(files) > 0 {
		return true, nil
	}
	return false, nil
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}
<commit_msg>Allow any afero-compliant FS to be used as dest<commit_after>// package fsync keeps two files or directories in sync.
//
//         err := fsync.Sync("~/dst", ".")
//
// After the above code, if err is nil, every file and directory in the current
// directory is copied to ~/dst and has the same permissions. Consequent calls
// will only copy changed or new files.
//
// SyncTo is a helper function which helps you sync a groups of files or
// directories into a signle destination. For instance, calling
//
//     SyncTo("public", "build/app.js", "build/app.css", "images", "fonts")
//
// is equivalient to calling
//
//     Sync("public/app.js", "build/app.js")
//     Sync("public/app.css", "build/app.css")
//     Sync("public/images", "images")
//     Sync("public/fonts", "fonts")
//
// Actually, this is how SyncTo is implemented: consequent calls to Sync.
//
// By default, sync code ignores extra files in the destination that don’t have
// identicals in the source. Setting Delete field of a Syncer to true changes
// this behavior and deletes these extra files.
package fsync

import (
	"bytes"
	"errors"
	"io"
	"os"
	"path"
	"runtime"

	"github.com/spf13/afero"
)

var (
	ErrFileOverDir = errors.New(
		"fsync: trying to overwrite a non-empty directory with a file")
)

// Sync copies files and directories inside src into dst.
func Sync(dst, src string) error {
	return NewSyncer().Sync(dst, src)
}

// SyncTo syncs srcs files and directories into to directory.
func SyncTo(to string, srcs ...string) error {
	return NewSyncer().SyncTo(to, srcs...)
}

// Type Syncer provides functions for syncing files.
type Syncer struct {
	// Set this to true to delete files in the destination that don't exist
	// in the source.
	Delete bool
	// By default, modification times are synced. This can be turned off by
	// setting this to true.
	NoTimes bool
	// TODO add options for not checking content for equality

	SrcFs  afero.Fs
	DestFs afero.Fs
}

// NewSyncer creates a new instance of Syncer with default options.
func NewSyncer() *Syncer {
	return &Syncer{SrcFs: new(afero.OsFs), DestFs: new(afero.OsFs)}
}

// Sync copies files and directories inside src into dst.
func (s *Syncer) Sync(dst, src string) error {
	// make sure src exists
	if _, err := s.SrcFs.Stat(src); err != nil {
		return err
	}
	// return error instead of replacing a non-empty directory with a file
	if b, err := s.checkDir(dst, src); err != nil {
		return err
	} else if b {
		return ErrFileOverDir
	}

	return s.syncRecover(dst, src)
}

// SyncTo syncs srcs files or directories into to directory.
func (s *Syncer) SyncTo(to string, srcs ...string) error {
	for _, src := range srcs {
		dst := path.Join(to, path.Base(src))
		if err := s.Sync(dst, src); err != nil {
			return err
		}
	}
	return nil
}

// syncRecover handles errors and calls sync
func (s *Syncer) syncRecover(dst, src string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r)
			}
			err = r.(error)
		}
	}()

	s.sync(dst, src)
	return nil
}

// sync updates dst to match with src, handling both files and directories.
func (s *Syncer) sync(dst, src string) {
	// sync permissions and modification times after handling content
	defer s.syncstats(dst, src)

	// read files info
	dstat, err := s.SrcFs.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		panic(err)
	}
	sstat, err := s.SrcFs.Stat(src)
	if err != nil && os.IsNotExist(err) {
		return // src was deleted before we could copy it
	}
	check(err)

	if !sstat.IsDir() {
		// src is a file
		// delete dst if its a directory
		if dstat != nil && dstat.IsDir() {
			check(s.DestFs.RemoveAll(dst))
		}
		if !s.equal(dst, src) {
			// perform copy
			df, err := s.DestFs.Create(dst)
			check(err)
			defer df.Close()
			sf, err := s.SrcFs.Open(src)
			if os.IsNotExist(err) {
				return
			}
			check(err)
			defer sf.Close()
			_, err = io.Copy(df, sf)
			if os.IsNotExist(err) {
				return
			}
			check(err)
		}
		return
	}

	// src is a directory
	// make dst if necessary
	if dstat == nil {
		// dst does not exist; create directory
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	} else if !dstat.IsDir() {
		// dst is a file; remove and create directory
		check(s.DestFs.Remove(dst))
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	}

	// go through sf files and sync them
	files, err := afero.ReadDir(src, s.SrcFs)
	if os.IsNotExist(err) {
		return
	}
	check(err)
	// make a map of filenames for quick lookup; used in deletion
	// deletion below
	m := make(map[string]bool, len(files))
	for _, file := range files {
		dst2 := path.Join(dst, file.Name())
		src2 := path.Join(src, file.Name())
		s.sync(dst2, src2)
		m[file.Name()] = true
	}

	// delete files from dst that does not exist in src
	if s.Delete {
		files, err = afero.ReadDir(dst, s.DestFs)
		check(err)
		for _, file := range files {
			if !m[file.Name()] {
				check(s.DestFs.RemoveAll(path.Join(dst, file.Name())))
			}
		}
	}
}

// syncstats makes sure dst has the same pemissions and modification time as src
func (s *Syncer) syncstats(dst, src string) {
	// get file infos; return if not exist and panic if error
	dstat, err1 := s.DestFs.Stat(dst)
	sstat, err2 := s.SrcFs.Stat(src)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return
	}
	check(err1)
	check(err2)

	// update dst's permission bits
	if dstat.Mode().Perm() != sstat.Mode().Perm() {
		check(s.DestFs.Chmod(dst, sstat.Mode().Perm()))
		return
	}

	// update dst's modification time
	if !s.NoTimes {
		if !dstat.ModTime().Equal(sstat.ModTime()) {
			err := s.DestFs.Chtimes(dst, sstat.ModTime(), sstat.ModTime())
			check(err)
		}
	}
}

// equal returns true if both files are equal
func (s *Syncer) equal(a, b string) bool {
	// get file infos
	info1, err1 := os.Stat(a)
	info2, err2 := os.Stat(b)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return false
	}
	check(err1)
	check(err2)

	// check sizes
	if info1.Size() != info2.Size() {
		return false
	}

	// both have the same size, check the contents
	f1, err := os.Open(a)
	check(err)
	defer f1.Close()
	f2, err := os.Open(b)
	check(err)
	defer f2.Close()
	buf1 := make([]byte, 1000)
	buf2 := make([]byte, 1000)
	for {
		// read from both
		n1, err := f1.Read(buf1)
		if err != nil && err != io.EOF {
			panic(err)
		}
		n2, err := f2.Read(buf2)
		if err != nil && err != io.EOF {
			panic(err)
		}

		// compare read bytes
		if !bytes.Equal(buf1[:n1], buf2[:n2]) {
			return false
		}

		// end of both files
		if n1 == 0 && n2 == 0 {
			break
		}
	}

	return true
}

// checkDir returns true if dst is a non-empty directory and src is a file
func (s *Syncer) checkDir(dst, src string) (b bool, err error) {
	// read file info
	dstat, err := os.Stat(dst)
	if os.IsNotExist(err) {
		return false, nil
	} else if err != nil {
		return false, err
	}
	sstat, err := os.Stat(src)
	if err != nil {
		return false, err
	}

	// return false is dst is not a directory or src is a directory
	if !dstat.IsDir() || sstat.IsDir() {
		return false, nil
	}

	// dst is a directory and src is a file
	// check if dst is non-empty
	// read dst directory

	files, err := afero.ReadDir(dst, s.DestFs)
	if err != nil {
		return false, err
	}
	if len(files) > 0 {
		return true, nil
	}
	return false, nil
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package emailUtils

import (
	"crypto/tls"
	"net"
	"net/smtp"
	"time"

	. "github.com/francoishill/goangi2/utils/loggingUtils"
)

const (
	EMAIL_PROVIDER_TYPE__DEFAULT  = 1 << iota
	EMAIL_PROVIDER_TYPE__SENDGRID = 1 << iota
)

var DefaultEmailContext *EmailContext

func CreateEmailContext_DefaultEmailProvider(logger ILogger, authUsername, authPassword, mailHostAndPort string, queueSendingIntervalMinutes int, doNotReplyFrom, adminRecipient, supportRecipient *EmailRecipient) *EmailContext {
	return &EmailContext{
		Logger:                      logger,
		emailProviderType:           EMAIL_PROVIDER_TYPE__DEFAULT,
		doNotReplyFrom:              doNotReplyFrom,
		adminRecipient:              adminRecipient,
		supportRecipient:            supportRecipient,
		authUsername:                authUsername,
		authPassword:                authPassword,
		mailHostAndPort:             mailHostAndPort,
		queueSendingIntervalMinutes: queueSendingIntervalMinutes,
		sendgridApiUser:             "", //Not used for Default email provider
		sendgridApiKey:              "", //Not used for Default email provider
		sendgridTemplateId:          "", //Not used for Default email provider
	}
}

func CreateEmailContext_SendGridProvider(logger ILogger, sendgridApiUser, sendgridApiKey, sendgridTemplateId string, doNotReplyFrom, adminRecipient, supportRecipient *EmailRecipient) *EmailContext {
	return &EmailContext{
		Logger:                      logger,
		emailProviderType:           EMAIL_PROVIDER_TYPE__SENDGRID,
		doNotReplyFrom:              doNotReplyFrom,
		adminRecipient:              adminRecipient,
		supportRecipient:            supportRecipient,
		authUsername:                "", //Not used for SendGrid email provider
		authPassword:                "", //Not used for SendGrid email provider
		mailHostAndPort:             "", //Not used for SendGrid email provider
		queueSendingIntervalMinutes: 0,  //Not used for SendGrid email provider
		sendgridApiUser:             sendgridApiUser,
		sendgridApiKey:              sendgridApiKey,
		sendgridTemplateId:          sendgridTemplateId,
	}
}

type EmailContext struct {
	Logger ILogger

	emailProviderType int

	doNotReplyFrom   *EmailRecipient //A do-not-reply email which is used as the from email of system mail, like Register, Forgot Password, etc
	adminRecipient   *EmailRecipient //Typically the person receiving general Errors and Notices
	supportRecipient *EmailRecipient //Typically the person responsible to respond to payment/transactions issues and failures

	authUsername                string
	authPassword                string
	mailHostAndPort             string
	queueSendingIntervalMinutes int

	sendgridApiUser    string
	sendgridApiKey     string
	sendgridTemplateId string
}

func (this *EmailContext) getSmtpPlainAuth() smtp.Auth {
	host, _, err := net.SplitHostPort(this.mailHostAndPort)
	if err != nil {
		panic(err)
	}
	var auth smtp.Auth
	if len(this.authUsername) > 0 || len(this.authPassword) > 0 {
		auth = smtp.PlainAuth("", this.authUsername, this.authPassword, host)
	} else {
		auth = nil
	}
	return auth
}

func (this *EmailContext) getAuthorizedSmtpClient() *smtp.Client {
	client, err := smtp.Dial(this.mailHostAndPort)
	checkError(err)

	host, _, _ := net.SplitHostPort(this.mailHostAndPort)
	tlsConn := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         host,
	}

	err = client.StartTLS(tlsConn)
	checkError(err)

	auth := this.getSmtpPlainAuth()
	if auth != nil {
		err = client.Auth(auth)
		checkError(err)
	}

	return client
}

func (this *EmailContext) GetDoNotReplyFrom() *EmailRecipient {
	return this.doNotReplyFrom
}

func (this *EmailContext) GetQueueSendingInterval() time.Duration {
	return time.Minute * time.Duration(this.queueSendingIntervalMinutes)
}

func (this *EmailContext) IsSendGridEmailProvider() bool {
	return this.emailProviderType == EMAIL_PROVIDER_TYPE__SENDGRID
}
<commit_msg>Added `GetAdminRecipient` and `GetSupportRecipient` to EmailContext.<commit_after>package emailUtils

import (
	"crypto/tls"
	"net"
	"net/smtp"
	"time"

	. "github.com/francoishill/goangi2/utils/loggingUtils"
)

const (
	EMAIL_PROVIDER_TYPE__DEFAULT  = 1 << iota
	EMAIL_PROVIDER_TYPE__SENDGRID = 1 << iota
)

var DefaultEmailContext *EmailContext

func CreateEmailContext_DefaultEmailProvider(logger ILogger, authUsername, authPassword, mailHostAndPort string, queueSendingIntervalMinutes int, doNotReplyFrom, adminRecipient, supportRecipient *EmailRecipient) *EmailContext {
	return &EmailContext{
		Logger:                      logger,
		emailProviderType:           EMAIL_PROVIDER_TYPE__DEFAULT,
		doNotReplyFrom:              doNotReplyFrom,
		adminRecipient:              adminRecipient,
		supportRecipient:            supportRecipient,
		authUsername:                authUsername,
		authPassword:                authPassword,
		mailHostAndPort:             mailHostAndPort,
		queueSendingIntervalMinutes: queueSendingIntervalMinutes,
		sendgridApiUser:             "", //Not used for Default email provider
		sendgridApiKey:              "", //Not used for Default email provider
		sendgridTemplateId:          "", //Not used for Default email provider
	}
}

func CreateEmailContext_SendGridProvider(logger ILogger, sendgridApiUser, sendgridApiKey, sendgridTemplateId string, doNotReplyFrom, adminRecipient, supportRecipient *EmailRecipient) *EmailContext {
	return &EmailContext{
		Logger:                      logger,
		emailProviderType:           EMAIL_PROVIDER_TYPE__SENDGRID,
		doNotReplyFrom:              doNotReplyFrom,
		adminRecipient:              adminRecipient,
		supportRecipient:            supportRecipient,
		authUsername:                "", //Not used for SendGrid email provider
		authPassword:                "", //Not used for SendGrid email provider
		mailHostAndPort:             "", //Not used for SendGrid email provider
		queueSendingIntervalMinutes: 0,  //Not used for SendGrid email provider
		sendgridApiUser:             sendgridApiUser,
		sendgridApiKey:              sendgridApiKey,
		sendgridTemplateId:          sendgridTemplateId,
	}
}

type EmailContext struct {
	Logger ILogger

	emailProviderType int

	doNotReplyFrom   *EmailRecipient //A do-not-reply email which is used as the from email of system mail, like Register, Forgot Password, etc
	adminRecipient   *EmailRecipient //Typically the person receiving general Errors and Notices
	supportRecipient *EmailRecipient //Typically the person responsible to respond to payment/transactions issues and failures

	authUsername                string
	authPassword                string
	mailHostAndPort             string
	queueSendingIntervalMinutes int

	sendgridApiUser    string
	sendgridApiKey     string
	sendgridTemplateId string
}

func (this *EmailContext) getSmtpPlainAuth() smtp.Auth {
	host, _, err := net.SplitHostPort(this.mailHostAndPort)
	if err != nil {
		panic(err)
	}
	var auth smtp.Auth
	if len(this.authUsername) > 0 || len(this.authPassword) > 0 {
		auth = smtp.PlainAuth("", this.authUsername, this.authPassword, host)
	} else {
		auth = nil
	}
	return auth
}

func (this *EmailContext) getAuthorizedSmtpClient() *smtp.Client {
	client, err := smtp.Dial(this.mailHostAndPort)
	checkError(err)

	host, _, _ := net.SplitHostPort(this.mailHostAndPort)
	tlsConn := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         host,
	}

	err = client.StartTLS(tlsConn)
	checkError(err)

	auth := this.getSmtpPlainAuth()
	if auth != nil {
		err = client.Auth(auth)
		checkError(err)
	}

	return client
}

func (this *EmailContext) GetDoNotReplyFrom() *EmailRecipient {
	return this.doNotReplyFrom
}

func (this *EmailContext) GetAdminRecipient() *EmailRecipient {
	return this.adminRecipient
}

func (this *EmailContext) GetSupportRecipient() *EmailRecipient {
	return this.supportRecipient
}

func (this *EmailContext) GetQueueSendingInterval() time.Duration {
	return time.Minute * time.Duration(this.queueSendingIntervalMinutes)
}

func (this *EmailContext) IsSendGridEmailProvider() bool {
	return this.emailProviderType == EMAIL_PROVIDER_TYPE__SENDGRID
}
<|endoftext|>


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

package setup

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/sacloud/libsacloud/v2/sacloud"
	"github.com/sacloud/libsacloud/v2/sacloud/accessor"
	"github.com/sacloud/libsacloud/v2/sacloud/types"
)

// MaxRetryCountExceededError リトライ最大数超過エラー
type MaxRetryCountExceededError error

var (
	// DefaultMaxRetryCount デフォルトリトライ最大数
	DefaultMaxRetryCount = 3
	// DefaultProvisioningRetryCount リソースごとのプロビジョニングAPI呼び出しのリトライ最大数
	DefaultProvisioningRetryCount = 10

	// DefaultProvisioningWaitInterval リソースごとのプロビジョニングAPI呼び出しのリトライ間隔
	DefaultProvisioningWaitInterval = 5 * time.Second

	// DefaultDeleteRetryCount リソースごとの削除API呼び出しのリトライ最大数
	DefaultDeleteRetryCount = 10

	// DefaultDeleteWaitInterval リソースごとの削除API呼び出しのリトライ間隔
	DefaultDeleteWaitInterval = 10 * time.Second

	// DefaultPollingInterval ポーリング処理の間隔
	DefaultPollingInterval = 5 * time.Second
)

// CreateFunc リソース作成関数
type CreateFunc func(ctx context.Context, zone string) (accessor.ID, error)

// ProvisionBeforeUpFunc リソース作成後、起動前のプロビジョニング関数
//
// リソース作成後に起動が行われないリソース(VPCルータなど)向け。
// 必要であればこの中でリソース起動処理を行う。
type ProvisionBeforeUpFunc func(ctx context.Context, zone string, id types.ID, target interface{}) error

// DeleteFunc リソース削除関数。
//
// リソース作成時のコピー待ちの間にリソースのAvailabilityがFailedになった場合に利用される。
type DeleteFunc func(ctx context.Context, zone string, id types.ID) error

// ReadFunc リソース起動待ちなどで利用するリソースのRead用Func
type ReadFunc func(ctx context.Context, zone string, id types.ID) (interface{}, error)

// RetryableSetup リソース作成時にコピー待ちや起動待ちが必要なリソースのビルダー。
//
// リソースのビルドの際、必要に応じてリトライ(リソースの削除&再作成)を行う。
type RetryableSetup struct {
	// Create リソース作成用関数
	Create CreateFunc
	// IsWaitForCopy コピー待ちを行うか
	IsWaitForCopy bool
	// IsWaitForUp 起動待ちを行うか
	IsWaitForUp bool
	// ProvisionBeforeUp リソース起動前のプロビジョニング関数
	ProvisionBeforeUp ProvisionBeforeUpFunc
	// Delete リソース削除用関数
	Delete DeleteFunc
	// WaitForUp リソース起動待ち関数
	Read ReadFunc
	// RetryCount リトライ回数
	RetryCount int
	// ProvisioningRetryCount プロビジョニングリトライ回数
	ProvisioningRetryCount int
	// ProvisioningRetryInterval プロビジョニングリトライ間隔
	ProvisioningRetryInterval time.Duration
	// DeleteRetryCount 削除リトライ回数
	DeleteRetryCount int
	// DeleteRetryInterval 削除リトライ間隔
	DeleteRetryInterval time.Duration
	// sacloud.StateWaiterによるステート待ちの間隔
	PollingInterval time.Duration
}

// Setup リソースのビルドを行う。必要に応じてリトライ(リソースの削除&再作成)を行う。
func (r *RetryableSetup) Setup(ctx context.Context, zone string) (interface{}, error) {
	if (r.IsWaitForCopy || r.IsWaitForUp) && r.Read == nil {
		return nil, errors.New("failed: Read is required when IsWaitForCopy or IsWaitForUp is true")
	}

	r.init()

	var created interface{}
	for r.RetryCount+1 > 0 {
		r.RetryCount--

		// リソース作成
		target, err := r.createResource(ctx, zone)
		if err != nil {
			return nil, err
		}
		id := target.GetID()

		// コピー待ち
		if r.IsWaitForCopy {
			// コピー待ち、Failedになった場合はリソース削除
			state, err := r.waitForCopyWithCleanup(ctx, zone, id)
			if err != nil {
				return nil, err
			}
			if state != nil {
				created = state
			}
		} else {
			created = target
		}

		// 起動前の設定など
		if err := r.provisionBeforeUp(ctx, zone, id, created); err != nil {
			return nil, err
		}

		// 起動待ち
		if err := r.waitForUp(ctx, zone, id, created); err != nil {
			return nil, err
		}

		if created != nil {
			break
		}
	}

	if created == nil {
		return nil, MaxRetryCountExceededError(fmt.Errorf("max retry count exceeded"))
	}
	return created, nil
}

func (r *RetryableSetup) init() {
	if r.RetryCount <= 0 {
		r.RetryCount = DefaultMaxRetryCount
	}
	if r.DeleteRetryCount <= 0 {
		r.DeleteRetryCount = DefaultDeleteRetryCount
	}
	if r.DeleteRetryInterval <= 0 {
		r.DeleteRetryInterval = DefaultDeleteWaitInterval
	}
	if r.ProvisioningRetryCount <= 0 {
		r.ProvisioningRetryCount = DefaultProvisioningRetryCount
	}
	if r.ProvisioningRetryInterval <= 0 {
		r.ProvisioningRetryInterval = DefaultProvisioningWaitInterval
	}
	if r.PollingInterval <= 0 {
		r.PollingInterval = DefaultPollingInterval
	}
}

func (r *RetryableSetup) createResource(ctx context.Context, zone string) (accessor.ID, error) {
	if r.Create == nil {
		return nil, fmt.Errorf("create func is required")
	}
	return r.Create(ctx, zone)
}

func (r *RetryableSetup) waitForCopyWithCleanup(ctx context.Context, zone string, id types.ID) (interface{}, error) {
	waiter := &sacloud.StatePollingWaiter{
		ReadFunc: func() (interface{}, error) {
			return r.Read(ctx, zone, id)
		},
		TargetAvailability: []types.EAvailability{
			types.Availabilities.Available,
			types.Availabilities.Failed,
		},
		PendingAvailability: []types.EAvailability{
			types.Availabilities.Unknown,
			types.Availabilities.Migrating,
			types.Availabilities.Uploading,
			types.Availabilities.Transferring,
			types.Availabilities.Discontinued,
		},
		PollingInterval: r.PollingInterval,
	}

	//wait
	compChan, progChan, errChan := waiter.AsyncWaitForState(ctx)
	var state interface{}
	var err error

loop:
	for {
		select {
		case v := <-compChan:
			state = v
			break loop
		case v := <-progChan:
			state = v
		case e := <-errChan:
			err = e
			break loop
		}
	}

	if state != nil {
		// Availabilityを持ち、Failedになっていた場合はリソースを削除してリトライ
		if f, ok := state.(accessor.Availability); ok && f != nil {
			if f.GetAvailability().IsFailed() {
				// FailedになったばかりだとDelete APIが失敗する(コピー進行中など)場合があるため、
				// 任意の回数リトライ&待機を行う
				for i := 0; i < r.DeleteRetryCount; i++ {
					time.Sleep(r.DeleteRetryInterval)
					if err = r.Delete(ctx, zone, id); err == nil {
						break
					}
				}

				return nil, nil
			}
		}

		return state, nil
	}
	if err != nil {
		return nil, err
	}

	return nil, nil
}

func (r *RetryableSetup) provisionBeforeUp(ctx context.Context, zone string, id types.ID, created interface{}) error {
	if r.ProvisionBeforeUp != nil && created != nil {
		var err error
		for i := 0; i < r.ProvisioningRetryCount; i++ {
			time.Sleep(r.ProvisioningRetryInterval)
			if err = r.ProvisionBeforeUp(ctx, zone, id, created); err == nil {
				break
			}
		}
		return err
	}
	return nil
}

func (r *RetryableSetup) waitForUp(ctx context.Context, zone string, id types.ID, created interface{}) error {
	if r.IsWaitForUp && created != nil {
		waiter := &sacloud.StatePollingWaiter{
			ReadFunc: func() (interface{}, error) {
				return r.Read(ctx, zone, id)
			},
			TargetAvailability: []types.EAvailability{
				types.Availabilities.Available,
			},
			PendingAvailability: []types.EAvailability{
				types.Availabilities.Unknown,
				types.Availabilities.Migrating,
				types.Availabilities.Uploading,
				types.Availabilities.Transferring,
				types.Availabilities.Discontinued,
			},
			TargetInstanceStatus: []types.EServerInstanceStatus{
				types.ServerInstanceStatuses.Up,
			},
			PendingInstanceStatus: []types.EServerInstanceStatus{
				types.ServerInstanceStatuses.Unknown,
				types.ServerInstanceStatuses.Cleaning,
				types.ServerInstanceStatuses.Down,
			},
			PollingInterval: r.PollingInterval,
		}
		_, err := waiter.WaitForState(ctx)
		return err
	}
	return nil
}
<commit_msg>Fix sleep timing<commit_after>// Copyright 2016-2020 The Libsacloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package setup

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/sacloud/libsacloud/v2/sacloud"
	"github.com/sacloud/libsacloud/v2/sacloud/accessor"
	"github.com/sacloud/libsacloud/v2/sacloud/types"
)

// MaxRetryCountExceededError リトライ最大数超過エラー
type MaxRetryCountExceededError error

var (
	// DefaultMaxRetryCount デフォルトリトライ最大数
	DefaultMaxRetryCount = 3
	// DefaultProvisioningRetryCount リソースごとのプロビジョニングAPI呼び出しのリトライ最大数
	DefaultProvisioningRetryCount = 10

	// DefaultProvisioningWaitInterval リソースごとのプロビジョニングAPI呼び出しのリトライ間隔
	DefaultProvisioningWaitInterval = 5 * time.Second

	// DefaultDeleteRetryCount リソースごとの削除API呼び出しのリトライ最大数
	DefaultDeleteRetryCount = 10

	// DefaultDeleteWaitInterval リソースごとの削除API呼び出しのリトライ間隔
	DefaultDeleteWaitInterval = 10 * time.Second

	// DefaultPollingInterval ポーリング処理の間隔
	DefaultPollingInterval = 5 * time.Second
)

// CreateFunc リソース作成関数
type CreateFunc func(ctx context.Context, zone string) (accessor.ID, error)

// ProvisionBeforeUpFunc リソース作成後、起動前のプロビジョニング関数
//
// リソース作成後に起動が行われないリソース(VPCルータなど)向け。
// 必要であればこの中でリソース起動処理を行う。
type ProvisionBeforeUpFunc func(ctx context.Context, zone string, id types.ID, target interface{}) error

// DeleteFunc リソース削除関数。
//
// リソース作成時のコピー待ちの間にリソースのAvailabilityがFailedになった場合に利用される。
type DeleteFunc func(ctx context.Context, zone string, id types.ID) error

// ReadFunc リソース起動待ちなどで利用するリソースのRead用Func
type ReadFunc func(ctx context.Context, zone string, id types.ID) (interface{}, error)

// RetryableSetup リソース作成時にコピー待ちや起動待ちが必要なリソースのビルダー。
//
// リソースのビルドの際、必要に応じてリトライ(リソースの削除&再作成)を行う。
type RetryableSetup struct {
	// Create リソース作成用関数
	Create CreateFunc
	// IsWaitForCopy コピー待ちを行うか
	IsWaitForCopy bool
	// IsWaitForUp 起動待ちを行うか
	IsWaitForUp bool
	// ProvisionBeforeUp リソース起動前のプロビジョニング関数
	ProvisionBeforeUp ProvisionBeforeUpFunc
	// Delete リソース削除用関数
	Delete DeleteFunc
	// WaitForUp リソース起動待ち関数
	Read ReadFunc
	// RetryCount リトライ回数
	RetryCount int
	// ProvisioningRetryCount プロビジョニングリトライ回数
	ProvisioningRetryCount int
	// ProvisioningRetryInterval プロビジョニングリトライ間隔
	ProvisioningRetryInterval time.Duration
	// DeleteRetryCount 削除リトライ回数
	DeleteRetryCount int
	// DeleteRetryInterval 削除リトライ間隔
	DeleteRetryInterval time.Duration
	// sacloud.StateWaiterによるステート待ちの間隔
	PollingInterval time.Duration
}

// Setup リソースのビルドを行う。必要に応じてリトライ(リソースの削除&再作成)を行う。
func (r *RetryableSetup) Setup(ctx context.Context, zone string) (interface{}, error) {
	if (r.IsWaitForCopy || r.IsWaitForUp) && r.Read == nil {
		return nil, errors.New("failed: Read is required when IsWaitForCopy or IsWaitForUp is true")
	}

	r.init()

	var created interface{}
	for r.RetryCount+1 > 0 {
		r.RetryCount--

		// リソース作成
		target, err := r.createResource(ctx, zone)
		if err != nil {
			return nil, err
		}
		id := target.GetID()

		// コピー待ち
		if r.IsWaitForCopy {
			// コピー待ち、Failedになった場合はリソース削除
			state, err := r.waitForCopyWithCleanup(ctx, zone, id)
			if err != nil {
				return nil, err
			}
			if state != nil {
				created = state
			}
		} else {
			created = target
		}

		// 起動前の設定など
		if err := r.provisionBeforeUp(ctx, zone, id, created); err != nil {
			return nil, err
		}

		// 起動待ち
		if err := r.waitForUp(ctx, zone, id, created); err != nil {
			return nil, err
		}

		if created != nil {
			break
		}
	}

	if created == nil {
		return nil, MaxRetryCountExceededError(fmt.Errorf("max retry count exceeded"))
	}
	return created, nil
}

func (r *RetryableSetup) init() {
	if r.RetryCount <= 0 {
		r.RetryCount = DefaultMaxRetryCount
	}
	if r.DeleteRetryCount <= 0 {
		r.DeleteRetryCount = DefaultDeleteRetryCount
	}
	if r.DeleteRetryInterval <= 0 {
		r.DeleteRetryInterval = DefaultDeleteWaitInterval
	}
	if r.ProvisioningRetryCount <= 0 {
		r.ProvisioningRetryCount = DefaultProvisioningRetryCount
	}
	if r.ProvisioningRetryInterval <= 0 {
		r.ProvisioningRetryInterval = DefaultProvisioningWaitInterval
	}
	if r.PollingInterval <= 0 {
		r.PollingInterval = DefaultPollingInterval
	}
}

func (r *RetryableSetup) createResource(ctx context.Context, zone string) (accessor.ID, error) {
	if r.Create == nil {
		return nil, fmt.Errorf("create func is required")
	}
	return r.Create(ctx, zone)
}

func (r *RetryableSetup) waitForCopyWithCleanup(ctx context.Context, zone string, id types.ID) (interface{}, error) {
	waiter := &sacloud.StatePollingWaiter{
		ReadFunc: func() (interface{}, error) {
			return r.Read(ctx, zone, id)
		},
		TargetAvailability: []types.EAvailability{
			types.Availabilities.Available,
			types.Availabilities.Failed,
		},
		PendingAvailability: []types.EAvailability{
			types.Availabilities.Unknown,
			types.Availabilities.Migrating,
			types.Availabilities.Uploading,
			types.Availabilities.Transferring,
			types.Availabilities.Discontinued,
		},
		PollingInterval: r.PollingInterval,
	}

	//wait
	compChan, progChan, errChan := waiter.AsyncWaitForState(ctx)
	var state interface{}
	var err error

loop:
	for {
		select {
		case v := <-compChan:
			state = v
			break loop
		case v := <-progChan:
			state = v
		case e := <-errChan:
			err = e
			break loop
		}
	}

	if state != nil {
		// Availabilityを持ち、Failedになっていた場合はリソースを削除してリトライ
		if f, ok := state.(accessor.Availability); ok && f != nil {
			if f.GetAvailability().IsFailed() {
				// FailedになったばかりだとDelete APIが失敗する(コピー進行中など)場合があるため、
				// 任意の回数リトライ&待機を行う
				for i := 0; i < r.DeleteRetryCount; i++ {
					time.Sleep(r.DeleteRetryInterval)
					if err = r.Delete(ctx, zone, id); err == nil {
						break
					}
				}

				return nil, nil
			}
		}

		return state, nil
	}
	if err != nil {
		return nil, err
	}

	return nil, nil
}

func (r *RetryableSetup) provisionBeforeUp(ctx context.Context, zone string, id types.ID, created interface{}) error {
	if r.ProvisionBeforeUp != nil && created != nil {
		var err error
		for i := 0; i < r.ProvisioningRetryCount; i++ {
			if err = r.ProvisionBeforeUp(ctx, zone, id, created); err == nil {
				break
			}
			time.Sleep(r.ProvisioningRetryInterval)
		}
		return err
	}
	return nil
}

func (r *RetryableSetup) waitForUp(ctx context.Context, zone string, id types.ID, created interface{}) error {
	if r.IsWaitForUp && created != nil {
		waiter := &sacloud.StatePollingWaiter{
			ReadFunc: func() (interface{}, error) {
				return r.Read(ctx, zone, id)
			},
			TargetAvailability: []types.EAvailability{
				types.Availabilities.Available,
			},
			PendingAvailability: []types.EAvailability{
				types.Availabilities.Unknown,
				types.Availabilities.Migrating,
				types.Availabilities.Uploading,
				types.Availabilities.Transferring,
				types.Availabilities.Discontinued,
			},
			TargetInstanceStatus: []types.EServerInstanceStatus{
				types.ServerInstanceStatuses.Up,
			},
			PendingInstanceStatus: []types.EServerInstanceStatus{
				types.ServerInstanceStatuses.Unknown,
				types.ServerInstanceStatuses.Cleaning,
				types.ServerInstanceStatuses.Down,
			},
			PollingInterval: r.PollingInterval,
		}
		_, err := waiter.WaitForState(ctx)
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/dynport/dgtk/cli"
	"strings"
)

type BaseEntity struct {
	Af string `cli:"type=opt short=a desc='something set in the base entity'"`
	Bf int    `cli:"type=opt short=b desc='something set in the base entity'"`
}

type ShowEntity struct {
	BaseEntity
	Verbose bool     `cli:"type=opt short=v long=verbose default=true desc='make command more verbose'"`
	Option  string   `cli:"type=opt short=o long=option required=true desc='some option'"`
	Ip      string   `cli:"type=opt short=i default='127.0.0.1' desc='another option'"`
	Name    string   `cli:"type=arg required=true desc='the first argument'"`
	Others  []string `cli:"type=arg desc='some variadic argument'"`
	w       string
	x       string
}

func (se *ShowEntity) Run() error {
	log.Printf("oha, running show entity, with:")
	log.Printf("\tverbose: %t", se.Verbose)
	log.Printf("\toption:  %s", se.Option)
	log.Printf("\tip:      %s", se.Ip)
	log.Printf("\tname:    %s", se.Name)
	log.Printf("\tothers   %s", strings.Join(se.Others, ", "))
	log.Printf("\tA        %s", se.Af)
	log.Printf("\tB        %d", se.Bf)
	log.Printf("\tw        %s", se.w)
	return nil
}

func main() {
	router := cli.NewRouter()

	router.Register("entities/show", &ShowEntity{w: "foog"}, "an example action")
	router.Register("entities/list", &ShowEntity{w: "foog"}, "an example action")
	router.Register("entit/list", &ShowEntity{w: "foog", Option: "barz"}, "an example action")
	router.RegisterFunc("version", func() error { log.Printf("x.y"); return nil }, "show the apps version")

	if e := router.RunWithArgs(); e != nil {
		log.Fatal(e)
	}
}
<commit_msg>fixed cli example imports (want log in here)<commit_after>package main

import (
	"github.com/dynport/dgtk/cli"
	"log"
	"strings"
)

type BaseEntity struct {
	Af string `cli:"type=opt short=a desc='something set in the base entity'"`
	Bf int    `cli:"type=opt short=b desc='something set in the base entity'"`
}

type ShowEntity struct {
	BaseEntity
	Verbose bool     `cli:"type=opt short=v long=verbose default=true desc='make command more verbose'"`
	Option  string   `cli:"type=opt short=o long=option required=true desc='some option'"`
	Ip      string   `cli:"type=opt short=i default='127.0.0.1' desc='another option'"`
	Name    string   `cli:"type=arg required=true desc='the first argument'"`
	Others  []string `cli:"type=arg desc='some variadic argument'"`
	w       string
	x       string
}

func (se *ShowEntity) Run() error {
	log.Printf("oha, running show entity, with:")
	log.Printf("\tverbose: %t", se.Verbose)
	log.Printf("\toption:  %s", se.Option)
	log.Printf("\tip:      %s", se.Ip)
	log.Printf("\tname:    %s", se.Name)
	log.Printf("\tothers   %s", strings.Join(se.Others, ", "))
	log.Printf("\tA        %s", se.Af)
	log.Printf("\tB        %d", se.Bf)
	log.Printf("\tw        %s", se.w)
	return nil
}

func main() {
	router := cli.NewRouter()

	router.Register("entities/show", &ShowEntity{w: "foog"}, "an example action")
	router.Register("entities/list", &ShowEntity{w: "foog"}, "an example action")
	router.Register("entit/list", &ShowEntity{w: "foog", Option: "barz"}, "an example action")
	router.RegisterFunc("version", func() error { log.Printf("x.y"); return nil }, "show the apps version")

	if e := router.RunWithArgs(); e != nil {
		log.Fatal(e)
	}
}
<|endoftext|>