<commit_before>package http

import (
	"github.com/astaxie/beego/orm"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
	log "github.com/Sirupsen/logrus"
)

type Nqm_node struct {
	Id         int
	Nid        string
	Pid        string
	Pname      string
	Pname_abbr string
	Cid        string
	Cname      string
	Cname_abbr string
	Iid        int
	Iname      string
	Status     int
	Note       string
	Addtime    int
}

func getNQMNodes(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	var NQMNodes []*Nqm_node
	_, err := o.Raw("SELECT nid, pname, cname, status, note FROM `gz_nqm`.`nqm_node` ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, node := range NQMNodes {
			idc := map[string]string{
				"idc":      node.Nid,
				"status":   strconv.Itoa(node.Status),
				"province": node.Pname,
				"city":     node.Cname,
				"note":     node.Note,
			}
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	setResponse(rw, nodes)
}

func getLatestTimestamp(tableName string, result map[string]interface{}) int64 {
	timestamp := int64(0)
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT mtime FROM `gz_nqm`.`" + tableName + "` ORDER BY mtime DESC LIMIT 1"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		mtime, err := strconv.Atoi(rows[0]["mtime"].(string))
		if err == nil {
			timestamp = int64(mtime)
		}
	}
	return timestamp
}

func getNearestTimestamp(tableName string, bound int64, result map[string]interface{}) int64 {
	timestamp := int64(0)
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT mtime FROM `gz_nqm`.`" + tableName
	sqlcmd += "` WHERE mtime <= ? ORDER BY mtime DESC LIMIT 1"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd, bound).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		mtime, err := strconv.Atoi(rows[0]["mtime"].(string))
		if err == nil {
			timestamp = int64(mtime)
		}
	}
	return timestamp
}

func getSum(slice []float64) float64 {
	sum := float64(0)
	for _, number := range slice {
		sum += number
	}
	return sum
}

func getPacketLossAndAveragePingTime(nodeName string, timestamps []int64) []map[string]interface{} {
	result := []map[string]interface{}{}
	tableName := "nqm_log_" + strings.Replace(nodeName, "-", "_", -1)
	for _, timestamp := range timestamps {
		sends := []float64{}
		receives := []float64{}
		averages := []float64{}
		o := orm.NewOrm()
		o.Using("gz_nqm")
		sqlcmd := "SELECT send, receive, avg FROM `gz_nqm`.`" + tableName + "` WHERE mtime = ?"
		var rows []orm.Params
		num, err := o.Raw(sqlcmd, strconv.Itoa(int(timestamp))).Values(&rows)
		if err != nil {
			log.Debugf("Error = %v", err.Error())
		} else if num > 0 {
			for _, row := range rows {
				send, err := strconv.ParseFloat(row["send"].(string), 64)
				if err != nil {
					log.Debugf("Error = %v", err.Error())
				} else {
					sends = append(sends, send)
				}
				receive, err := strconv.ParseFloat(row["receive"].(string), 64)
				if err != nil {
					log.Debugf("Error = %v", err.Error())
				} else {
					receives = append(receives, receive)
				}
				avg, err := strconv.ParseFloat(row["avg"].(string), 64)
				if err != nil {
					log.Debugf("Error = %v", err.Error())
				} else {
					averages = append(averages, avg)
				}
			}
		}
		item := map[string]interface{}{
			"node":                        nodeName,
			"packetLossRate":              "",
			"averagePingTimeMilliseconds": "",
			"time": time.Unix(timestamp, 0).Format("2006-01-02 15:04"),
		}
		divider := float64(len(sends))
		if divider > 0 {
			packetLossRate := 1 - (getSum(receives) / getSum(sends))
			averagePingTimeMilliseconds := getSum(averages) / divider
			item["packetLossRate"] = packetLossRate
			item["averagePingTimeMilliseconds"] = averagePingTimeMilliseconds
		}
		result = append(result, item)
	}
	return result
}

func getNQMPacketLoss(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}

	tablesMap := map[string]interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'nqm_log_prober_%'"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		for _, row := range rows {
			tablesMap[row["table_name"].(string)] = ""
		}
	}

	nids := []string{}
	pidMap := map[string]interface{}{}
	var NQMNodes []*Nqm_node
	_, err = o.Raw("SELECT nid, pid, status FROM `gz_nqm`.`nqm_node` ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, node := range NQMNodes {
			if node.Status > 0 {
				nids = append(nids, node.Nid)
				pidMap[node.Nid] = node.Pid
			}
		}
	}
	idcNames := []string{}
	for _, nid := range nids {
		if _, ok := tablesMap["nqm_log_prober_"+strings.Replace(nid, "-", "_", -1)]; ok {
			idcNames = append(idcNames, nid)
		}
	}
	sort.Strings(idcNames)
	for _, idcName := range idcNames {
		tableName := "nqm_log_" + strings.Replace(idcName, "-", "_", -1)
		timestamp := getLatestTimestamp(tableName, result)
		if timestamp > 0 {
			result := getPacketLossAndAveragePingTime(idcName, []int64{timestamp})
			idc := result[0]
			idc["nodeName"] = idcName
			idc["pid"] = pidMap[idcName]
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	setResponse(rw, nodes)
}

func getJaguar(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}
	timestamp := int64(0)
	loc, err := time.LoadLocation("Asia/Taipei")
	if err != nil {
		loc = time.Local
	}
	timeFormat := "2006-01-02 15:04"
	timeInput := req.URL.Query().Get("time")
	date, err := time.ParseInLocation(timeFormat, timeInput, loc)
	if err == nil {
		timestamp = date.Unix()
	}
	nodeMap := map[string]map[string]string{}
	nodeNames := []string{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	var NQMNodes []*Nqm_node
	_, err = o.Raw("SELECT nid, pname, cname, iname FROM `gz_nqm`.`nqm_node` ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, node := range NQMNodes {
			nodeName := node.Nid
			node := map[string]string{
				"node":     nodeName,
				"province": node.Pname,
				"city":     node.Cname,
				"isp":      node.Iname,
			}
			nodeMap[nodeName] = node
			nodeNames = append(nodeNames, nodeName)
		}
	}
	sqlcmd := "SELECT nid, ip, note FROM `gz_nqm`.`nqm_dev` WHERE nid IN ('"
	sqlcmd += strings.Join(nodeNames, "','") + "')"
	var rows []orm.Params
	_, err = o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, row := range rows {
			nodeName := row["nid"].(string)
			nodeMap[nodeName]["ip"] = row["ip"].(string)
			nodeMap[nodeName]["idc"] = row["note"].(string)
		}
	}
	for _, nodeName := range nodeNames {
		node := nodeMap[nodeName]
		item := map[string]interface{}{
			"node":     node["node"],
			"province": node["province"],
			"city":     node["city"],
			"isp":      node["isp"],
			"ip":       node["ip"],
			"idc":      node["idc"],
			"loss":     nil,
			"ping.ms":  nil,
			"time":     nil,
		}
		tableName := "nqm_log_" + strings.Replace(nodeName, "-", "_", -1)
		timestampLatest := getLatestTimestamp(tableName, result)
		if timestampLatest > 0 {
			timestampNearest := timestampLatest
			if timestamp > 0 {
				timestampNearest = getNearestTimestamp(tableName, timestamp, result)
			}
			result := getPacketLossAndAveragePingTime(nodeName, []int64{timestampNearest})
			resp := result[0]
			if _, ok := resp["packetLossRate"]; ok {
				item["loss"] = resp["packetLossRate"]
				item["ping.ms"] = resp["averagePingTimeMilliseconds"]
				item["time"] = resp["time"]
			}
		}
		items = append(items, item)
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	setResponse(rw, nodes)
}

func configNQMRoutes() {
	http.HandleFunc("/api/nqm/nodes", getNQMNodes)
	http.HandleFunc("/api/nqm/loss", getNQMPacketLoss)
	http.HandleFunc("/api/nqm/jaguar", getJaguar)
}
<commit_msg>[OWL-1310][query] add func getTimestamps()<commit_after>package http

import (
	"github.com/astaxie/beego/orm"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
	log "github.com/Sirupsen/logrus"
)

type Nqm_node struct {
	Id         int
	Nid        string
	Pid        string
	Pname      string
	Pname_abbr string
	Cid        string
	Cname      string
	Cname_abbr string
	Iid        int
	Iname      string
	Status     int
	Note       string
	Addtime    int
}

func getNQMNodes(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	var NQMNodes []*Nqm_node
	_, err := o.Raw("SELECT nid, pname, cname, status, note FROM `gz_nqm`.`nqm_node` ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, node := range NQMNodes {
			idc := map[string]string{
				"idc":      node.Nid,
				"status":   strconv.Itoa(node.Status),
				"province": node.Pname,
				"city":     node.Cname,
				"note":     node.Note,
			}
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	setResponse(rw, nodes)
}

func getLatestTimestamp(tableName string, result map[string]interface{}) int64 {
	timestamp := int64(0)
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT mtime FROM `gz_nqm`.`" + tableName + "` ORDER BY mtime DESC LIMIT 1"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		mtime, err := strconv.Atoi(rows[0]["mtime"].(string))
		if err == nil {
			timestamp = int64(mtime)
		}
	}
	return timestamp
}

func getNearestTimestamp(tableName string, bound int64, result map[string]interface{}) int64 {
	timestamp := int64(0)
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT mtime FROM `gz_nqm`.`" + tableName
	sqlcmd += "` WHERE mtime <= ? ORDER BY mtime DESC LIMIT 1"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd, bound).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		mtime, err := strconv.Atoi(rows[0]["mtime"].(string))
		if err == nil {
			timestamp = int64(mtime)
		}
	}
	return timestamp
}

func getSum(slice []float64) float64 {
	sum := float64(0)
	for _, number := range slice {
		sum += number
	}
	return sum
}

func getPacketLossAndAveragePingTime(nodeName string, timestamps []int64) []map[string]interface{} {
	result := []map[string]interface{}{}
	tableName := "nqm_log_" + strings.Replace(nodeName, "-", "_", -1)
	for _, timestamp := range timestamps {
		sends := []float64{}
		receives := []float64{}
		averages := []float64{}
		o := orm.NewOrm()
		o.Using("gz_nqm")
		sqlcmd := "SELECT send, receive, avg FROM `gz_nqm`.`" + tableName + "` WHERE mtime = ?"
		var rows []orm.Params
		num, err := o.Raw(sqlcmd, strconv.Itoa(int(timestamp))).Values(&rows)
		if err != nil {
			log.Debugf("Error = %v", err.Error())
		} else if num > 0 {
			for _, row := range rows {
				send, err := strconv.ParseFloat(row["send"].(string), 64)
				if err != nil {
					log.Debugf("Error = %v", err.Error())
				} else {
					sends = append(sends, send)
				}
				receive, err := strconv.ParseFloat(row["receive"].(string), 64)
				if err != nil {
					log.Debugf("Error = %v", err.Error())
				} else {
					receives = append(receives, receive)
				}
				avg, err := strconv.ParseFloat(row["avg"].(string), 64)
				if err != nil {
					log.Debugf("Error = %v", err.Error())
				} else {
					averages = append(averages, avg)
				}
			}
		}
		item := map[string]interface{}{
			"node":                        nodeName,
			"packetLossRate":              "",
			"averagePingTimeMilliseconds": "",
			"time": time.Unix(timestamp, 0).Format("2006-01-02 15:04"),
		}
		divider := float64(len(sends))
		if divider > 0 {
			packetLossRate := 1 - (getSum(receives) / getSum(sends))
			averagePingTimeMilliseconds := getSum(averages) / divider
			item["packetLossRate"] = packetLossRate
			item["averagePingTimeMilliseconds"] = averagePingTimeMilliseconds
		}
		result = append(result, item)
	}
	return result
}

func getNQMPacketLoss(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}

	tablesMap := map[string]interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'nqm_log_prober_%'"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		for _, row := range rows {
			tablesMap[row["table_name"].(string)] = ""
		}
	}

	nids := []string{}
	pidMap := map[string]interface{}{}
	var NQMNodes []*Nqm_node
	_, err = o.Raw("SELECT nid, pid, status FROM `gz_nqm`.`nqm_node` ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, node := range NQMNodes {
			if node.Status > 0 {
				nids = append(nids, node.Nid)
				pidMap[node.Nid] = node.Pid
			}
		}
	}
	idcNames := []string{}
	for _, nid := range nids {
		if _, ok := tablesMap["nqm_log_prober_"+strings.Replace(nid, "-", "_", -1)]; ok {
			idcNames = append(idcNames, nid)
		}
	}
	sort.Strings(idcNames)
	for _, idcName := range idcNames {
		tableName := "nqm_log_" + strings.Replace(idcName, "-", "_", -1)
		timestamp := getLatestTimestamp(tableName, result)
		if timestamp > 0 {
			result := getPacketLossAndAveragePingTime(idcName, []int64{timestamp})
			idc := result[0]
			idc["nodeName"] = idcName
			idc["pid"] = pidMap[idcName]
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	setResponse(rw, nodes)
}

func getJaguar(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}
	timestamp := int64(0)
	loc, err := time.LoadLocation("Asia/Taipei")
	if err != nil {
		loc = time.Local
	}
	timeFormat := "2006-01-02 15:04"
	timeInput := req.URL.Query().Get("time")
	date, err := time.ParseInLocation(timeFormat, timeInput, loc)
	if err == nil {
		timestamp = date.Unix()
	}
	nodeMap := map[string]map[string]string{}
	nodeNames := []string{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	var NQMNodes []*Nqm_node
	_, err = o.Raw("SELECT nid, pname, cname, iname FROM `gz_nqm`.`nqm_node` ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, node := range NQMNodes {
			nodeName := node.Nid
			node := map[string]string{
				"node":     nodeName,
				"province": node.Pname,
				"city":     node.Cname,
				"isp":      node.Iname,
			}
			nodeMap[nodeName] = node
			nodeNames = append(nodeNames, nodeName)
		}
	}
	sqlcmd := "SELECT nid, ip, note FROM `gz_nqm`.`nqm_dev` WHERE nid IN ('"
	sqlcmd += strings.Join(nodeNames, "','") + "')"
	var rows []orm.Params
	_, err = o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else {
		for _, row := range rows {
			nodeName := row["nid"].(string)
			nodeMap[nodeName]["ip"] = row["ip"].(string)
			nodeMap[nodeName]["idc"] = row["note"].(string)
		}
	}
	for _, nodeName := range nodeNames {
		node := nodeMap[nodeName]
		item := map[string]interface{}{
			"node":     node["node"],
			"province": node["province"],
			"city":     node["city"],
			"isp":      node["isp"],
			"ip":       node["ip"],
			"idc":      node["idc"],
			"loss":     nil,
			"ping.ms":  nil,
			"time":     nil,
		}
		tableName := "nqm_log_" + strings.Replace(nodeName, "-", "_", -1)
		timestampLatest := getLatestTimestamp(tableName, result)
		if timestampLatest > 0 {
			timestampNearest := timestampLatest
			if timestamp > 0 {
				timestampNearest = getNearestTimestamp(tableName, timestamp, result)
			}
			result := getPacketLossAndAveragePingTime(nodeName, []int64{timestampNearest})
			resp := result[0]
			if _, ok := resp["packetLossRate"]; ok {
				item["loss"] = resp["packetLossRate"]
				item["ping.ms"] = resp["averagePingTimeMilliseconds"]
				item["time"] = resp["time"]
			}
		}
		items = append(items, item)
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	setResponse(rw, nodes)
}

func getTimestamps(tableName string, timestampFrom int64, timestampTo int64) []int64 {
	timestamps := []int64{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT DISTINCT mtime FROM `gz_nqm`.`" + tableName
	sqlcmd += "` WHERE mtime BETWEEN ? AND ? ORDER BY mtime ASC"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd, timestampFrom, timestampTo).Values(&rows)
	if err != nil {
		log.Debugf("Error = %v", err.Error())
	} else if num > 0 {
		for _, row := range rows {
			timestamp, err := strconv.ParseInt(row["mtime"].(string), 10, 64)
			if err != nil {
				log.Debugf("Error = %v", err.Error())
			} else {
				timestamps = append(timestamps, timestamp)
			}
		}
	}
	return timestamps
}

func configNQMRoutes() {
	http.HandleFunc("/api/nqm/nodes", getNQMNodes)
	http.HandleFunc("/api/nqm/loss", getNQMPacketLoss)
	http.HandleFunc("/api/nqm/jaguar", getJaguar)
}
<|endoftext|>


<commit_before>package data

import (
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type Account struct {
	ID              bson.ObjectId   `bson:"_id"`
	Emails          []AccountEmail  `bson:"emails"`
	Password        AccountPassword `bson:"password"`
	OrganizationIDs []bson.ObjectId `bson:"organization_ids"`

	CreatedAt  time.Time `bson:"created_at"`
	ModifiedAt time.Time `bson:"modified_at"`
}

func GetAccount(id bson.ObjectId) (*Account, error) {
	acc := Account{}

	err := sess.DB("").C(accountC).FindId(id).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}
<commit_msg>Implement GetAccountEmail function<commit_after>package data

import (
	"time"

	"github.com/asaskevich/govalidator"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type Account struct {
	ID              bson.ObjectId   `bson:"_id"`
	Emails          []AccountEmail  `bson:"emails"`
	Password        AccountPassword `bson:"password"`
	OrganizationIDs []bson.ObjectId `bson:"organization_ids"`

	CreatedAt  time.Time `bson:"created_at"`
	ModifiedAt time.Time `bson:"modified_at"`
}

func GetAccount(id bson.ObjectId) (*Account, error) {
	acc := Account{}

	err := sess.DB("").C(accountC).FindId(id).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}

func GetAccountEmail(addr string) (*Account, error) {
	addr, err := govalidator.NormalizeEmail(addr)
	if err != nil {
		return nil, nil
	}
	acc := Account{}
	err = sess.DB("").C(accountC).Find(bson.M{"emails": addr}).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}
<|endoftext|>


<commit_before>package msgpack

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type field struct {
	name      string
	omitEmpty bool
	array     bool
	index     []int
	typ       reflect.Type
	empty     reflect.Value
}

func collectFields(fields []*field, t reflect.Type, visited map[reflect.Type]bool, depth map[string]int, index []int) []*field {
	// Break recursion.
	if visited[t] {
		return fields
	}
	visited[t] = true

	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		if sf.PkgPath != "" && !sf.Anonymous {
			// Skip field if not exported and not anonymous.
			continue
		}

		var (
			name      string
			omitEmpty bool
			array     bool
		)
		for i, p := range strings.Split(sf.Tag.Get("msgpack"), ",") {
			if i == 0 {
				name = p
			} else if p == "omitempty" {
				omitEmpty = true
			} else if p == "array" {
				array = true
			} else {
				panic(fmt.Errorf("msgpack: unknown field tag %s for type %s", p, t.Name()))
			}
		}

		if name == "-" {
			// Skip field when field tag starts with "-".
			continue
		}

		ft := sf.Type
		if ft.Name() == "" && ft.Kind() == reflect.Ptr {
			ft = ft.Elem()
		}

		if name == "" && sf.Anonymous && ft.Kind() == reflect.Struct {
			// Flatten anonymous struct field.
			fields = collectFields(fields, ft, visited, depth, append(index, i))
			continue
		}

		if name == "" {
			name = sf.Name
		}

		// Check for name collisions.
		d, found := depth[name]
		if !found {
			d = 65535
		}
		if len(index) == d {
			// There is another field with same name and same depth.
			// Remove that field and skip this field.
			j := 0
			for i := 0; i < len(fields); i++ {
				if name != fields[i].name {
					fields[j] = fields[i]
					j++
				}
			}
			fields = fields[:j]
			continue
		}
		depth[name] = len(index)

		f := &field{
			name:      name,
			omitEmpty: omitEmpty,
			array:     array,
			index:     make([]int, len(index)+1),
			typ:       sf.Type,
		}
		copy(f.index, index)
		f.index[len(index)] = i

		// Parse empty field tag.

		if e := sf.Tag.Get("empty"); e != "" {
			switch sf.Type.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				bits := 0
				if sf.Type.Kind() != reflect.Int {
					bits = sf.Type.Bits()
				}
				v, err := strconv.ParseInt(e, 10, bits)
				if err != nil {
					panic(fmt.Errorf("msgpack: error parsing field empty field %s.%s: %w", t.Name(), sf.Name, err))
				}
				f.empty = reflect.New(sf.Type).Elem()
				f.empty.SetInt(v)
			case reflect.Bool:
				v, err := strconv.ParseBool(e)
				if err != nil {
					panic(fmt.Errorf("msgpack: error parsing field empty field %s.%s: %w", t.Name(), sf.Name, err))
				}
				f.empty = reflect.New(sf.Type).Elem()
				f.empty.SetBool(v)
			case reflect.String:
				f.empty = reflect.New(sf.Type).Elem()
				f.empty.SetString(e)
			default:
				panic(fmt.Errorf("msgpack: unsupported empty field %s.%s", t.Name(), sf.Name))
			}
		}

		fields = append(fields, f)

	}
	return fields
}

func fieldsForType(t reflect.Type) ([]*field, bool) {
	fields := collectFields(nil, t, make(map[reflect.Type]bool), make(map[string]int), nil)
	array := false
	for _, field := range fields {
		if field.array {
			array = true
			break
		}
	}
	return fields, array
}

func fieldByIndex(v reflect.Value, index []int) reflect.Value {
	for _, i := range index {
		if v.Kind() == reflect.Ptr {
			if v.IsNil() {
				return reflect.Value{}
			}
			v = v.Elem()
		}
		v = v.Field(i)
	}
	return v
}
<commit_msg>msgpack: format field.go<commit_after>package msgpack

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type field struct {
	name      string
	omitEmpty bool
	array     bool
	index     []int
	typ       reflect.Type
	empty     reflect.Value
}

func collectFields(fields []*field, t reflect.Type, visited map[reflect.Type]bool, depth map[string]int, index []int) []*field {
	// Break recursion
	if visited[t] {
		return fields
	}
	visited[t] = true

	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		if sf.PkgPath != "" && !sf.Anonymous {
			// Skip field if not exported and not anonymous
			continue
		}

		var (
			name      string
			omitEmpty bool
			array     bool
		)
		for i, p := range strings.Split(sf.Tag.Get("msgpack"), ",") {
			if i == 0 {
				name = p
			} else if p == "omitempty" {
				omitEmpty = true
			} else if p == "array" {
				array = true
			} else {
				panic(fmt.Errorf("msgpack: unknown field tag %s for type %s", p, t.Name()))
			}
		}

		if name == "-" {
			// Skip field when field tag starts with "-"
			continue
		}

		ft := sf.Type
		if ft.Name() == "" && ft.Kind() == reflect.Ptr {
			ft = ft.Elem()
		}

		if name == "" && sf.Anonymous && ft.Kind() == reflect.Struct {
			// Flatten anonymous struct field
			fields = collectFields(fields, ft, visited, depth, append(index, i))
			continue
		}

		if name == "" {
			name = sf.Name
		}

		// Check for name collisions
		d, found := depth[name]
		if !found {
			d = 65535
		}

		if len(index) == d {
			// There is another field with same name and same depth
			// Remove that field and skip this field
			j := 0
			for i := 0; i < len(fields); i++ {
				if name != fields[i].name {
					fields[j] = fields[i]
					j++
				}
			}
			fields = fields[:j]
			continue
		}
		depth[name] = len(index)

		f := &field{
			name:      name,
			omitEmpty: omitEmpty,
			array:     array,
			index:     make([]int, len(index)+1),
			typ:       sf.Type,
		}
		copy(f.index, index)
		f.index[len(index)] = i

		// Parse empty field tag
		if e := sf.Tag.Get("empty"); e != "" {
			switch sf.Type.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				bits := 0
				if sf.Type.Kind() != reflect.Int {
					bits = sf.Type.Bits()
				}
				v, err := strconv.ParseInt(e, 10, bits)
				if err != nil {
					panic(fmt.Errorf("msgpack: error parsing field empty field %s.%s: %w", t.Name(), sf.Name, err))
				}
				f.empty = reflect.New(sf.Type).Elem()
				f.empty.SetInt(v)

			case reflect.Bool:
				v, err := strconv.ParseBool(e)
				if err != nil {
					panic(fmt.Errorf("msgpack: error parsing field empty field %s.%s: %w", t.Name(), sf.Name, err))
				}
				f.empty = reflect.New(sf.Type).Elem()
				f.empty.SetBool(v)

			case reflect.String:
				f.empty = reflect.New(sf.Type).Elem()
				f.empty.SetString(e)

			default:
				panic(fmt.Errorf("msgpack: unsupported empty field %s.%s", t.Name(), sf.Name))
			}
		}

		fields = append(fields, f)

	}

	return fields
}

func fieldsForType(t reflect.Type) ([]*field, bool) {
	fields := collectFields(nil, t, make(map[reflect.Type]bool), make(map[string]int), nil)
	array := false

	for _, field := range fields {
		if field.array {
			array = true
			break
		}
	}

	return fields, array
}

func fieldByIndex(v reflect.Value, index []int) reflect.Value {
	for _, i := range index {
		if v.Kind() == reflect.Ptr {
			if v.IsNil() {
				return reflect.Value{}
			}
			v = v.Elem()
		}
		v = v.Field(i)
	}

	return v
}
<|endoftext|>


<commit_before>package pipeline

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/fjukstad/kvik/kompute"
)

type Stage struct {
	Name      string
	Package   string
	Function  string
	Arguments map[string]string
	Depends   []Stage
	Session   *kompute.Session
}

type Pipeline struct {
	Name    string
	Kompute *kompute.Kompute
	Stages  []*Stage
}

func NewPipeline(name string, k *kompute.Kompute) Pipeline {
	p := Pipeline{name, k, nil}
	return p
}

func (p *Pipeline) AddStage(s Stage) {
	p.Stages = append(p.Stages, &s)
}

func NewStage(name, function, pkg string, argnames, args []string) Stage {

	argmap := make(map[string]string, 0)

	for i, argname := range argnames {
		arg := args[i]
		argmap[argname] = arg
	}

	s := Stage{name, pkg, function, argmap, []Stage{}, nil}

	return s
}

func (p *Pipeline) RunParallel() ([]*Pipeline, error) {

	pipeMap := map[string]*Pipeline{}
	pipes := []*Pipeline{}

	i := 0

	for _, stage := range p.Stages {

		deps := stage.GetDependencies()
		// if it's not dependent on anything we
		// in a new pipeline.
		if len(deps) == 0 {
			pipe := NewPipeline(p.Name+"-par-"+strconv.Itoa(i), p.Kompute)
			pipe.AddStage(*stage)
			pipeMap[stage.Name] = &pipe
			i++
			pipes = append(pipes, &pipe)

		} else {

			pipe := pipeMap[deps[0]]
			fmt.Println(pipe)
			pipe.AddStage(*stage)
			pipeMap[stage.Name] = pipe
			fmt.Println("Pipeline", len(pipe.Stages))
		}
	}

	for _, pipe := range pipes {
		fmt.Println(pipe.Name, len(pipe.Stages))
		pipe.Run()
	}

	return pipes, nil
}

type Result struct {
	m     *sync.Mutex
	c     *sync.Cond
	Key   string
	Error error
}

func (p *Pipeline) WorkMagic() ([]*Result, error) {

	resultMap := make(map[string]*Result, 0)

	for _, stage := range p.Stages {
		m := sync.Mutex{}
		c := sync.NewCond(&m)
		resultMap[stage.Name] = &Result{&m, c, "", nil}
	}

	for _, stage := range p.Stages {
		deps := stage.GetDependencies()
		r := resultMap[stage.Name]

		if len(deps) == 0 {
			go func(r *Result, stage *Stage) {
				r.m.Lock()
				key, err := p.ExecuteStage(stage)
				r.Key = key
				r.Error = err
				//resultMap[stage.Name] = &Result{l, key, err}
				r.m.Unlock()
				r.c.Broadcast()

			}(r, stage)
		} else {
			m := make(chan bool, len(deps)-1)

			for _, dep := range deps {
				r := resultMap[dep]
				go func(r *Result, stage *Stage, dep string) {

					r.m.Lock()

					for r.Key == "" {
						r.c.Wait()
					}

					stage.ReplaceArg(dep, r.Key)
					m <- true
				}(r, stage, dep)
			}

			//		PARALLELIZE FOR LOOP:

			for i := 0; i < len(deps); i++ {
				<-m
			}

			go func(r *Result, stage *Stage) {
				r.m.Lock()

				r.Key, r.Error = p.ExecuteStage(stage)

				r.m.Unlock()
				r.c.Broadcast()

			}(r, stage)

		}
	}

	results := []*Result{}
	for _, stage := range p.Stages {
		r := resultMap[stage.Name]
		if r.Key == "" {
			r.m.Lock()
		}
		for r.Key == "" {
			r.c.Wait()
		}
		results = append(results, r)
	}

	return results, nil
}

func (s *Stage) ReplaceArg(oldarg string, newarg string) {
	for i, arg := range s.Arguments {
		if strings.Contains(arg, oldarg) {
			s.Arguments[i] = newarg
			return
		}
	}
}

func (p *Pipeline) ExecuteStage(stage *Stage) (string, error) {

	args := stage.GetArguments()
	fun := stage.GetFullFunctionName()

	s, err := p.Kompute.Call(fun, args)

	if err != nil {
		s, err = p.Kompute.Call(fun, args)

		if err != nil {
			fmt.Println("ERROR", err)
			return "", err
		}
	}
	return s.Key, nil
}

func (s Stage) GetDependencies() []string {
	deps := []string{}
	for _, arg := range s.Arguments {
		if strings.Contains(arg, "from:") {
			argname := strings.Split(arg, "from:")[1]
			deps = append(deps, argname)
		}
	}
	return deps
}

func (p *Pipeline) GetResult(format string) string {
	lastStage := p.Stages[len(p.Stages)-1]
	res, _ := lastStage.Session.GetResult(p.Kompute, format)
	return res
}

func (p *Pipeline) Run() error {

	for _, stage := range p.Stages {

		args := stage.GetArguments()
		fun := stage.GetFullFunctionName()

		s, err := p.Kompute.Call(fun, args)
		if err != nil {
			s, err = p.Kompute.Call(fun, args)
			if err != nil {
				fmt.Println("ERROR", err)
				return err
			}
		}

		stage.Session = s
	}

	return nil
}

func (p *Pipeline) Save() error {
	file, err := os.OpenFile(p.Name+".json", os.O_RDWR|os.O_CREATE, 0660)
	if err != nil {
		return err
	}

	b, err := json.Marshal(p)
	if err != nil {
		return err
	}

	_, err = file.Write(b)
	if err != nil {
		return err
	}
	return nil
}

func (s *Stage) GetFullFunctionName() string {
	return s.Package + "/R/" + s.Function
}

func (s *Stage) GetArguments() string {

	str := ""
	i := 0

	for k, v := range s.Arguments {

		if strings.Contains(v, "/") || strings.Contains(v, ".") {
			v = "\"" + v + "\""
		}

		str = str + k + "=" + v
		if i < len(s.Arguments)-1 {
			str = str + "&"
		}
		i++
	}

	return str
}

func (s *Stage) Print() {
	fmt.Println("\tName:", s.Name)
	fmt.Println("\tPackage", s.Package)
	fmt.Println("\tFunction", s.Function)
	fmt.Println("\tArguments:")
	for k, v := range s.Arguments {
		fmt.Println("\t\t", k, "\t", v)
	}
	if s.Session != nil {
		fmt.Println("\tSession: ", s.Session.Key)
		fmt.Println("\tURL: /ocpu/tmp/" + s.Session.Key + "/R/")
	}

}
<commit_msg>concurrent run done<commit_after>package pipeline

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"sync"

	"github.com/fjukstad/kvik/kompute"
)

type Stage struct {
	Name      string
	Package   string
	Function  string
	Arguments map[string]string
	Depends   []Stage
	Session   *kompute.Session
}

type Pipeline struct {
	Name    string
	Kompute *kompute.Kompute
	Stages  []*Stage
}

func NewPipeline(name string, k *kompute.Kompute) Pipeline {
	p := Pipeline{name, k, nil}
	return p
}

func (p *Pipeline) AddStage(s Stage) {
	p.Stages = append(p.Stages, &s)
}

func NewStage(name, function, pkg string, argnames, args []string) Stage {

	argmap := make(map[string]string, 0)

	for i, argname := range argnames {
		arg := args[i]
		argmap[argname] = arg
	}

	s := Stage{name, pkg, function, argmap, []Stage{}, nil}

	return s
}

type Result struct {
	m     *sync.Mutex
	c     *sync.Cond
	Key   string
	Error error
}

func (p *Pipeline) Run() ([]*Result, error) {

	resultMap := make(map[string]*Result, 0)

	for _, stage := range p.Stages {
		m := sync.Mutex{}
		c := sync.NewCond(&m)
		resultMap[stage.Name] = &Result{&m, c, "", nil}
	}

	m := make(chan bool, len(p.Stages))

	for _, stage := range p.Stages {
		deps := stage.GetDependencies()
		r := resultMap[stage.Name]

		go func(r *Result, stage *Stage, deps []string) {
			if len(deps) == 0 {
				r.m.Lock()
				r.Key, r.Error = p.ExecuteStage(stage)
				r.c.Broadcast()
				r.m.Unlock()

			} else {
				for _, dep := range deps {
					r := resultMap[dep]
					r.m.Lock()

					for r.Key == "" {
						r.c.Wait()
					}

					stage.ReplaceArg(dep, r.Key)
					r.m.Unlock()
				}

				r.m.Lock()
				r.Key, r.Error = p.ExecuteStage(stage)
				r.c.Broadcast()
				r.m.Unlock()
			}

			m <- true
		}(r, stage, deps)
	}

	for i := 0; i < len(p.Stages); i++ {
		<-m
	}

	results := []*Result{}
	for _, stage := range p.Stages {
		r := resultMap[stage.Name]
		results = append(results, r)

	}

	return results, nil
}

func (s *Stage) ReplaceArg(oldarg string, newarg string) {
	for i, arg := range s.Arguments {
		if strings.Contains(arg, oldarg) {
			s.Arguments[i] = newarg
			return
		}
	}
}

func (p *Pipeline) ExecuteStage(stage *Stage) (string, error) {

	args := stage.GetArguments()
	fun := stage.GetFullFunctionName()

	s, err := p.Kompute.Call(fun, args)

	if err != nil {
		s, err = p.Kompute.Call(fun, args)

		if err != nil {
			fmt.Println("ERROR", err)
			return "", err
		}
	}
	return s.Key, nil
}

func (s Stage) GetDependencies() []string {
	deps := []string{}
	for _, arg := range s.Arguments {
		if strings.Contains(arg, "from:") {
			argname := strings.Split(arg, "from:")[1]
			deps = append(deps, argname)
		}
	}
	return deps
}

func (p *Pipeline) GetResult(format string) string {
	lastStage := p.Stages[len(p.Stages)-1]
	res, _ := lastStage.Session.GetResult(p.Kompute, format)
	return res
}

func (p *Pipeline) Save() error {
	file, err := os.OpenFile(p.Name+".json", os.O_RDWR|os.O_CREATE, 0660)
	if err != nil {
		return err
	}

	b, err := json.Marshal(p)
	if err != nil {
		return err
	}

	_, err = file.Write(b)
	if err != nil {
		return err
	}
	return nil
}

func (s *Stage) GetFullFunctionName() string {
	return s.Package + "/R/" + s.Function
}

func (s *Stage) GetArguments() string {

	str := ""
	i := 0

	for k, v := range s.Arguments {

		if strings.Contains(v, "/") || strings.Contains(v, ".") {
			v = "\"" + v + "\""
		}

		str = str + k + "=" + v
		if i < len(s.Arguments)-1 {
			str = str + "&"
		}
		i++
	}

	return str
}

func (s *Stage) Print() {
	fmt.Println("\tName:", s.Name)
	fmt.Println("\tPackage", s.Package)
	fmt.Println("\tFunction", s.Function)
	fmt.Println("\tArguments:")
	for k, v := range s.Arguments {
		fmt.Println("\t\t", k, "\t", v)
	}
	if s.Session != nil {
		fmt.Println("\tSession: ", s.Session.Key)
		fmt.Println("\tURL: /ocpu/tmp/" + s.Session.Key + "/R/")
	}

}
<|endoftext|>


<commit_before>/*

   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 test

import (
	"net/http"
	"testing"

	"github.com/apache/incubator-trafficcontrol/traffic_ops/client"
	"github.com/apache/incubator-trafficcontrol/traffic_ops/client/fixtures"
	"github.com/jheitz200/test_helper"
)

func TestProfile(t *testing.T) {
	resp := fixtures.Profiles()
	server := testHelper.ValidHTTPServer(resp)
	defer server.Close()

	var httpClient http.Client
	to := client.Session{
		URL:       server.URL,
		UserAgent: &httpClient,
	}

	testHelper.Context(t, "Given the need to test a successful Traffic Ops request for Profiles")

	profiles, err := to.Profiles()
	if err != nil {
		testHelper.Error(t, "Should be able to make a request to Traffic Ops")
	} else {
		testHelper.Success(t, "Should be able to make a request to Traffic Ops")
	}

	if len(profiles) != 1 {
		testHelper.Error(t, "Should get back \"1\" Profile, got: %d", len(profiles))
	} else {
		testHelper.Success(t, "Should get back \"1\" Profile")
	}

	for _, p := range profiles {
		if p.ID != 1 {
			testHelper.Error(t, "Should get back 1 for \"ID\", got: %s", p.Name)
		} else {
			testHelper.Success(t, "Should get back 1 for \"ID\"")
		}

		if p.Name != "TR_CDN2" {
			testHelper.Error(t, "Should get back \"TR_CDN2\" for \"Name\", got: %s", p.Name)
		} else {
			testHelper.Success(t, "Should get back \"TR_CDN2\" for \"Name\"")
		}

		if p.Description != "kabletown Content Router" {
			testHelper.Error(t, "Should get back \"kabletown Content Router\" for \"Description\", got: %s", p.Description)
		} else {
			testHelper.Success(t, "Should get back \"kabletown Content Router\" for \"Description\"")
		}
	}
}

func TestProfilesUnauthorized(t *testing.T) {
	server := testHelper.InvalidHTTPServer(http.StatusUnauthorized)
	defer server.Close()

	var httpClient http.Client
	to := client.Session{
		URL:       server.URL,
		UserAgent: &httpClient,
	}

	testHelper.Context(t, "Given the need to test a failed Traffic Ops request for Profiles")

	_, err := to.Profiles()
	if err == nil {
		testHelper.Error(t, "Should not be able to make a request to Traffic Ops")
	} else {
		testHelper.Success(t, "Should not be able to make a request to Traffic Ops")
	}
}
<commit_msg>update profile test to log correct error for ID<commit_after>/*

   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 test

import (
	"net/http"
	"testing"

	"github.com/apache/incubator-trafficcontrol/traffic_ops/client"
	"github.com/apache/incubator-trafficcontrol/traffic_ops/client/fixtures"
	"github.com/jheitz200/test_helper"
)

func TestProfile(t *testing.T) {
	resp := fixtures.Profiles()
	server := testHelper.ValidHTTPServer(resp)
	defer server.Close()

	var httpClient http.Client
	to := client.Session{
		URL:       server.URL,
		UserAgent: &httpClient,
	}

	testHelper.Context(t, "Given the need to test a successful Traffic Ops request for Profiles")

	profiles, err := to.Profiles()
	if err != nil {
		testHelper.Error(t, "Should be able to make a request to Traffic Ops")
	} else {
		testHelper.Success(t, "Should be able to make a request to Traffic Ops")
	}

	if len(profiles) != 1 {
		testHelper.Error(t, "Should get back \"1\" Profile, got: %d", len(profiles))
	} else {
		testHelper.Success(t, "Should get back \"1\" Profile")
	}

	for _, p := range profiles {
		if p.ID != 1 {
			testHelper.Error(t, "Should get back 1 for \"ID\", got: %d", p.ID)
		} else {
			testHelper.Success(t, "Should get back 1 for \"ID\"")
		}

		if p.Name != "TR_CDN2" {
			testHelper.Error(t, "Should get back \"TR_CDN2\" for \"Name\", got: %s", p.Name)
		} else {
			testHelper.Success(t, "Should get back \"TR_CDN2\" for \"Name\"")
		}

		if p.Description != "kabletown Content Router" {
			testHelper.Error(t, "Should get back \"kabletown Content Router\" for \"Description\", got: %s", p.Description)
		} else {
			testHelper.Success(t, "Should get back \"kabletown Content Router\" for \"Description\"")
		}
	}
}

func TestProfilesUnauthorized(t *testing.T) {
	server := testHelper.InvalidHTTPServer(http.StatusUnauthorized)
	defer server.Close()

	var httpClient http.Client
	to := client.Session{
		URL:       server.URL,
		UserAgent: &httpClient,
	}

	testHelper.Context(t, "Given the need to test a failed Traffic Ops request for Profiles")

	_, err := to.Profiles()
	if err == nil {
		testHelper.Error(t, "Should not be able to make a request to Traffic Ops")
	} else {
		testHelper.Success(t, "Should not be able to make a request to Traffic Ops")
	}
}
<|endoftext|>


<commit_before>package plugins

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/bradfitz/slice"
	"github.com/bwmarrin/discordgo"
	"github.com/getsentry/raven-go"
	rethink "github.com/gorethink/gorethink"
)

type GuildAnnouncements struct{}

type Announcement_Setting struct {
	Id                  string `rethink:"id,omitempty"`
	GuildID             string `rethink:"guildid"`
	GuildJoinChannelID  string `rethink:"guild_join_channelid"`
	GuildJoinText       string `rethink:"guild_join_text"`
	GuildJoinEnabled    bool   `rethink:"guild_join_enabled"`
	GuildLeaveChannelID string `rethink:"guild_leave_channelid"`
	GuildLeaveText      string `rethink:"guild_leave_text"`
	GuildLeaveEnabled   bool   `rethink:"guild_leave_enabled"`
}

func (m *GuildAnnouncements) Commands() []string {
	return []string{
		"guildannouncements",
	}
}

func (m *GuildAnnouncements) Init(session *discordgo.Session) {

}

func (m *GuildAnnouncements) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	args := strings.Fields(content)
	if len(args) >= 2 {
		switch args[0] {
		case "set":
			switch args[1] {
			case "guild_join":
				helpers.RequireAdmin(msg, func() {
					sourceChannel, err := helpers.GetChannel(msg.ChannelID)
					helpers.Relax(err)
					guildAnnouncementSetting := m.getEntryByOrCreateEmpty("guildid", sourceChannel.GuildID)
					guildAnnouncementSetting.GuildID = sourceChannel.GuildID
					var successMessage string
					// Add Text
					if len(args) >= 4 {
						targetChannel, err := helpers.GetChannelFromMention(msg, args[2])
						if err != nil || targetChannel.ID == "" {
							session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
							return
						}

						newText := strings.TrimSpace(strings.Replace(content, strings.Join(args[:3], " "), "", 1))
						guildAnnouncementSetting.GuildJoinEnabled = true
						guildAnnouncementSetting.GuildJoinChannelID = targetChannel.ID
						guildAnnouncementSetting.GuildJoinText = newText
						successMessage = helpers.GetText("plugins.guildannouncements.message-edited")
					} else {
						// Remove Text
						guildAnnouncementSetting.GuildJoinEnabled = false
						successMessage = helpers.GetText("plugins.guildannouncements.message-disabled")
					}
					m.setEntry(guildAnnouncementSetting)
					_, err = session.ChannelMessageSend(msg.ChannelID, successMessage)
					helpers.Relax(err)
				})
			case "guild_leave":
				helpers.RequireAdmin(msg, func() {
					sourceChannel, err := helpers.GetChannel(msg.ChannelID)
					helpers.Relax(err)

					guildAnnouncementSetting := m.getEntryByOrCreateEmpty("guildid", sourceChannel.GuildID)
					guildAnnouncementSetting.GuildID = sourceChannel.GuildID
					var successMessage string
					// Add Text
					if len(args) >= 4 {
						targetChannel, err := helpers.GetChannelFromMention(msg, args[2])
						if err != nil || targetChannel.ID == "" {
							session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
							return
						}

						newText := strings.TrimSpace(strings.Replace(content, strings.Join(args[:3], " "), "", 1))
						guildAnnouncementSetting.GuildLeaveEnabled = true
						guildAnnouncementSetting.GuildLeaveChannelID = targetChannel.ID
						guildAnnouncementSetting.GuildLeaveText = newText
						successMessage = helpers.GetText("plugins.guildannouncements.message-edited")
					} else {
						// Remove Text
						guildAnnouncementSetting.GuildLeaveEnabled = false
						successMessage = helpers.GetText("plugins.guildannouncements.message-disabled")
					}
					m.setEntry(guildAnnouncementSetting)
					_, err = session.ChannelMessageSend(msg.ChannelID, successMessage)
					helpers.Relax(err)
				})
			}
		}
	}

}

func (m *GuildAnnouncements) OnMessage(content string, msg *discordgo.Message, session *discordgo.Session) {

}

func (m *GuildAnnouncements) OnGuildMemberAdd(member *discordgo.Member, session *discordgo.Session) {
	go func() {
		defer helpers.Recover()

		guild, err := helpers.GetGuild(member.GuildID)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
			return
		}
		helpers.Relax(err)
		for _, guildAnnouncementSetting := range m.GetAnnouncementSettings() {
			if guildAnnouncementSetting.GuildJoinEnabled == true && guildAnnouncementSetting.GuildID == guild.ID {
				guildJoinChannelID := guildAnnouncementSetting.GuildJoinChannelID
				guildJoinText := m.ReplaceMemberText(guildAnnouncementSetting.GuildJoinText, member)
				if guildJoinText != "" {
					go func() {
						_, err := session.ChannelMessageSend(guildJoinChannelID, guildJoinText)
						if err != nil {
							cache.GetLogger().WithField("module", "guildannouncements").Error(fmt.Sprintf("Error Sending Join Message in %s #%s: %s",
								guild.Name, guild.ID, err.Error()))
						}
					}()
				}
			}
		}
		cache.GetLogger().WithField("module", "guildannouncements").Info(fmt.Sprintf("User %s (%s) joined Guild %s (#%s)", member.User.Username, member.User.ID, guild.Name, guild.ID))
	}()
}
func (m *GuildAnnouncements) OnGuildMemberRemove(member *discordgo.Member, session *discordgo.Session) {
	go func() {
		defer helpers.Recover()

		guild, err := helpers.GetGuild(member.GuildID)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
			return
		}
		for _, guildAnnouncementSetting := range m.GetAnnouncementSettings() {
			if guildAnnouncementSetting.GuildLeaveEnabled == true && guildAnnouncementSetting.GuildID == guild.ID {
				guildLeaveChannelID := guildAnnouncementSetting.GuildLeaveChannelID
				guildLeaveText := m.ReplaceMemberText(guildAnnouncementSetting.GuildLeaveText, member)
				if guildLeaveText != "" {
					go func() {
						_, err := session.ChannelMessageSend(guildLeaveChannelID, guildLeaveText)
						if err != nil {
							cache.GetLogger().WithField("module", "guildannouncements").Error(fmt.Sprintf("Error Sending Leave Message in %s #%s: %s",
								guild.Name, guild.ID, err.Error()))
						}
					}()
				}
			}
		}
		cache.GetLogger().WithField("module", "guildannouncements").Info(fmt.Sprintf("User %s (%s) left Guild %s (#%s)", member.User.Username, member.User.ID, guild.Name, guild.ID))
	}()
}

func (m *GuildAnnouncements) GetAnnouncementSettings() []Announcement_Setting {
	var entryBucket []Announcement_Setting
	cursor, err := rethink.Table("guild_announcements").Run(helpers.GetDB())
	helpers.Relax(err)

	err = cursor.All(&entryBucket)
	helpers.Relax(err)

	return entryBucket
}

func (m *GuildAnnouncements) ReplaceMemberText(text string, member *discordgo.Member) string {
	guild, err := cache.GetSession().Guild(member.GuildID)
	if errD, ok := err.(*discordgo.RESTError); ok {
		if errD.Message.Code != 50001 { // It's probably Robyul leaving a server :sob:
			return ""
		} else {
			helpers.Relax(err)
		}
	} else {
		helpers.Relax(err)
	}
	allMembers := make([]*discordgo.Member, 0)
	for _, botGuild := range cache.GetSession().State.Guilds {
		if botGuild.ID == guild.ID {
			for _, member := range guild.Members {
				if member.JoinedAt == "" {
					member, err := helpers.GetGuildMember(member.GuildID, member.User.ID)
					if err == nil && member.JoinedAt != "" {
						allMembers = append(allMembers, member)
					}
				} else {
					allMembers = append(allMembers, member)
				}
			}
		}
	}
	slice.Sort(allMembers[:], func(i, j int) bool {
		iMemberTime, err := discordgo.Timestamp(allMembers[i].JoinedAt).Parse()
		helpers.Relax(err)
		jMemberTime, err := discordgo.Timestamp(allMembers[j].JoinedAt).Parse()
		helpers.Relax(err)
		return iMemberTime.Before(jMemberTime)
	})

	userNumber := -1
	for i, sortedMember := range allMembers[:] {
		if sortedMember.User.ID == member.User.ID {
			userNumber = i + 1
			break
		}
	}

	text = strings.Replace(text, "{USER_USERNAME}", member.User.Username, -1)
	text = strings.Replace(text, "{USER_ID}", member.User.ID, -1)
	text = strings.Replace(text, "{USER_DISCRIMINATOR}", member.User.Discriminator, -1)
	text = strings.Replace(text, "{USER_NUMBER}", strconv.Itoa(userNumber), -1)
	text = strings.Replace(text, "{USER_MENTION}", fmt.Sprintf("<@%s>", member.User.ID), -1)
	text = strings.Replace(text, "{GUILD_NAME}", guild.Name, -1)
	text = strings.Replace(text, "{GUILD_ID}", guild.ID, -1)
	return text
}

func (m *GuildAnnouncements) getEntryByOrCreateEmpty(key string, id string) Announcement_Setting {
	var entryBucket Announcement_Setting
	listCursor, err := rethink.Table("guild_announcements").Filter(
		rethink.Row.Field(key).Eq(id),
	).Run(helpers.GetDB())
	if err != nil {
		panic(err)
	}
	defer listCursor.Close()
	err = listCursor.One(&entryBucket)

	// If user has no DB entries create an empty document
	if err == rethink.ErrEmptyResult {
		insert := rethink.Table("guild_announcements").Insert(Announcement_Setting{})
		res, e := insert.RunWrite(helpers.GetDB())
		// If the creation was successful read the document
		if e != nil {
			panic(e)
		} else {
			return m.getEntryByOrCreateEmpty("id", res.GeneratedKeys[0])
		}
	} else if err != nil {
		panic(err)
	}

	return entryBucket
}

func (m *GuildAnnouncements) setEntry(entry Announcement_Setting) {
	_, err := rethink.Table("guild_announcements").Update(entry).Run(helpers.GetDB())
	helpers.Relax(err)
}
func (m *GuildAnnouncements) OnReactionAdd(reaction *discordgo.MessageReactionAdd, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnReactionRemove(reaction *discordgo.MessageReactionRemove, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnGuildBanAdd(user *discordgo.GuildBanAdd, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnGuildBanRemove(user *discordgo.GuildBanRemove, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnMessageDelete(msg *discordgo.MessageDelete, session *discordgo.Session) {

}
<commit_msg>[guildannouncements] fixes handling faulty responses (fixes ROBYUL-DISCORD-8E)<commit_after>package plugins

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/bradfitz/slice"
	"github.com/bwmarrin/discordgo"
	"github.com/getsentry/raven-go"
	rethink "github.com/gorethink/gorethink"
)

type GuildAnnouncements struct{}

type Announcement_Setting struct {
	Id                  string `rethink:"id,omitempty"`
	GuildID             string `rethink:"guildid"`
	GuildJoinChannelID  string `rethink:"guild_join_channelid"`
	GuildJoinText       string `rethink:"guild_join_text"`
	GuildJoinEnabled    bool   `rethink:"guild_join_enabled"`
	GuildLeaveChannelID string `rethink:"guild_leave_channelid"`
	GuildLeaveText      string `rethink:"guild_leave_text"`
	GuildLeaveEnabled   bool   `rethink:"guild_leave_enabled"`
}

func (m *GuildAnnouncements) Commands() []string {
	return []string{
		"guildannouncements",
	}
}

func (m *GuildAnnouncements) Init(session *discordgo.Session) {

}

func (m *GuildAnnouncements) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	args := strings.Fields(content)
	if len(args) >= 2 {
		switch args[0] {
		case "set":
			switch args[1] {
			case "guild_join":
				helpers.RequireAdmin(msg, func() {
					sourceChannel, err := helpers.GetChannel(msg.ChannelID)
					helpers.Relax(err)
					guildAnnouncementSetting := m.getEntryByOrCreateEmpty("guildid", sourceChannel.GuildID)
					guildAnnouncementSetting.GuildID = sourceChannel.GuildID
					var successMessage string
					// Add Text
					if len(args) >= 4 {
						targetChannel, err := helpers.GetChannelFromMention(msg, args[2])
						if err != nil || targetChannel.ID == "" {
							session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
							return
						}

						newText := strings.TrimSpace(strings.Replace(content, strings.Join(args[:3], " "), "", 1))
						guildAnnouncementSetting.GuildJoinEnabled = true
						guildAnnouncementSetting.GuildJoinChannelID = targetChannel.ID
						guildAnnouncementSetting.GuildJoinText = newText
						successMessage = helpers.GetText("plugins.guildannouncements.message-edited")
					} else {
						// Remove Text
						guildAnnouncementSetting.GuildJoinEnabled = false
						successMessage = helpers.GetText("plugins.guildannouncements.message-disabled")
					}
					m.setEntry(guildAnnouncementSetting)
					_, err = session.ChannelMessageSend(msg.ChannelID, successMessage)
					helpers.Relax(err)
				})
			case "guild_leave":
				helpers.RequireAdmin(msg, func() {
					sourceChannel, err := helpers.GetChannel(msg.ChannelID)
					helpers.Relax(err)

					guildAnnouncementSetting := m.getEntryByOrCreateEmpty("guildid", sourceChannel.GuildID)
					guildAnnouncementSetting.GuildID = sourceChannel.GuildID
					var successMessage string
					// Add Text
					if len(args) >= 4 {
						targetChannel, err := helpers.GetChannelFromMention(msg, args[2])
						if err != nil || targetChannel.ID == "" {
							session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
							return
						}

						newText := strings.TrimSpace(strings.Replace(content, strings.Join(args[:3], " "), "", 1))
						guildAnnouncementSetting.GuildLeaveEnabled = true
						guildAnnouncementSetting.GuildLeaveChannelID = targetChannel.ID
						guildAnnouncementSetting.GuildLeaveText = newText
						successMessage = helpers.GetText("plugins.guildannouncements.message-edited")
					} else {
						// Remove Text
						guildAnnouncementSetting.GuildLeaveEnabled = false
						successMessage = helpers.GetText("plugins.guildannouncements.message-disabled")
					}
					m.setEntry(guildAnnouncementSetting)
					_, err = session.ChannelMessageSend(msg.ChannelID, successMessage)
					helpers.Relax(err)
				})
			}
		}
	}

}

func (m *GuildAnnouncements) OnMessage(content string, msg *discordgo.Message, session *discordgo.Session) {

}

func (m *GuildAnnouncements) OnGuildMemberAdd(member *discordgo.Member, session *discordgo.Session) {
	go func() {
		defer helpers.Recover()

		guild, err := helpers.GetGuild(member.GuildID)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
			return
		}
		helpers.Relax(err)
		for _, guildAnnouncementSetting := range m.GetAnnouncementSettings() {
			if guildAnnouncementSetting.GuildJoinEnabled == true && guildAnnouncementSetting.GuildID == guild.ID {
				guildJoinChannelID := guildAnnouncementSetting.GuildJoinChannelID
				guildJoinText := m.ReplaceMemberText(guildAnnouncementSetting.GuildJoinText, member)
				if guildJoinText != "" {
					go func() {
						_, err := session.ChannelMessageSend(guildJoinChannelID, guildJoinText)
						if err != nil {
							cache.GetLogger().WithField("module", "guildannouncements").Error(fmt.Sprintf("Error Sending Join Message in %s #%s: %s",
								guild.Name, guild.ID, err.Error()))
						}
					}()
				}
			}
		}
		cache.GetLogger().WithField("module", "guildannouncements").Info(fmt.Sprintf("User %s (%s) joined Guild %s (#%s)", member.User.Username, member.User.ID, guild.Name, guild.ID))
	}()
}
func (m *GuildAnnouncements) OnGuildMemberRemove(member *discordgo.Member, session *discordgo.Session) {
	go func() {
		defer helpers.Recover()

		guild, err := helpers.GetGuild(member.GuildID)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
			return
		}
		for _, guildAnnouncementSetting := range m.GetAnnouncementSettings() {
			if guildAnnouncementSetting.GuildLeaveEnabled == true && guildAnnouncementSetting.GuildID == guild.ID {
				guildLeaveChannelID := guildAnnouncementSetting.GuildLeaveChannelID
				guildLeaveText := m.ReplaceMemberText(guildAnnouncementSetting.GuildLeaveText, member)
				if guildLeaveText != "" {
					go func() {
						_, err := session.ChannelMessageSend(guildLeaveChannelID, guildLeaveText)
						if err != nil {
							cache.GetLogger().WithField("module", "guildannouncements").Error(fmt.Sprintf("Error Sending Leave Message in %s #%s: %s",
								guild.Name, guild.ID, err.Error()))
						}
					}()
				}
			}
		}
		cache.GetLogger().WithField("module", "guildannouncements").Info(fmt.Sprintf("User %s (%s) left Guild %s (#%s)", member.User.Username, member.User.ID, guild.Name, guild.ID))
	}()
}

func (m *GuildAnnouncements) GetAnnouncementSettings() []Announcement_Setting {
	var entryBucket []Announcement_Setting
	cursor, err := rethink.Table("guild_announcements").Run(helpers.GetDB())
	helpers.Relax(err)

	err = cursor.All(&entryBucket)
	helpers.Relax(err)

	return entryBucket
}

func (m *GuildAnnouncements) ReplaceMemberText(text string, member *discordgo.Member) string {
	guild, err := cache.GetSession().Guild(member.GuildID)
	if errD, ok := err.(*discordgo.RESTError); ok {
		if errD.Message.Code != 50001 { // It's probably Robyul leaving a server :sob:
			return ""
		} else {
			helpers.Relax(err)
		}
	} else {
		helpers.Relax(err)
	}
	allMembers := make([]*discordgo.Member, 0)
	for _, botGuild := range cache.GetSession().State.Guilds {
		if botGuild.ID == guild.ID {
			for _, member := range guild.Members {
				if member.JoinedAt == "" {
					member, err := helpers.GetGuildMember(member.GuildID, member.User.ID)
					if err == nil && member.JoinedAt != "" {
						allMembers = append(allMembers, member)
					}
				} else {
					allMembers = append(allMembers, member)
				}
			}
		}
	}
	slice.Sort(allMembers[:], func(i, j int) bool {
		if allMembers[i].JoinedAt == "" || allMembers[j].JoinedAt == "" {
			return false
		}

		iMemberTime, err := discordgo.Timestamp(allMembers[i].JoinedAt).Parse()
		helpers.Relax(err)
		jMemberTime, err := discordgo.Timestamp(allMembers[j].JoinedAt).Parse()
		helpers.Relax(err)
		return iMemberTime.Before(jMemberTime)
	})

	userNumber := -1
	for i, sortedMember := range allMembers[:] {
		if sortedMember.User.ID == member.User.ID {
			userNumber = i + 1
			break
		}
	}

	text = strings.Replace(text, "{USER_USERNAME}", member.User.Username, -1)
	text = strings.Replace(text, "{USER_ID}", member.User.ID, -1)
	text = strings.Replace(text, "{USER_DISCRIMINATOR}", member.User.Discriminator, -1)
	text = strings.Replace(text, "{USER_NUMBER}", strconv.Itoa(userNumber), -1)
	text = strings.Replace(text, "{USER_MENTION}", fmt.Sprintf("<@%s>", member.User.ID), -1)
	text = strings.Replace(text, "{GUILD_NAME}", guild.Name, -1)
	text = strings.Replace(text, "{GUILD_ID}", guild.ID, -1)
	return text
}

func (m *GuildAnnouncements) getEntryByOrCreateEmpty(key string, id string) Announcement_Setting {
	var entryBucket Announcement_Setting
	listCursor, err := rethink.Table("guild_announcements").Filter(
		rethink.Row.Field(key).Eq(id),
	).Run(helpers.GetDB())
	if err != nil {
		panic(err)
	}
	defer listCursor.Close()
	err = listCursor.One(&entryBucket)

	// If user has no DB entries create an empty document
	if err == rethink.ErrEmptyResult {
		insert := rethink.Table("guild_announcements").Insert(Announcement_Setting{})
		res, e := insert.RunWrite(helpers.GetDB())
		// If the creation was successful read the document
		if e != nil {
			panic(e)
		} else {
			return m.getEntryByOrCreateEmpty("id", res.GeneratedKeys[0])
		}
	} else if err != nil {
		panic(err)
	}

	return entryBucket
}

func (m *GuildAnnouncements) setEntry(entry Announcement_Setting) {
	_, err := rethink.Table("guild_announcements").Update(entry).Run(helpers.GetDB())
	helpers.Relax(err)
}
func (m *GuildAnnouncements) OnReactionAdd(reaction *discordgo.MessageReactionAdd, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnReactionRemove(reaction *discordgo.MessageReactionRemove, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnGuildBanAdd(user *discordgo.GuildBanAdd, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnGuildBanRemove(user *discordgo.GuildBanRemove, session *discordgo.Session) {

}
func (m *GuildAnnouncements) OnMessageDelete(msg *discordgo.MessageDelete, session *discordgo.Session) {

}
<|endoftext|>


<commit_before>package hostess

import (
	"io/ioutil"
	"os"
	"strings"
)

const defaultOSX = `
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##

127.0.0.1       localhost
255.255.255.255 broadcasthost
::1             localhost
fe80::1%lo0     localhost
`

const defaultLinux = `
127.0.0.1   localhost
127.0.1.1   HOSTNAME

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
`

// Hostfile represents /etc/hosts (or a similar file, depending on OS), and
// includes a list of Hostnames. Hostfile includes
type Hostfile struct {
	Path  string
	Hosts Hostlist
	data  []byte
}

// NewHostfile creates a new Hostfile object from the specified file.
func NewHostfile(path string) *Hostfile {
	return &Hostfile{path, Hostlist{}, []byte{}}
}

// Read the contents of the hostfile from disk
func (h *Hostfile) Read() error {
	data, err := ioutil.ReadFile(h.Path)
	if err == nil {
		h.data = data
	}
	return err
}

// Parse reads
func (h *Hostfile) Parse() []error {
	var errs []error
	for _, v := range strings.Split(string(h.data), "\n") {
		for _, hostname := range ParseLine(v) {
			err := h.Hosts.Add(hostname)
			if err != nil {
				errs = append(errs, err)
			}
		}
	}
	return errs
}

// LoadHostfile creates a new Hostfile struct and tries to populate it from
// disk. Read and/or parse errors are returned as a slice.
func LoadHostfile() (hostfile *Hostfile, errs []error) {
	hostfile = NewHostfile(GetHostsPath())
	readErr := hostfile.Read()
	if readErr != nil {
		errs = []error{readErr}
		return
	}
	errs = hostfile.Parse()
	return
}

// TrimWS (Trim Whitespace) removes space, newline, and tabs from a string
// using strings.Trim()
func TrimWS(s string) string {
	return strings.Trim(s, " \n\t")
}

// ParseLine parses an individual line in a hostfile, which may contain one
// (un)commented ip and one or more hostnames. For example
//
//	127.0.0.1 localhost mysite1 mysite2
func ParseLine(line string) Hostlist {
	var hostnames Hostlist

	if len(line) == 0 {
		return hostnames
	}

	// Parse leading # for disabled lines
	enabled := true
	if line[0:1] == "#" {
		enabled = false
		line = TrimWS(line[1:])
	}

	// Parse other #s for actual comments
	line = strings.Split(line, "#")[0]

	// Replace tabs and multispaces with single spaces throughout
	line = strings.Replace(line, "\t", " ", -1)
	for strings.Contains(line, "  ") {
		line = strings.Replace(line, "  ", " ", -1)
	}

	// Break line into words
	words := strings.Split(line, " ")

	// Separate the first bit (the ip) from the other bits (the domains)
	ip := words[0]
	domains := words[1:]

	if LooksLikeIPv4(ip) || LooksLikeIPv6(ip) {
		for _, v := range domains {
			hostname := NewHostname(v, ip, enabled)
			hostnames = append(hostnames, hostname)
		}
	}

	return hostnames
}

// MoveToFront looks for string in a slice of strings and if it finds it, moves
// it to the front of the slice.
// Note: this could probably be made faster using pointers to switch the values
// instead of copying a bunch of crap, but it works and speed is not a problem.
func MoveToFront(list []string, search string) []string {
	for k, v := range list {
		if v == search {
			list = append(list[:k], list[k+1:]...)
		}
	}
	return append([]string{search}, list...)
}

// Format takes the current list of Hostnames in this Hostfile and turns it
// into a string suitable for use as an /etc/hosts file.
// Sorting uses the following logic:
// 1. List is sorted by IP address
// 2. Commented items are left in place
// 3. 127.* appears at the top of the list (so boot resolvers don't break)
// 4. When present, localhost will always appear first in the domain list
func (h *Hostfile) Format() string {
	// localhost := "127.0.0.1 localhost"

	localhosts := make(map[string][]string)
	ips := make(map[string][]string)

	// Map domains and IPs into slices of domains keyd by IP
	// 127.0.0.1 = [localhost, blah, blah2]
	// 2.2.2.3 = [domain1, domain2]
	for _, hostname := range h.Hosts {
		if hostname.IP.String()[0:4] == "127." {
			localhosts[hostname.IP.String()] = append(localhosts[hostname.IP.String()], hostname.Domain)
		} else {
			ips[hostname.IP.String()] = append(ips[hostname.IP.String()], hostname.Domain)
		}
	}

	// localhosts_keys := getSortedMapKeys(localhosts)
	// ips_keys := getSortedMapKeys(ips)
	var out []string

	// for _, ip := range localhosts_keys {
	// 	enabled := ip
	// 	enabled_b := false
	// 	disabled := "# " + ip
	// 	disabled_b := false
	// 	IP := net.ParseIP(ip)
	// 	for _, domain := range h.ListDomainsByIP(IP) {
	// 		hostname := *h.Hosts[domain]
	// 		if hostname.IP.Equal(IP) {
	// 			if hostname.Enabled {
	// 				enabled += " " + hostname.Domain
	// 				enabled_b = true
	// 			} else {
	// 				disabled += " " + hostname.Domain
	// 				disabled_b = true
	// 			}
	// 		}
	// 	}
	// 	if enabled_b {
	// 		out = append(out, enabled)
	// 	}
	// 	if disabled_b {
	// 		out = append(out, disabled)
	// 	}
	// }

	// for _, ip := range ips_keys {
	// 	enabled := ip
	// 	enabled_b := false
	// 	disabled := "# " + ip
	// 	disabled_b := false
	// 	IP := net.ParseIP(ip)
	// 	for _, domain := range h.ListDomainsByIP(IP) {
	// 		hostname := *h.Hosts[domain]
	// 		if hostname.IP.Equal(IP) {
	// 			if hostname.Enabled {
	// 				enabled += " " + hostname.Domain
	// 				enabled_b = true
	// 			} else {
	// 				disabled += " " + hostname.Domain
	// 				disabled_b = true
	// 			}
	// 		}
	// 	}
	// 	if enabled_b {
	// 		out = append(out, enabled)
	// 	}
	// 	if disabled_b {
	// 		out = append(out, disabled)
	// 	}
	// }

	return strings.Join(out, "\n")
}

// Save writes the Hostfile to disk to /etc/hosts or to the location specified
// by the HOSTESS_PATH environment variable (if set).
func (h *Hostfile) Save() error {
	// h.Format(h.Path)
	return nil
}

// GetHostsPath returns the location of the hostfile; either env HOSTESS_PATH
// or /etc/hosts if HOSTESS_PATH is not set.
func GetHostsPath() string {
	path := os.Getenv("HOSTESS_PATH")
	if path == "" {
		path = "/etc/hosts"
	}
	return path
}
<commit_msg>Rearranged hostfile methods so they're declared before they're used, and grouped more logically<commit_after>package hostess

import (
	"io/ioutil"
	"os"
	"strings"
)

const defaultOSX = `
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##

127.0.0.1       localhost
255.255.255.255 broadcasthost
::1             localhost
fe80::1%lo0     localhost
`

const defaultLinux = `
127.0.0.1   localhost
127.0.1.1   HOSTNAME

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
`

// Hostfile represents /etc/hosts (or a similar file, depending on OS), and
// includes a list of Hostnames. Hostfile includes
type Hostfile struct {
	Path  string
	Hosts Hostlist
	data  []byte
}

// NewHostfile creates a new Hostfile object from the specified file.
func NewHostfile(path string) *Hostfile {
	return &Hostfile{path, Hostlist{}, []byte{}}
}

// GetHostsPath returns the location of the hostfile; either env HOSTESS_PATH
// or /etc/hosts if HOSTESS_PATH is not set.
func GetHostsPath() string {
	path := os.Getenv("HOSTESS_PATH")
	if path == "" {
		path = "/etc/hosts"
	}
	return path
}

// TrimWS (Trim Whitespace) removes space, newline, and tabs from a string
// using strings.Trim()
func TrimWS(s string) string {
	return strings.Trim(s, " \n\t")
}

// ParseLine parses an individual line in a hostfile, which may contain one
// (un)commented ip and one or more hostnames. For example
//
//	127.0.0.1 localhost mysite1 mysite2
func ParseLine(line string) Hostlist {
	var hostnames Hostlist

	if len(line) == 0 {
		return hostnames
	}

	// Parse leading # for disabled lines
	enabled := true
	if line[0:1] == "#" {
		enabled = false
		line = TrimWS(line[1:])
	}

	// Parse other #s for actual comments
	line = strings.Split(line, "#")[0]

	// Replace tabs and multispaces with single spaces throughout
	line = strings.Replace(line, "\t", " ", -1)
	for strings.Contains(line, "  ") {
		line = strings.Replace(line, "  ", " ", -1)
	}

	// Break line into words
	words := strings.Split(line, " ")

	// Separate the first bit (the ip) from the other bits (the domains)
	ip := words[0]
	domains := words[1:]

	if LooksLikeIPv4(ip) || LooksLikeIPv6(ip) {
		for _, v := range domains {
			hostname := NewHostname(v, ip, enabled)
			hostnames = append(hostnames, hostname)
		}
	}

	return hostnames
}

// Parse reads
func (h *Hostfile) Parse() []error {
	var errs []error
	for _, v := range strings.Split(string(h.data), "\n") {
		for _, hostname := range ParseLine(v) {
			err := h.Hosts.Add(hostname)
			if err != nil {
				errs = append(errs, err)
			}
		}
	}
	return errs
}

// Read the contents of the hostfile from disk
func (h *Hostfile) Read() error {
	data, err := ioutil.ReadFile(h.Path)
	if err == nil {
		h.data = data
	}
	return err
}

// LoadHostfile creates a new Hostfile struct and tries to populate it from
// disk. Read and/or parse errors are returned as a slice.
func LoadHostfile() (hostfile *Hostfile, errs []error) {
	hostfile = NewHostfile(GetHostsPath())
	readErr := hostfile.Read()
	if readErr != nil {
		errs = []error{readErr}
		return
	}
	errs = hostfile.Parse()
	return
}

// MoveToFront looks for string in a slice of strings and if it finds it, moves
// it to the front of the slice.
// Note: this could probably be made faster using pointers to switch the values
// instead of copying a bunch of crap, but it works and speed is not a problem.
func MoveToFront(list []string, search string) []string {
	for k, v := range list {
		if v == search {
			list = append(list[:k], list[k+1:]...)
		}
	}
	return append([]string{search}, list...)
}

// Format takes the current list of Hostnames in this Hostfile and turns it
// into a string suitable for use as an /etc/hosts file.
// Sorting uses the following logic:
// 1. List is sorted by IP address
// 2. Commented items are left in place
// 3. 127.* appears at the top of the list (so boot resolvers don't break)
// 4. When present, localhost will always appear first in the domain list
func (h *Hostfile) Format() string {
	// localhost := "127.0.0.1 localhost"

	localhosts := make(map[string][]string)
	ips := make(map[string][]string)

	// Map domains and IPs into slices of domains keyd by IP
	// 127.0.0.1 = [localhost, blah, blah2]
	// 2.2.2.3 = [domain1, domain2]
	for _, hostname := range h.Hosts {
		if hostname.IP.String()[0:4] == "127." {
			localhosts[hostname.IP.String()] = append(localhosts[hostname.IP.String()], hostname.Domain)
		} else {
			ips[hostname.IP.String()] = append(ips[hostname.IP.String()], hostname.Domain)
		}
	}

	// localhosts_keys := getSortedMapKeys(localhosts)
	// ips_keys := getSortedMapKeys(ips)
	var out []string

	// for _, ip := range localhosts_keys {
	// 	enabled := ip
	// 	enabled_b := false
	// 	disabled := "# " + ip
	// 	disabled_b := false
	// 	IP := net.ParseIP(ip)
	// 	for _, domain := range h.ListDomainsByIP(IP) {
	// 		hostname := *h.Hosts[domain]
	// 		if hostname.IP.Equal(IP) {
	// 			if hostname.Enabled {
	// 				enabled += " " + hostname.Domain
	// 				enabled_b = true
	// 			} else {
	// 				disabled += " " + hostname.Domain
	// 				disabled_b = true
	// 			}
	// 		}
	// 	}
	// 	if enabled_b {
	// 		out = append(out, enabled)
	// 	}
	// 	if disabled_b {
	// 		out = append(out, disabled)
	// 	}
	// }

	// for _, ip := range ips_keys {
	// 	enabled := ip
	// 	enabled_b := false
	// 	disabled := "# " + ip
	// 	disabled_b := false
	// 	IP := net.ParseIP(ip)
	// 	for _, domain := range h.ListDomainsByIP(IP) {
	// 		hostname := *h.Hosts[domain]
	// 		if hostname.IP.Equal(IP) {
	// 			if hostname.Enabled {
	// 				enabled += " " + hostname.Domain
	// 				enabled_b = true
	// 			} else {
	// 				disabled += " " + hostname.Domain
	// 				disabled_b = true
	// 			}
	// 		}
	// 	}
	// 	if enabled_b {
	// 		out = append(out, enabled)
	// 	}
	// 	if disabled_b {
	// 		out = append(out, disabled)
	// 	}
	// }

	return strings.Join(out, "\n")
}

// Save writes the Hostfile to disk to /etc/hosts or to the location specified
// by the HOSTESS_PATH environment variable (if set).
func (h *Hostfile) Save() error {
	// h.Format(h.Path)
	return nil
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import log "github.com/Sirupsen/logrus"
import hue "github.com/stefanwichmann/go.hue"
import "strconv"
import "time"
import "errors"

var lightsSupportingDimming = []string{"Dimmable Light", "Color Temperature Light", "Color Light", "Extended Color Light"}
var lightsSupportingColorTemperature = []string{"Color Temperature Light", "Extended Color Light"}
var lightsSupportingXYColor = []string{"Color Light", "Extended Color Light"}

// HueLight represents a physical hue light.
type HueLight struct {
	Name                     string
	HueLight                 hue.Light
	SetColorTemperature      int
	SetBrightness            int
	TargetColorTemperature   int
	TargetColor              []float32
	TargetBrightness         int
	CurrentColorTemperature  int
	CurrentColor             []float32
	CurrentBrightness        int
	CurrentColorMode         string
	SupportsColorTemperature bool
	SupportsXYColor          bool
	Dimmable                 bool
	Reachable                bool
	On                       bool
	MinimumColorTemperature  int
}

const lightTransitionIntervalInSeconds = 1

func (light *HueLight) initialize(attr hue.LightAttributes) {
	// initialize non changing values
	light.Name = attr.Name
	light.Dimmable = containsString(lightsSupportingDimming, attr.Type)
	light.SupportsColorTemperature = containsString(lightsSupportingColorTemperature, attr.Type)
	light.SupportsXYColor = containsString(lightsSupportingXYColor, attr.Type)

	// set minimum color temperature depending on type
	if attr.Type == "Color Temperature Light" {
		light.MinimumColorTemperature = 2200
	} else if light.SupportsXYColor || light.SupportsColorTemperature {
		light.MinimumColorTemperature = 2000
	} else {
		light.MinimumColorTemperature = 0
	}

	log.Debugf("💡 Light %s - Initialization complete. Identified as %s (ModelID: %s, Version: %s)", light.Name, attr.Type, attr.ModelId, attr.SoftwareVersion)

	light.updateCurrentLightState(attr)
}

func (light *HueLight) supportsColorTemperature() bool {
	if light.SupportsXYColor || light.SupportsColorTemperature {
		return true
	}
	return false
}

func (light *HueLight) supportsBrightness() bool {
	if light.Dimmable {
		return true
	}
	return false
}

func (light *HueLight) updateCurrentLightState(attr hue.LightAttributes) {
	light.CurrentColorTemperature = attr.State.Ct

	var color []float32
	for _, value := range attr.State.Xy {
		color = append(color, roundFloat(value, 3))
	}
	light.CurrentColor = color
	light.CurrentBrightness = attr.State.Bri
	light.CurrentColorMode = attr.State.ColorMode

	if !attr.State.Reachable {
		light.Reachable = false
		light.On = false
	} else {
		light.Reachable = true
		light.On = attr.State.On
	}
}

func (light *HueLight) setLightState(colorTemperature int, brightness int) error {
	if colorTemperature != -1 && (colorTemperature < 2000 || colorTemperature > 6500) {
		log.Warningf("💡 Light %s - Invalid color temperature %d", light.Name, colorTemperature)
	}
	if brightness < -1 || brightness > 100 {
		log.Warningf("💡 Light %s - Invalid brightness %d", light.Name, brightness)
	}

	if colorTemperature != -1 && colorTemperature < light.MinimumColorTemperature {
		colorTemperature = light.MinimumColorTemperature
		log.Debugf("💡 Light %s - Adjusted color temperature to light capability of %dK", light.Name, colorTemperature)
	}

	light.SetColorTemperature = colorTemperature
	light.SetBrightness = brightness

	// map parameters to target values
	light.TargetColorTemperature = mapColorTemperature(colorTemperature)
	light.TargetColor = colorTemperatureToXYColor(colorTemperature)
	light.TargetBrightness = mapBrightness(brightness)

	// Send new state to light bulb
	var hueLightState hue.SetLightState

	if colorTemperature != -1 {
		// Set supported colormodes. If both are, the brigde will prefer xy colors
		if light.SupportsXYColor {
			hueLightState.Xy = light.TargetColor
		}
		if light.SupportsColorTemperature {
			hueLightState.Ct = strconv.Itoa(light.TargetColorTemperature)
		}
	}

	if brightness != -1 {
		if brightness == 0 {
			// Target brightness zero should turn the light off.
			hueLightState.On = "Off"
		} else if light.Dimmable {
			hueLightState.Bri = strconv.Itoa(light.TargetBrightness)
		}
	}
	hueLightState.TransitionTime = strconv.Itoa(lightTransitionIntervalInSeconds * 10) // Conversion to 100ms-value

	// Send new state to the light
	log.Debugf("💡 HueLight %s - Setting light state to %dK and %d%% brightness (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, colorTemperature, brightness, light.TargetColorTemperature, light.CurrentColorTemperature, light.TargetColor, light.CurrentColor, light.TargetBrightness, light.CurrentBrightness)
	result, err := light.HueLight.SetState(hueLightState)
	if err != nil {
		log.Warningf("💡 HueLight %s - Setting light state failed: %v (Result: %v)", light.Name, err, result)
		return err
	}

	// Debug: Update current state to double check
	if log.GetLevel() == log.DebugLevel {
		// Wait while the light is in transition before checking again
		time.Sleep(lightTransitionIntervalInSeconds + 1*time.Second)

		attr, err := light.HueLight.GetLightAttributes()
		if err != nil {
			return err
		}
		light.updateCurrentLightState(*attr)
		if light.hasChanged() {
			log.Warningf("💡 HueLight %s - Failed to update light state: %+v", light.Name, light)
			return errors.New("Light state update double-check failed")
		}
	}

	log.Debugf("💡 HueLight %s - Light was successfully updated (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, light.TargetColorTemperature, light.CurrentColorTemperature, light.TargetColor, light.CurrentColor, light.TargetBrightness, light.CurrentBrightness)
	return nil
}

func (light *HueLight) hasChanged() bool {
	if light.SupportsXYColor && light.CurrentColorMode == "xy" {
		if !equalsFloat(light.TargetColor, []float32{-1, -1}, 0) && !equalsFloat(light.TargetColor, light.CurrentColor, 0.001) {
			log.Debugf("💡 HueLight %s - Color has changed! CurrentColor: %v, TargetColor: %v (%dK)", light.Name, light.CurrentColor, light.TargetColor, light.SetColorTemperature)
			return true
		}
	} else if light.SupportsColorTemperature && light.CurrentColorMode == "ct" {
		if light.TargetColorTemperature != -1 && !equalsInt(light.TargetColorTemperature, light.CurrentColorTemperature, 2) {
			log.Debugf("💡 HueLight %s - Color temperature has changed! CurrentColorTemperature: %d, TargetColorTemperatur: %d (%dK)", light.Name, light.CurrentColorTemperature, light.TargetColorTemperature, light.SetColorTemperature)
			return true
		}
	}

	if light.Dimmable && light.TargetBrightness != -1 && !equalsInt(light.TargetBrightness, light.CurrentBrightness, 2) {
		log.Debugf("💡 HueLight %s - Brightness has changed! CurrentBrightness: %d, TargetBrightness: %d (%d%%)", light.Name, light.CurrentBrightness, light.TargetBrightness, light.SetBrightness)
		return true
	}

	return false
}

func (light *HueLight) hasState(colorTemperature int, brightness int) bool {
	return light.hasColorTemperature(colorTemperature) && light.hasBrightness(brightness)
}

func (light *HueLight) hasColorTemperature(colorTemperature int) bool {
	if colorTemperature == -1 || light.TargetColorTemperature == -1 {
		return true
	}
	if !light.SupportsXYColor && !light.SupportsColorTemperature {
		return true
	}

	if colorTemperature < light.MinimumColorTemperature {
		colorTemperature = light.MinimumColorTemperature
		log.Debugf("💡 Light %s - Adjusted color temperature to light capability of %dK", light.Name, colorTemperature)
	}

	if light.SupportsXYColor && light.CurrentColorMode == "xy" {
		if equalsFloat(colorTemperatureToXYColor(colorTemperature), light.CurrentColor, 0.001) {
			return true
		}
		return false
	} else if light.SupportsColorTemperature && light.CurrentColorMode == "ct" {
		if equalsInt(light.CurrentColorTemperature, mapColorTemperature(colorTemperature), 2) {
			return true
		}
		return false
	}

	// Missmatch in color modes? Log warning for debug purposes and assume unchanged
	log.Warningf("💡 HueLight %s - Unknown color mode in HasColorTemperature method! Current light state: %+v", light.Name, light)

	return true
}

func (light *HueLight) hasBrightness(brightness int) bool {
	if brightness == -1 || light.TargetBrightness == -1 {
		return true
	}
	if !light.Dimmable {
		return true
	}
	if !equalsInt(light.CurrentBrightness, mapBrightness(brightness), 2) {
		return false
	}
	return true
}

func (light *HueLight) getCurrentColorTemperature() (int, error) {
	if !light.hasChanged() {
		return light.SetColorTemperature, nil
	}

	if light.CurrentColorTemperature >= 153 && light.CurrentColorTemperature <= 500 {
		return int(float64(1000000) / float64(light.CurrentColorTemperature)), nil
	}

	return 0, errors.New("Could not determine current color temperature")
}

func (light *HueLight) getCurrentBrightness() (int, error) {
	if !light.hasChanged() {
		return light.SetBrightness, nil
	}

	if light.CurrentBrightness >= 1 && light.CurrentBrightness <= 254 {
		return int((float64(light.CurrentBrightness) / float64(254)) * float64(100)), nil
	}

	return 0, errors.New("Could not determine current brightness")
}

func mapColorTemperature(colorTemperature int) int {
	if colorTemperature == -1 {
		return -1
	}

	if colorTemperature > 6500 {
		colorTemperature = 6500
	} else if colorTemperature < 2000 {
		colorTemperature = 2000
	}
	return int((float64(1) / float64(colorTemperature)) * float64(1000000))
}

func mapBrightness(brightness int) int {
	if brightness == -1 {
		return -1
	}

	if brightness > 100 {
		brightness = 100
	} else if brightness < 0 {
		brightness = 0
	}
	return int((float64(brightness) / float64(100)) * float64(254))
}
<commit_msg>Remove light transition time and double check in debug mode<commit_after>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import log "github.com/Sirupsen/logrus"
import hue "github.com/stefanwichmann/go.hue"
import "strconv"

import "errors"

var lightsSupportingDimming = []string{"Dimmable Light", "Color Temperature Light", "Color Light", "Extended Color Light"}
var lightsSupportingColorTemperature = []string{"Color Temperature Light", "Extended Color Light"}
var lightsSupportingXYColor = []string{"Color Light", "Extended Color Light"}

// HueLight represents a physical hue light.
type HueLight struct {
	Name                     string
	HueLight                 hue.Light
	SetColorTemperature      int
	SetBrightness            int
	TargetColorTemperature   int
	TargetColor              []float32
	TargetBrightness         int
	CurrentColorTemperature  int
	CurrentColor             []float32
	CurrentBrightness        int
	CurrentColorMode         string
	SupportsColorTemperature bool
	SupportsXYColor          bool
	Dimmable                 bool
	Reachable                bool
	On                       bool
	MinimumColorTemperature  int
}

func (light *HueLight) initialize(attr hue.LightAttributes) {
	// initialize non changing values
	light.Name = attr.Name
	light.Dimmable = containsString(lightsSupportingDimming, attr.Type)
	light.SupportsColorTemperature = containsString(lightsSupportingColorTemperature, attr.Type)
	light.SupportsXYColor = containsString(lightsSupportingXYColor, attr.Type)

	// set minimum color temperature depending on type
	if attr.Type == "Color Temperature Light" {
		light.MinimumColorTemperature = 2200
	} else if light.SupportsXYColor || light.SupportsColorTemperature {
		light.MinimumColorTemperature = 2000
	} else {
		light.MinimumColorTemperature = 0
	}

	log.Debugf("💡 Light %s - Initialization complete. Identified as %s (ModelID: %s, Version: %s)", light.Name, attr.Type, attr.ModelId, attr.SoftwareVersion)

	light.updateCurrentLightState(attr)
}

func (light *HueLight) supportsColorTemperature() bool {
	if light.SupportsXYColor || light.SupportsColorTemperature {
		return true
	}
	return false
}

func (light *HueLight) supportsBrightness() bool {
	if light.Dimmable {
		return true
	}
	return false
}

func (light *HueLight) updateCurrentLightState(attr hue.LightAttributes) {
	light.CurrentColorTemperature = attr.State.Ct

	var color []float32
	for _, value := range attr.State.Xy {
		color = append(color, roundFloat(value, 3))
	}
	light.CurrentColor = color
	light.CurrentBrightness = attr.State.Bri
	light.CurrentColorMode = attr.State.ColorMode

	if !attr.State.Reachable {
		light.Reachable = false
		light.On = false
	} else {
		light.Reachable = true
		light.On = attr.State.On
	}
}

func (light *HueLight) setLightState(colorTemperature int, brightness int) error {
	if colorTemperature != -1 && (colorTemperature < 2000 || colorTemperature > 6500) {
		log.Warningf("💡 Light %s - Invalid color temperature %d", light.Name, colorTemperature)
	}
	if brightness < -1 || brightness > 100 {
		log.Warningf("💡 Light %s - Invalid brightness %d", light.Name, brightness)
	}

	if colorTemperature != -1 && colorTemperature < light.MinimumColorTemperature {
		colorTemperature = light.MinimumColorTemperature
		log.Debugf("💡 Light %s - Adjusted color temperature to light capability of %dK", light.Name, colorTemperature)
	}

	light.SetColorTemperature = colorTemperature
	light.SetBrightness = brightness

	// map parameters to target values
	light.TargetColorTemperature = mapColorTemperature(colorTemperature)
	light.TargetColor = colorTemperatureToXYColor(colorTemperature)
	light.TargetBrightness = mapBrightness(brightness)

	// Send new state to light bulb
	var hueLightState hue.SetLightState

	if colorTemperature != -1 {
		// Set supported colormodes. If both are, the brigde will prefer xy colors
		if light.SupportsXYColor {
			hueLightState.Xy = light.TargetColor
		}
		if light.SupportsColorTemperature {
			hueLightState.Ct = strconv.Itoa(light.TargetColorTemperature)
		}
	}

	if brightness != -1 {
		if brightness == 0 {
			// Target brightness zero should turn the light off.
			hueLightState.On = "Off"
		} else if light.Dimmable {
			hueLightState.Bri = strconv.Itoa(light.TargetBrightness)
		}
	}

	// Send new state to the light
	log.Debugf("💡 HueLight %s - Setting light state to %dK and %d%% brightness (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, colorTemperature, brightness, light.TargetColorTemperature, light.CurrentColorTemperature, light.TargetColor, light.CurrentColor, light.TargetBrightness, light.CurrentBrightness)
	result, err := light.HueLight.SetState(hueLightState)
	if err != nil {
		log.Warningf("💡 HueLight %s - Setting light state failed: %v (Result: %v)", light.Name, err, result)
		return err
	}

	log.Debugf("💡 HueLight %s - Light was successfully updated (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, light.TargetColorTemperature, light.CurrentColorTemperature, light.TargetColor, light.CurrentColor, light.TargetBrightness, light.CurrentBrightness)
	return nil
}

func (light *HueLight) hasChanged() bool {
	if light.SupportsXYColor && light.CurrentColorMode == "xy" {
		if !equalsFloat(light.TargetColor, []float32{-1, -1}, 0) && !equalsFloat(light.TargetColor, light.CurrentColor, 0.001) {
			log.Debugf("💡 HueLight %s - Color has changed! CurrentColor: %v, TargetColor: %v (%dK)", light.Name, light.CurrentColor, light.TargetColor, light.SetColorTemperature)
			return true
		}
	} else if light.SupportsColorTemperature && light.CurrentColorMode == "ct" {
		if light.TargetColorTemperature != -1 && !equalsInt(light.TargetColorTemperature, light.CurrentColorTemperature, 2) {
			log.Debugf("💡 HueLight %s - Color temperature has changed! CurrentColorTemperature: %d, TargetColorTemperatur: %d (%dK)", light.Name, light.CurrentColorTemperature, light.TargetColorTemperature, light.SetColorTemperature)
			return true
		}
	}

	if light.Dimmable && light.TargetBrightness != -1 && !equalsInt(light.TargetBrightness, light.CurrentBrightness, 2) {
		log.Debugf("💡 HueLight %s - Brightness has changed! CurrentBrightness: %d, TargetBrightness: %d (%d%%)", light.Name, light.CurrentBrightness, light.TargetBrightness, light.SetBrightness)
		return true
	}

	return false
}

func (light *HueLight) hasState(colorTemperature int, brightness int) bool {
	return light.hasColorTemperature(colorTemperature) && light.hasBrightness(brightness)
}

func (light *HueLight) hasColorTemperature(colorTemperature int) bool {
	if colorTemperature == -1 || light.TargetColorTemperature == -1 {
		return true
	}
	if !light.SupportsXYColor && !light.SupportsColorTemperature {
		return true
	}

	if colorTemperature < light.MinimumColorTemperature {
		colorTemperature = light.MinimumColorTemperature
		log.Debugf("💡 Light %s - Adjusted color temperature to light capability of %dK", light.Name, colorTemperature)
	}

	if light.SupportsXYColor && light.CurrentColorMode == "xy" {
		if equalsFloat(colorTemperatureToXYColor(colorTemperature), light.CurrentColor, 0.001) {
			return true
		}
		return false
	} else if light.SupportsColorTemperature && light.CurrentColorMode == "ct" {
		if equalsInt(light.CurrentColorTemperature, mapColorTemperature(colorTemperature), 2) {
			return true
		}
		return false
	}

	// Missmatch in color modes? Log warning for debug purposes and assume unchanged
	log.Warningf("💡 HueLight %s - Unknown color mode in HasColorTemperature method! Current light state: %+v", light.Name, light)

	return true
}

func (light *HueLight) hasBrightness(brightness int) bool {
	if brightness == -1 || light.TargetBrightness == -1 {
		return true
	}
	if !light.Dimmable {
		return true
	}
	if !equalsInt(light.CurrentBrightness, mapBrightness(brightness), 2) {
		return false
	}
	return true
}

func (light *HueLight) getCurrentColorTemperature() (int, error) {
	if !light.hasChanged() {
		return light.SetColorTemperature, nil
	}

	if light.CurrentColorTemperature >= 153 && light.CurrentColorTemperature <= 500 {
		return int(float64(1000000) / float64(light.CurrentColorTemperature)), nil
	}

	return 0, errors.New("Could not determine current color temperature")
}

func (light *HueLight) getCurrentBrightness() (int, error) {
	if !light.hasChanged() {
		return light.SetBrightness, nil
	}

	if light.CurrentBrightness >= 1 && light.CurrentBrightness <= 254 {
		return int((float64(light.CurrentBrightness) / float64(254)) * float64(100)), nil
	}

	return 0, errors.New("Could not determine current brightness")
}

func mapColorTemperature(colorTemperature int) int {
	if colorTemperature == -1 {
		return -1
	}

	if colorTemperature > 6500 {
		colorTemperature = 6500
	} else if colorTemperature < 2000 {
		colorTemperature = 2000
	}
	return int((float64(1) / float64(colorTemperature)) * float64(1000000))
}

func mapBrightness(brightness int) int {
	if brightness == -1 {
		return -1
	}

	if brightness > 100 {
		brightness = 100
	} else if brightness < 0 {
		brightness = 0
	}
	return int((float64(brightness) / float64(100)) * float64(254))
}
<|endoftext|>