<commit_before>package gandi

import (
	"fmt"

	gandiclient "github.com/prasmussen/gandi-api/client"
	gandidomain "github.com/prasmussen/gandi-api/domain"
	gandizone "github.com/prasmussen/gandi-api/domain/zone"
	gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
	gandiversion "github.com/prasmussen/gandi-api/domain/zone/version"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/miekg/dns/dnsutil"
)

// fetchDomainList gets list of domains for account. Cache ids for easy lookup.
func (c *GandiApi) fetchDomainList() error {
	if c.domainIndex != nil {
		return nil
	}
	c.domainIndex = map[string]int64{}
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	domain := gandidomain.New(gc)
	domains, err := domain.List()
	if err != nil {
		//	fmt.Println(err)
		return err
	}
	for _, d := range domains {
		c.domainIndex[d.Fqdn] = d.Id
	}
	return nil
}

// fetchDomainInfo gets information about a domain.
func (c *GandiApi) fetchDomainInfo(fqdn string) (*gandidomain.DomainInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	domain := gandidomain.New(gc)
	return domain.Info(fqdn)
}

// getRecordsForDomain returns a list of records for a zone.
func (c *GandiApi) getZoneRecords(zoneid int64, origin string) ([]*models.RecordConfig, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	record := gandirecord.New(gc)
	recs, err := record.List(zoneid, 0)
	if err != nil {
		return nil, err
	}
	rcs := make([]*models.RecordConfig, 0, len(recs))
	for _, r := range recs {
		rcs = append(rcs, convert(r, origin))
	}
	return rcs, nil
}

// listZones retrieves the list of zones.
func (c *GandiApi) listZones() ([]*gandizone.ZoneInfoBase, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.List()
}

// setZone assigns a particular zone to a domain.
func (c *GandiApi) setZones(domainname string, zone_id int64) (*gandidomain.DomainInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.Set(domainname, zone_id)
}

// getZoneInfo gets ZoneInfo about a zone.
func (c *GandiApi) getZoneInfo(zoneid int64) (*gandizone.ZoneInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.Info(zoneid)
}

// createZone creates an entirely new zone.
func (c *GandiApi) createZone(name string) (*gandizone.ZoneInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.Create(name)
}

func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) {
	var zone_id int64
	if zoneinfo.Domains < 2 {
		// If there is only on{ domain linked to this zone, use it.
		zone_id = zoneinfo.Id
		fmt.Printf("Using zone id=%d named %#v\n", zone_id, zoneinfo.Name)
		return zone_id, nil
	}

	// We can't use the zone_id given to us. Let's make/find a new one.
	zones, err := c.listZones()
	if err != nil {
		return 0, err
	}
	zonename := fmt.Sprintf("%s dnscontrol", domainname)
	for _, z := range zones {
		if z.Name == zonename {
			zone_id = z.Id
			fmt.Printf("Recycling zone id=%d named %#v\n", zone_id, z.Name)
			return zone_id, nil
		}
	}
	zoneinfo, err = c.createZone(zonename)
	if err != nil {
		return 0, err
	}
	zone_id = zoneinfo.Id
	fmt.Printf("Created zone id=%d named %#v\n", zone_id, zoneinfo.Name)
	return zone_id, nil
}

// makeEditableZone
func (c *GandiApi) makeEditableZone(zone_id int64) (int64, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	version := gandiversion.New(gc)
	return version.New(zone_id, 0)
}

// setZoneRecords
func (c *GandiApi) setZoneRecords(zone_id, version_id int64, records []gandirecord.RecordSet) ([]*gandirecord.RecordInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	record := gandirecord.New(gc)
	return record.SetRecords(zone_id, version_id, records)
}

// activateVersion
func (c *GandiApi) activateVersion(zone_id, version_id int64) (bool, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	version := gandiversion.New(gc)
	return version.Set(zone_id, version_id)
}

func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []gandirecord.RecordSet) error {

	// Get the zone_id of the zone we'll be updating.
	zoneinfo, err := c.getZoneInfo(zoneID)
	if err != nil {
		return err
	}
	//fmt.Println("ZONEINFO:", zoneinfo)
	zoneID, err = c.getEditableZone(domainname, zoneinfo)
	if err != nil {
		return err
	}

	// Get the versionID of the zone we're updating.
	versionID, err := c.makeEditableZone(zoneID)
	if err != nil {
		return err
	}

	// Update the new version.
	_, err = c.setZoneRecords(zoneID, versionID, records)
	if err != nil {
		return err
	}

	// Activate zone version
	_, err = c.activateVersion(zoneID, versionID)
	if err != nil {
		return err
	}
	_, err = c.setZones(domainname, zoneID)
	if err != nil {
		return err
	}

	return nil
}

func convert(r *gandirecord.RecordInfo, origin string) *models.RecordConfig {
	rc := &models.RecordConfig{
		NameFQDN: dnsutil.AddOrigin(r.Name, origin),
		Name:     r.Name,
		Type:     r.Type,
		Original: r,
		Target:   r.Value,
		TTL:      uint32(r.Ttl),
	}
	switch r.Type {
	case "A", "AAAA", "NS", "CNAME", "TXT":
		// no-op
	case "MX":
		var err error
		rc.MxPreference, rc.Target, err = models.SplitCombinedMxValue(r.Value)
		if err != nil {
			panic(fmt.Sprintf("gandi.convert bad mx value format: %#v", r.Value))
		}
	default:
		panic(fmt.Sprintf("gandi.convert unimplemented rtype %v", r.Type))
		// We panic so that we quickly find any switch statements
		// that have not been updated for a new RR type.
	}
	return rc
}
<commit_msg>Gandi: Fix broken PTR tests. (#188)<commit_after>package gandi

import (
	"fmt"

	gandiclient "github.com/prasmussen/gandi-api/client"
	gandidomain "github.com/prasmussen/gandi-api/domain"
	gandizone "github.com/prasmussen/gandi-api/domain/zone"
	gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
	gandiversion "github.com/prasmussen/gandi-api/domain/zone/version"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/miekg/dns/dnsutil"
)

// fetchDomainList gets list of domains for account. Cache ids for easy lookup.
func (c *GandiApi) fetchDomainList() error {
	if c.domainIndex != nil {
		return nil
	}
	c.domainIndex = map[string]int64{}
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	domain := gandidomain.New(gc)
	domains, err := domain.List()
	if err != nil {
		//	fmt.Println(err)
		return err
	}
	for _, d := range domains {
		c.domainIndex[d.Fqdn] = d.Id
	}
	return nil
}

// fetchDomainInfo gets information about a domain.
func (c *GandiApi) fetchDomainInfo(fqdn string) (*gandidomain.DomainInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	domain := gandidomain.New(gc)
	return domain.Info(fqdn)
}

// getRecordsForDomain returns a list of records for a zone.
func (c *GandiApi) getZoneRecords(zoneid int64, origin string) ([]*models.RecordConfig, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	record := gandirecord.New(gc)
	recs, err := record.List(zoneid, 0)
	if err != nil {
		return nil, err
	}
	rcs := make([]*models.RecordConfig, 0, len(recs))
	for _, r := range recs {
		rcs = append(rcs, convert(r, origin))
	}
	return rcs, nil
}

// listZones retrieves the list of zones.
func (c *GandiApi) listZones() ([]*gandizone.ZoneInfoBase, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.List()
}

// setZone assigns a particular zone to a domain.
func (c *GandiApi) setZones(domainname string, zone_id int64) (*gandidomain.DomainInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.Set(domainname, zone_id)
}

// getZoneInfo gets ZoneInfo about a zone.
func (c *GandiApi) getZoneInfo(zoneid int64) (*gandizone.ZoneInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.Info(zoneid)
}

// createZone creates an entirely new zone.
func (c *GandiApi) createZone(name string) (*gandizone.ZoneInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	zone := gandizone.New(gc)
	return zone.Create(name)
}

func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) {
	var zone_id int64
	if zoneinfo.Domains < 2 {
		// If there is only on{ domain linked to this zone, use it.
		zone_id = zoneinfo.Id
		fmt.Printf("Using zone id=%d named %#v\n", zone_id, zoneinfo.Name)
		return zone_id, nil
	}

	// We can't use the zone_id given to us. Let's make/find a new one.
	zones, err := c.listZones()
	if err != nil {
		return 0, err
	}
	zonename := fmt.Sprintf("%s dnscontrol", domainname)
	for _, z := range zones {
		if z.Name == zonename {
			zone_id = z.Id
			fmt.Printf("Recycling zone id=%d named %#v\n", zone_id, z.Name)
			return zone_id, nil
		}
	}
	zoneinfo, err = c.createZone(zonename)
	if err != nil {
		return 0, err
	}
	zone_id = zoneinfo.Id
	fmt.Printf("Created zone id=%d named %#v\n", zone_id, zoneinfo.Name)
	return zone_id, nil
}

// makeEditableZone
func (c *GandiApi) makeEditableZone(zone_id int64) (int64, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	version := gandiversion.New(gc)
	return version.New(zone_id, 0)
}

// setZoneRecords
func (c *GandiApi) setZoneRecords(zone_id, version_id int64, records []gandirecord.RecordSet) ([]*gandirecord.RecordInfo, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	record := gandirecord.New(gc)
	return record.SetRecords(zone_id, version_id, records)
}

// activateVersion
func (c *GandiApi) activateVersion(zone_id, version_id int64) (bool, error) {
	gc := gandiclient.New(c.ApiKey, gandiclient.Production)
	version := gandiversion.New(gc)
	return version.Set(zone_id, version_id)
}

func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []gandirecord.RecordSet) error {

	// Get the zone_id of the zone we'll be updating.
	zoneinfo, err := c.getZoneInfo(zoneID)
	if err != nil {
		return err
	}
	//fmt.Println("ZONEINFO:", zoneinfo)
	zoneID, err = c.getEditableZone(domainname, zoneinfo)
	if err != nil {
		return err
	}

	// Get the versionID of the zone we're updating.
	versionID, err := c.makeEditableZone(zoneID)
	if err != nil {
		return err
	}

	// Update the new version.
	_, err = c.setZoneRecords(zoneID, versionID, records)
	if err != nil {
		return err
	}

	// Activate zone version
	_, err = c.activateVersion(zoneID, versionID)
	if err != nil {
		return err
	}
	_, err = c.setZones(domainname, zoneID)
	if err != nil {
		return err
	}

	return nil
}

// convert takes a DNS record from Gandi and returns our native RecordConfig format.
func convert(r *gandirecord.RecordInfo, origin string) *models.RecordConfig {
	rc := &models.RecordConfig{
		NameFQDN: dnsutil.AddOrigin(r.Name, origin),
		Name:     r.Name,
		Type:     r.Type,
		Original: r,
		Target:   r.Value,
		TTL:      uint32(r.Ttl),
	}
	switch r.Type {
	case "A", "AAAA", "NS", "CNAME", "PTR", "TXT":
		// no-op
	case "MX":
		var err error
		rc.MxPreference, rc.Target, err = models.SplitCombinedMxValue(r.Value)
		if err != nil {
			panic(fmt.Sprintf("gandi.convert bad mx value format: %#v", r.Value))
		}
	default:
		panic(fmt.Sprintf("gandi.convert unimplemented rtype %v", r.Type))
		// We panic so that we quickly find any switch statements
		// that have not been updated for a new RR type.
	}
	return rc
}
<|endoftext|>


<commit_before>package decoders

import (
	"errors"
	"fmt"
	"log"
	"math"
	"regexp"
	"strconv"
	"time"

	"github.com/seadsystem/Backend/DB/landingzone/constants"
)

type SeadPacket struct {
	Type      byte
	Location  byte
	Timestamp time.Time
	Period    float64
	Count     uint
	Data      float32
	Serial    int
}

var headerRegex *regexp.Regexp
var InvalidHeader = errors.New("Invalid header.")
var InvalidPacket = errors.New("Invalid packet.")
var InvalidTime = errors.New("Invalid time.")

// init sets up stuff we need with proper error handling. If it isn't complicated or doesn't need error handling, it can probably just be assigned directly.
func init() {
	var err error
	headerRegex, err = regexp.Compile(constants.HEADER_REGEX)
	if err != nil {
		log.Panic("Regex compile error:", err)
	}
}

// DecodeHeader verifies that the header is in the correct format and extracts the serial number
func DecodeHeader(packet []byte) (serial int, offset float64, err error) {
	headerStrings := headerRegex.FindSubmatch(packet)

	if headerStrings == nil || len(headerStrings) != 3 {
		err = InvalidHeader
		return
	}

	log.Printf("Header serial string: %s\n", string(headerStrings[1]))

	var double_time float64
	double_time, err = asciiTimeToDouble(headerStrings[2])
	if err != nil {
		return
	}

	offset = float64(time.Now().Unix()) - double_time

	serial, err = strconv.Atoi(string(headerStrings[1]))
	return
}

// DecodePacket extracts the data sent from sensor
func DecodePacket(buffer []byte, offset float64) (packet SeadPacket, err error) {
	for i := 0; i < len(buffer); {
		datatype := buffer[i]
		i++

		// Switch on the type of data sent in the packet
		switch {
		case datatype == 'T':
			// Type
			packet.Type = buffer[i]
			i++
		case datatype == 'l':
			// Location
			packet.Location = buffer[i]
			i++
		case datatype == 't':
			// Timestamp
			var double_time float64
			double_time, err = asciiTimeToDouble(buffer[i : i+14])
			double_time += offset
			nano := math.Pow10(6)
			packet.Timestamp = time.Unix(int64(double_time), (int_time*nano)%int64(nano))
			//packet.Timestamp, err = asciiTimeToDouble(buffer[i : i+14])
			i += 14
		case datatype == 'P':
			// Period separator
			packet.Period, err = asciiTimeToDouble(buffer[i : i+14])
			i += 14
		case datatype == 'C':
			// Count
			packet.Count = Binary2uint(buffer[i : i+2])
			i += 2
		case datatype == 'D':
			// Data
			// if count isn't set, return error
			if packet.Count == 0 {
				err = InvalidPacket
			} else {
				count := 2 * int(packet.Count)
				packet.Data = math.Float32frombits(uint32(Binary2uint(buffer[i : i+count])))
				i += count
			}
		case datatype == 'S':
			// Serial
			packet.Serial, err = strconv.Atoi(string(buffer[i : i+6]))
			i += 6
		case datatype == 'X':
			return
		default:
			err = InvalidPacket
		}

		if err != nil {
			return
		}
	}
	err = InvalidPacket
	return
}

func doubleToAsciiTime(double_time float64) string {
	// TODO: Check if this logic is correct or if we need to use http://golang.org/pkg/math/#Mod
	int_time := int(double_time)
	var days = math.Floor(double_time / (60 * 60 * 24))
	var hours = (int_time % (60 * 60 * 24)) / (60 * 60)
	var minutes = (int_time % (60 * 60)) / 60
	var seconds = (int_time % (60)) / 1
	var milliseconds = (int_time * 1000) % 1000
	var clock_time = (int_time * 12000) % 12

	return fmt.Sprintf("%03d%02d%02d%02d%03d%02d", days, hours, minutes, seconds, milliseconds, clock_time)
}

func asciiTimeToDouble(ascii_time []byte) (time float64, err error) {
	// Check time string format
	if len(ascii_time) != 16 {
		err = InvalidTime
	}
	_, err = strconv.Atoi(string(ascii_time))
	if err != nil {
		return
	}

	// Do the conversion now that we know it should work
	var ptr int = 0
	days, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	time += float64(60 * 60 * 24 * days)
	hours, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(60 * 60 * hours)
	minutes, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(60 * minutes)
	seconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(seconds)
	milliseconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	time += float64(milliseconds) / 1000.0
	clock, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(clock) / 12000.0
	return
}

// Every checks if every byte in a slice meets some criteria
func Every(data []byte, check func(byte) bool) bool {
	for _, element := range data {
		if !check(element) {
			return false
		}
	}
	return true
}

// Binary2uint converts a byte array containing binary data into an int
func Binary2uint(data []byte) (total uint) {
	for index, element := range data {
		total += uint(element) << uint(index*8)
	}
	return
}

// Binary2uint64 converts a byte array containing binary data into an int
func Binary2uint64(data []byte) (total uint64) {
	for index, element := range data {
		total += uint64(element) << uint64(index*8)
	}
	return
}
<commit_msg>Removed reference to removed variable int_time.<commit_after>package decoders

import (
	"errors"
	"fmt"
	"log"
	"math"
	"regexp"
	"strconv"
	"time"

	"github.com/seadsystem/Backend/DB/landingzone/constants"
)

type SeadPacket struct {
	Type      byte
	Location  byte
	Timestamp time.Time
	Period    float64
	Count     uint
	Data      float32
	Serial    int
}

var headerRegex *regexp.Regexp
var InvalidHeader = errors.New("Invalid header.")
var InvalidPacket = errors.New("Invalid packet.")
var InvalidTime = errors.New("Invalid time.")

// init sets up stuff we need with proper error handling. If it isn't complicated or doesn't need error handling, it can probably just be assigned directly.
func init() {
	var err error
	headerRegex, err = regexp.Compile(constants.HEADER_REGEX)
	if err != nil {
		log.Panic("Regex compile error:", err)
	}
}

// DecodeHeader verifies that the header is in the correct format and extracts the serial number
func DecodeHeader(packet []byte) (serial int, offset float64, err error) {
	headerStrings := headerRegex.FindSubmatch(packet)

	if headerStrings == nil || len(headerStrings) != 3 {
		err = InvalidHeader
		return
	}

	log.Printf("Header serial string: %s\n", string(headerStrings[1]))

	var double_time float64
	double_time, err = asciiTimeToDouble(headerStrings[2])
	if err != nil {
		return
	}

	offset = float64(time.Now().Unix()) - double_time

	serial, err = strconv.Atoi(string(headerStrings[1]))
	return
}

// DecodePacket extracts the data sent from sensor
func DecodePacket(buffer []byte, offset float64) (packet SeadPacket, err error) {
	for i := 0; i < len(buffer); {
		datatype := buffer[i]
		i++

		// Switch on the type of data sent in the packet
		switch {
		case datatype == 'T':
			// Type
			packet.Type = buffer[i]
			i++
		case datatype == 'l':
			// Location
			packet.Location = buffer[i]
			i++
		case datatype == 't':
			// Timestamp
			var double_time float64
			double_time, err = asciiTimeToDouble(buffer[i : i+14])
			double_time += offset
			nano := math.Pow10(6)
			packet.Timestamp = time.Unix(int64(double_time), (double_time*nano)%int64(nano))
			//packet.Timestamp, err = asciiTimeToDouble(buffer[i : i+14])
			i += 14
		case datatype == 'P':
			// Period separator
			packet.Period, err = asciiTimeToDouble(buffer[i : i+14])
			i += 14
		case datatype == 'C':
			// Count
			packet.Count = Binary2uint(buffer[i : i+2])
			i += 2
		case datatype == 'D':
			// Data
			// if count isn't set, return error
			if packet.Count == 0 {
				err = InvalidPacket
			} else {
				count := 2 * int(packet.Count)
				packet.Data = math.Float32frombits(uint32(Binary2uint(buffer[i : i+count])))
				i += count
			}
		case datatype == 'S':
			// Serial
			packet.Serial, err = strconv.Atoi(string(buffer[i : i+6]))
			i += 6
		case datatype == 'X':
			return
		default:
			err = InvalidPacket
		}

		if err != nil {
			return
		}
	}
	err = InvalidPacket
	return
}

func doubleToAsciiTime(double_time float64) string {
	// TODO: Check if this logic is correct or if we need to use http://golang.org/pkg/math/#Mod
	int_time := int(double_time)
	var days = math.Floor(double_time / (60 * 60 * 24))
	var hours = (int_time % (60 * 60 * 24)) / (60 * 60)
	var minutes = (int_time % (60 * 60)) / 60
	var seconds = (int_time % (60)) / 1
	var milliseconds = (int_time * 1000) % 1000
	var clock_time = (int_time * 12000) % 12

	return fmt.Sprintf("%03d%02d%02d%02d%03d%02d", days, hours, minutes, seconds, milliseconds, clock_time)
}

func asciiTimeToDouble(ascii_time []byte) (time float64, err error) {
	// Check time string format
	if len(ascii_time) != 16 {
		err = InvalidTime
	}
	_, err = strconv.Atoi(string(ascii_time))
	if err != nil {
		return
	}

	// Do the conversion now that we know it should work
	var ptr int = 0
	days, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	time += float64(60 * 60 * 24 * days)
	hours, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(60 * 60 * hours)
	minutes, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(60 * minutes)
	seconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(seconds)
	milliseconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	time += float64(milliseconds) / 1000.0
	clock, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	time += float64(clock) / 12000.0
	return
}

// Every checks if every byte in a slice meets some criteria
func Every(data []byte, check func(byte) bool) bool {
	for _, element := range data {
		if !check(element) {
			return false
		}
	}
	return true
}

// Binary2uint converts a byte array containing binary data into an int
func Binary2uint(data []byte) (total uint) {
	for index, element := range data {
		total += uint(element) << uint(index*8)
	}
	return
}

// Binary2uint64 converts a byte array containing binary data into an int
func Binary2uint64(data []byte) (total uint64) {
	for index, element := range data {
		total += uint64(element) << uint64(index*8)
	}
	return
}
<|endoftext|>


<commit_before>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("stack command", func() {
	var (
		orgName          string
		spaceName        string
		stackName        string
		stackDescription string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		stackName = helpers.PrefixedRandomName("stack")
		stackDescription = "this is a test stack"
	})

	Describe("help", func() {
		When("--help flag is set", func() {
			It("Displays command usage to output", func() {
				session := helpers.CF("stack", "--help")

				Eventually(session).Should(Say(`NAME:`))
				Eventually(session).Should(Say(`stack - Show information for a stack \(a stack is a pre-built file system, including an operating system, that can run apps\)`))
				Eventually(session).Should(Say("USAGE:"))
				Eventually(session).Should(Say("cf stack STACK_NAME"))

				Eventually(session).Should(Exit(0))
			})
		})
	})

	When("the stack name is not provided", func() {
		It("tells the user that the stack name is required, prints help text, and exits 1", func() {
			session := helpers.CF("stack")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `STACK_NAME` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	When("the environment is not setup correctly", func() {
		It("fails with the appropriate errors", func() {
			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "stack", stackName)
		})
	})

	When("the environment is set up correctly", func() {
		var username string

		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			username, _ = helpers.GetCredentials()
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		When("the input is invalid", func() {
			When("there are not enough arguments", func() {
				It("outputs the usage and exits 1", func() {
					session := helpers.CF("stack")

					Eventually(session.Err).Should(Say("Incorrect Usage:"))
					Eventually(session).Should(Say("NAME:"))
					Eventually(session).Should(Exit(1))
				})
			})

			When("there too many arguments", func() {
				It("ignores the extra arguments", func() {
					session := helpers.CF("stack", stackName, "extra")

					Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Eventually(session.Err).Should(Say("Stack %s not found", stackName))
					Eventually(session).Should(Say("FAILED"))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		When("the stack does not exist", func() {
			It("Fails", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session.Err).Should(Say("Stack %s not found", stackName))
				Eventually(session).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		When("the stack exists", func() {
			BeforeEach(func() {
				jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s"}`, stackName, stackDescription)
				session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks")
				Eventually(session).Should(Exit(0))
			})
			It("Shows the details for the stack", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session).Should(Say(`name:\s+%s`, stackName))
				Eventually(session).Should(Say(`description:\s+%s`, stackDescription))
				Eventually(session).Should(Exit(0))
			})

			When("the stack exists and the --guid flag is passed", func() {
				It("prints nothing but the guid", func() {
					session := helpers.CF("stack", stackName, "--guid")

					Consistently(session).ShouldNot(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName))
					Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription))
					Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`))
					Eventually(session).Should(Exit(0))
				})
			})
		})
	})
})
<commit_msg>Fix integration tests so they dont leak stacks<commit_after>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
	"regexp"
)

var _ = Describe("stack command", func() {
	var (
		orgName          string
		spaceName        string
		stackName        string
		stackDescription string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		stackName = helpers.PrefixedRandomName("stack")
		stackDescription = "this is a test stack"
	})

	Describe("help", func() {
		When("--help flag is set", func() {
			It("Displays command usage to output", func() {
				session := helpers.CF("stack", "--help")

				Eventually(session).Should(Say(`NAME:`))
				Eventually(session).Should(Say(`stack - Show information for a stack \(a stack is a pre-built file system, including an operating system, that can run apps\)`))
				Eventually(session).Should(Say("USAGE:"))
				Eventually(session).Should(Say("cf stack STACK_NAME"))

				Eventually(session).Should(Exit(0))
			})
		})
	})

	When("the stack name is not provided", func() {
		It("tells the user that the stack name is required, prints help text, and exits 1", func() {
			session := helpers.CF("stack")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `STACK_NAME` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	When("the environment is not setup correctly", func() {
		It("fails with the appropriate errors", func() {
			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "stack", stackName)
		})
	})

	When("the environment is set up correctly", func() {
		var username string

		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			username, _ = helpers.GetCredentials()
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		When("the input is invalid", func() {
			When("there are not enough arguments", func() {
				It("outputs the usage and exits 1", func() {
					session := helpers.CF("stack")

					Eventually(session.Err).Should(Say("Incorrect Usage:"))
					Eventually(session).Should(Say("NAME:"))
					Eventually(session).Should(Exit(1))
				})
			})

			When("there too many arguments", func() {
				It("ignores the extra arguments", func() {
					session := helpers.CF("stack", stackName, "extra")

					Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Eventually(session.Err).Should(Say("Stack %s not found", stackName))
					Eventually(session).Should(Say("FAILED"))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		When("the stack does not exist", func() {
			It("Fails", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session.Err).Should(Say("Stack %s not found", stackName))
				Eventually(session).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		When("the stack exists", func() {
			BeforeEach(func() {
				stackName = "cflinuxfs3"
				stackDescription = regexp.QuoteMeta("Cloud Foundry Linux-based filesystem (Ubuntu 18.04)")
			})

			It("Shows the details for the stack", func() {
				session := helpers.CF("stack", "cflinuxfs3")

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session).Should(Say(`name:\s+%s`, stackName))
				Eventually(session).Should(Say(`description:\s+%s`, stackDescription))
				Eventually(session).Should(Exit(0))
			})

			When("the stack exists and the --guid flag is passed", func() {
				It("prints nothing but the guid", func() {
					session := helpers.CF("stack", stackName, "--guid")

					Consistently(session).ShouldNot(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName))
					Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription))
					Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`))
					Eventually(session).Should(Exit(0))
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Couchbase, Inc.
//  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 cbft

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/gorilla/mux"
)

// DiagHandler represents a part of the DiagGetHandler response.
// Different modules can provide their parts of a DiagGetHandler
// response via their own DiagHandler's.
type DiagHandler struct {
	Name        string
	Handler     http.Handler
	HandlerFunc http.HandlerFunc
}

// DiagGetHandler is a REST handler that retrieves diagnostic
// information for a node.
type DiagGetHandler struct {
	versionMain string
	mgr         *Manager
	mr          *MsgRing
}

func NewDiagGetHandler(versionMain string,
	mgr *Manager, mr *MsgRing) *DiagGetHandler {
	return &DiagGetHandler{versionMain: versionMain, mgr: mgr, mr: mr}
}

func (h *DiagGetHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	handlers := []DiagHandler{
		{"/api/cfg", NewCfgGetHandler(h.mgr), nil},
		{"/api/index", NewListIndexHandler(h.mgr), nil},
		{"/api/log", NewLogGetHandler(h.mgr, h.mr), nil},
		{"/api/managerMeta", NewManagerMetaHandler(h.mgr, nil), nil},
		{"/api/pindex", NewListPIndexHandler(h.mgr), nil},
		{"/api/runtime", NewRuntimeGetHandler(h.versionMain, h.mgr), nil},
		{"/api/runtime/args", nil, restGetRuntimeArgs},
		{"/api/runtime/stats", nil, restGetRuntimeStats},
		{"/api/runtime/statsMem", nil, restGetRuntimeStatsMem},
		{"/api/stats", NewStatsHandler(h.mgr), nil},
	}

	for _, t := range PIndexImplTypes {
		for _, h := range t.DiagHandlers {
			handlers = append(handlers, h)
		}
	}

	w.Write(jsonOpenBrace)
	for i, handler := range handlers {
		if i > 0 {
			w.Write(jsonComma)
		}
		w.Write([]byte(fmt.Sprintf(`"%s":`, handler.Name)))
		if handler.Handler != nil {
			handler.Handler.ServeHTTP(w, req)
		}
		if handler.HandlerFunc != nil {
			handler.HandlerFunc.ServeHTTP(w, req)
		}
	}

	var first = true
	var visit func(path string, f os.FileInfo, err error) error
	visit = func(path string, f os.FileInfo, err error) error {
		m := map[string]interface{}{
			"Path":    path,
			"Name":    f.Name(),
			"Size":    f.Size(),
			"Mode":    f.Mode(),
			"ModTime": f.ModTime().Format(time.RFC3339Nano),
			"IsDir":   f.IsDir(),
		}
		if strings.HasPrefix(f.Name(), "PINDEX_") || // Matches PINDEX_xxx_META.
			strings.HasSuffix(f.Name(), "_META") || // Matches PINDEX_META.
			strings.HasSuffix(f.Name(), ".json") { // Matches index_meta.json.
			b, err := ioutil.ReadFile(path)
			if err == nil {
				m["Contents"] = string(b)
			}
		}
		buf, err := json.Marshal(m)
		if err == nil {
			if !first {
				w.Write(jsonComma)
			}
			w.Write(buf)
			first = false
		}
		return nil
	}

	w.Write([]byte(`,"dataDir":[`))
	filepath.Walk(h.mgr.dataDir, visit)
	w.Write([]byte(`]`))

	entries, err := AssetDir("static/dist")
	if err == nil {
		for _, name := range entries {
			// Ex: "static/dist/manifest.txt".
			a, err := Asset("static/dist/" + name)
			if err == nil {
				j, err := json.Marshal(strings.TrimSpace(string(a)))
				if err == nil {
					w.Write([]byte(`,"`))
					w.Write([]byte("/static/dist/" + name))
					w.Write([]byte(`":`))
					w.Write(j)
				}
			}
		}
	}

	w.Write(jsonCloseBrace)
}

// ---------------------------------------------------

// StatsHandler is a REST handler that provides stats/metrics for a
// node.
type StatsHandler struct {
	mgr *Manager
}

func NewStatsHandler(mgr *Manager) *StatsHandler {
	return &StatsHandler{mgr: mgr}
}

var statsFeedsPrefix = []byte("\"feeds\":{")
var statsPIndexesPrefix = []byte("\"pindexes\":{")
var statsManagerPrefix = []byte(",\"manager\":")
var statsNamePrefix = []byte("\"")
var statsNameSuffix = []byte("\":")

func (h *StatsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	indexName := mux.Vars(req)["indexName"]

	feeds, pindexes := h.mgr.CurrentMaps()
	feedNames := make([]string, 0, len(feeds))
	for feedName := range feeds {
		feedNames = append(feedNames, feedName)
	}
	sort.Strings(feedNames)

	pindexNames := make([]string, 0, len(pindexes))
	for pindexName := range pindexes {
		pindexNames = append(pindexNames, pindexName)
	}
	sort.Strings(pindexNames)

	w.Write(jsonOpenBrace)

	first := true
	w.Write(statsFeedsPrefix)
	for _, feedName := range feedNames {
		if indexName == "" || indexName == feeds[feedName].IndexName() {
			if !first {
				w.Write(jsonComma)
			}
			first = false
			w.Write(statsNamePrefix)
			w.Write([]byte(feedName))
			w.Write(statsNameSuffix)
			feeds[feedName].Stats(w)
		}
	}
	w.Write(jsonCloseBraceComma)

	first = true
	w.Write(statsPIndexesPrefix)
	for _, pindexName := range pindexNames {
		if indexName == "" || indexName == pindexes[pindexName].IndexName {
			if !first {
				w.Write(jsonComma)
			}
			first = false
			w.Write(statsNamePrefix)
			w.Write([]byte(pindexName))
			w.Write(statsNameSuffix)
			pindexes[pindexName].Dest.Stats(w)
		}
	}
	w.Write(jsonCloseBrace)

	if indexName == "" {
		w.Write(statsManagerPrefix)
		var mgrStats ManagerStats
		h.mgr.stats.AtomicCopyTo(&mgrStats)
		mgrStatsJSON, err := json.Marshal(&mgrStats)
		if err == nil && len(mgrStatsJSON) > 0 {
			w.Write(mgrStatsJSON)
		} else {
			w.Write(jsonNULL)
		}
	}

	w.Write(jsonCloseBrace)
}

// ---------------------------------------------------

// ManagerKickHandler is a REST handler that processes a request to
// kick a manager.
type ManagerKickHandler struct {
	mgr *Manager
}

func NewManagerKickHandler(mgr *Manager) *ManagerKickHandler {
	return &ManagerKickHandler{mgr: mgr}
}

func (h *ManagerKickHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.mgr.Kick(req.FormValue("msg"))
	mustEncode(w, struct {
		Status string `json:"status"`
	}{Status: "ok"})
}

// ---------------------------------------------------

// CfgGetHandler is a REST handler that retrieves the contents of the
// Cfg system.
type CfgGetHandler struct {
	mgr *Manager
}

func NewCfgGetHandler(mgr *Manager) *CfgGetHandler {
	return &CfgGetHandler{mgr: mgr}
}

func (h *CfgGetHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// TODO: Might need to scrub auth passwords from this output.
	cfg := h.mgr.Cfg()
	indexDefs, indexDefsCAS, indexDefsErr :=
		CfgGetIndexDefs(cfg)
	nodeDefsWanted, nodeDefsWantedCAS, nodeDefsWantedErr :=
		CfgGetNodeDefs(cfg, NODE_DEFS_WANTED)
	nodeDefsKnown, nodeDefsKnownCAS, nodeDefsKnownErr :=
		CfgGetNodeDefs(cfg, NODE_DEFS_KNOWN)
	planPIndexes, planPIndexesCAS, planPIndexesErr :=
		CfgGetPlanPIndexes(cfg)
	mustEncode(w, struct {
		Status            string        `json:"status"`
		IndexDefs         *IndexDefs    `json:"indexDefs"`
		IndexDefsCAS      uint64        `json:"indexDefsCAS"`
		IndexDefsErr      error         `json:"indexDefsErr"`
		NodeDefsWanted    *NodeDefs     `json:"nodeDefsWanted"`
		NodeDefsWantedCAS uint64        `json:"nodeDefsWantedCAS"`
		NodeDefsWantedErr error         `json:"nodeDefsWantedErr"`
		NodeDefsKnown     *NodeDefs     `json:"nodeDefsKnown"`
		NodeDefsKnownCAS  uint64        `json:"nodeDefsKnownCAS"`
		NodeDefsKnownErr  error         `json:"nodeDefsKnownErr"`
		PlanPIndexes      *PlanPIndexes `json:"planPIndexes"`
		PlanPIndexesCAS   uint64        `json:"planPIndexesCAS"`
		PlanPIndexesErr   error         `json:"planPIndexesErr"`
	}{
		Status:            "ok",
		IndexDefs:         indexDefs,
		IndexDefsCAS:      indexDefsCAS,
		IndexDefsErr:      indexDefsErr,
		NodeDefsWanted:    nodeDefsWanted,
		NodeDefsWantedCAS: nodeDefsWantedCAS,
		NodeDefsWantedErr: nodeDefsWantedErr,
		NodeDefsKnown:     nodeDefsKnown,
		NodeDefsKnownCAS:  nodeDefsKnownCAS,
		NodeDefsKnownErr:  nodeDefsKnownErr,
		PlanPIndexes:      planPIndexes,
		PlanPIndexesCAS:   planPIndexesCAS,
		PlanPIndexesErr:   planPIndexesErr,
	})
}

// ---------------------------------------------------

// CfgRefreshHandler is a REST handler that processes a request for
// the manager/node to refresh its cached snapshot of the Cfg system
// contents.
type CfgRefreshHandler struct {
	mgr *Manager
}

func NewCfgRefreshHandler(mgr *Manager) *CfgRefreshHandler {
	return &CfgRefreshHandler{mgr: mgr}
}

func (h *CfgRefreshHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.mgr.Cfg().Refresh()
	h.mgr.GetIndexDefs(true)
	h.mgr.GetPlanPIndexes(true)
	mustEncode(w, struct {
		Status string `json:"status"`
	}{Status: "ok"})
}
<commit_msg>REST diag includes /debug/pprof/goroutine?debug=2 output<commit_after>//  Copyright (c) 2014 Couchbase, Inc.
//  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 cbft

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime/pprof"
	"sort"
	"strings"
	"time"

	"github.com/gorilla/mux"
)

// DiagHandler represents a part of the DiagGetHandler response.
// Different modules can provide their parts of a DiagGetHandler
// response via their own DiagHandler's.
type DiagHandler struct {
	Name        string
	Handler     http.Handler
	HandlerFunc http.HandlerFunc
}

// DiagGetHandler is a REST handler that retrieves diagnostic
// information for a node.
type DiagGetHandler struct {
	versionMain string
	mgr         *Manager
	mr          *MsgRing
}

func NewDiagGetHandler(versionMain string,
	mgr *Manager, mr *MsgRing) *DiagGetHandler {
	return &DiagGetHandler{versionMain: versionMain, mgr: mgr, mr: mr}
}

func (h *DiagGetHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	handlers := []DiagHandler{
		{"/api/cfg", NewCfgGetHandler(h.mgr), nil},
		{"/api/index", NewListIndexHandler(h.mgr), nil},
		{"/api/log", NewLogGetHandler(h.mgr, h.mr), nil},
		{"/api/managerMeta", NewManagerMetaHandler(h.mgr, nil), nil},
		{"/api/pindex", NewListPIndexHandler(h.mgr), nil},
		{"/api/runtime", NewRuntimeGetHandler(h.versionMain, h.mgr), nil},
		{"/api/runtime/args", nil, restGetRuntimeArgs},
		{"/api/runtime/stats", nil, restGetRuntimeStats},
		{"/api/runtime/statsMem", nil, restGetRuntimeStatsMem},
		{"/api/stats", NewStatsHandler(h.mgr), nil},
		{"/debug/pprof/goroutine?debug=2", nil,
			func(w http.ResponseWriter, r *http.Request) {
				var b bytes.Buffer
				debug := 2
				pprof.Lookup("goroutine").WriteTo(&b, debug)
				mustEncode(w, b.String())
			}},
	}

	for _, t := range PIndexImplTypes {
		for _, h := range t.DiagHandlers {
			handlers = append(handlers, h)
		}
	}

	w.Write(jsonOpenBrace)
	for i, handler := range handlers {
		if i > 0 {
			w.Write(jsonComma)
		}
		w.Write([]byte(fmt.Sprintf(`"%s":`, handler.Name)))
		if handler.Handler != nil {
			handler.Handler.ServeHTTP(w, req)
		}
		if handler.HandlerFunc != nil {
			handler.HandlerFunc.ServeHTTP(w, req)
		}
	}

	var first = true
	var visit func(path string, f os.FileInfo, err error) error
	visit = func(path string, f os.FileInfo, err error) error {
		m := map[string]interface{}{
			"Path":    path,
			"Name":    f.Name(),
			"Size":    f.Size(),
			"Mode":    f.Mode(),
			"ModTime": f.ModTime().Format(time.RFC3339Nano),
			"IsDir":   f.IsDir(),
		}
		if strings.HasPrefix(f.Name(), "PINDEX_") || // Matches PINDEX_xxx_META.
			strings.HasSuffix(f.Name(), "_META") || // Matches PINDEX_META.
			strings.HasSuffix(f.Name(), ".json") { // Matches index_meta.json.
			b, err := ioutil.ReadFile(path)
			if err == nil {
				m["Contents"] = string(b)
			}
		}
		buf, err := json.Marshal(m)
		if err == nil {
			if !first {
				w.Write(jsonComma)
			}
			w.Write(buf)
			first = false
		}
		return nil
	}

	w.Write([]byte(`,"dataDir":[`))
	filepath.Walk(h.mgr.dataDir, visit)
	w.Write([]byte(`]`))

	entries, err := AssetDir("static/dist")
	if err == nil {
		for _, name := range entries {
			// Ex: "static/dist/manifest.txt".
			a, err := Asset("static/dist/" + name)
			if err == nil {
				j, err := json.Marshal(strings.TrimSpace(string(a)))
				if err == nil {
					w.Write([]byte(`,"`))
					w.Write([]byte("/static/dist/" + name))
					w.Write([]byte(`":`))
					w.Write(j)
				}
			}
		}
	}

	w.Write(jsonCloseBrace)
}

// ---------------------------------------------------

// StatsHandler is a REST handler that provides stats/metrics for a
// node.
type StatsHandler struct {
	mgr *Manager
}

func NewStatsHandler(mgr *Manager) *StatsHandler {
	return &StatsHandler{mgr: mgr}
}

var statsFeedsPrefix = []byte("\"feeds\":{")
var statsPIndexesPrefix = []byte("\"pindexes\":{")
var statsManagerPrefix = []byte(",\"manager\":")
var statsNamePrefix = []byte("\"")
var statsNameSuffix = []byte("\":")

func (h *StatsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	indexName := mux.Vars(req)["indexName"]

	feeds, pindexes := h.mgr.CurrentMaps()
	feedNames := make([]string, 0, len(feeds))
	for feedName := range feeds {
		feedNames = append(feedNames, feedName)
	}
	sort.Strings(feedNames)

	pindexNames := make([]string, 0, len(pindexes))
	for pindexName := range pindexes {
		pindexNames = append(pindexNames, pindexName)
	}
	sort.Strings(pindexNames)

	w.Write(jsonOpenBrace)

	first := true
	w.Write(statsFeedsPrefix)
	for _, feedName := range feedNames {
		if indexName == "" || indexName == feeds[feedName].IndexName() {
			if !first {
				w.Write(jsonComma)
			}
			first = false
			w.Write(statsNamePrefix)
			w.Write([]byte(feedName))
			w.Write(statsNameSuffix)
			feeds[feedName].Stats(w)
		}
	}
	w.Write(jsonCloseBraceComma)

	first = true
	w.Write(statsPIndexesPrefix)
	for _, pindexName := range pindexNames {
		if indexName == "" || indexName == pindexes[pindexName].IndexName {
			if !first {
				w.Write(jsonComma)
			}
			first = false
			w.Write(statsNamePrefix)
			w.Write([]byte(pindexName))
			w.Write(statsNameSuffix)
			pindexes[pindexName].Dest.Stats(w)
		}
	}
	w.Write(jsonCloseBrace)

	if indexName == "" {
		w.Write(statsManagerPrefix)
		var mgrStats ManagerStats
		h.mgr.stats.AtomicCopyTo(&mgrStats)
		mgrStatsJSON, err := json.Marshal(&mgrStats)
		if err == nil && len(mgrStatsJSON) > 0 {
			w.Write(mgrStatsJSON)
		} else {
			w.Write(jsonNULL)
		}
	}

	w.Write(jsonCloseBrace)
}

// ---------------------------------------------------

// ManagerKickHandler is a REST handler that processes a request to
// kick a manager.
type ManagerKickHandler struct {
	mgr *Manager
}

func NewManagerKickHandler(mgr *Manager) *ManagerKickHandler {
	return &ManagerKickHandler{mgr: mgr}
}

func (h *ManagerKickHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.mgr.Kick(req.FormValue("msg"))
	mustEncode(w, struct {
		Status string `json:"status"`
	}{Status: "ok"})
}

// ---------------------------------------------------

// CfgGetHandler is a REST handler that retrieves the contents of the
// Cfg system.
type CfgGetHandler struct {
	mgr *Manager
}

func NewCfgGetHandler(mgr *Manager) *CfgGetHandler {
	return &CfgGetHandler{mgr: mgr}
}

func (h *CfgGetHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// TODO: Might need to scrub auth passwords from this output.
	cfg := h.mgr.Cfg()
	indexDefs, indexDefsCAS, indexDefsErr :=
		CfgGetIndexDefs(cfg)
	nodeDefsWanted, nodeDefsWantedCAS, nodeDefsWantedErr :=
		CfgGetNodeDefs(cfg, NODE_DEFS_WANTED)
	nodeDefsKnown, nodeDefsKnownCAS, nodeDefsKnownErr :=
		CfgGetNodeDefs(cfg, NODE_DEFS_KNOWN)
	planPIndexes, planPIndexesCAS, planPIndexesErr :=
		CfgGetPlanPIndexes(cfg)
	mustEncode(w, struct {
		Status            string        `json:"status"`
		IndexDefs         *IndexDefs    `json:"indexDefs"`
		IndexDefsCAS      uint64        `json:"indexDefsCAS"`
		IndexDefsErr      error         `json:"indexDefsErr"`
		NodeDefsWanted    *NodeDefs     `json:"nodeDefsWanted"`
		NodeDefsWantedCAS uint64        `json:"nodeDefsWantedCAS"`
		NodeDefsWantedErr error         `json:"nodeDefsWantedErr"`
		NodeDefsKnown     *NodeDefs     `json:"nodeDefsKnown"`
		NodeDefsKnownCAS  uint64        `json:"nodeDefsKnownCAS"`
		NodeDefsKnownErr  error         `json:"nodeDefsKnownErr"`
		PlanPIndexes      *PlanPIndexes `json:"planPIndexes"`
		PlanPIndexesCAS   uint64        `json:"planPIndexesCAS"`
		PlanPIndexesErr   error         `json:"planPIndexesErr"`
	}{
		Status:            "ok",
		IndexDefs:         indexDefs,
		IndexDefsCAS:      indexDefsCAS,
		IndexDefsErr:      indexDefsErr,
		NodeDefsWanted:    nodeDefsWanted,
		NodeDefsWantedCAS: nodeDefsWantedCAS,
		NodeDefsWantedErr: nodeDefsWantedErr,
		NodeDefsKnown:     nodeDefsKnown,
		NodeDefsKnownCAS:  nodeDefsKnownCAS,
		NodeDefsKnownErr:  nodeDefsKnownErr,
		PlanPIndexes:      planPIndexes,
		PlanPIndexesCAS:   planPIndexesCAS,
		PlanPIndexesErr:   planPIndexesErr,
	})
}

// ---------------------------------------------------

// CfgRefreshHandler is a REST handler that processes a request for
// the manager/node to refresh its cached snapshot of the Cfg system
// contents.
type CfgRefreshHandler struct {
	mgr *Manager
}

func NewCfgRefreshHandler(mgr *Manager) *CfgRefreshHandler {
	return &CfgRefreshHandler{mgr: mgr}
}

func (h *CfgRefreshHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.mgr.Cfg().Refresh()
	h.mgr.GetIndexDefs(true)
	h.mgr.GetPlanPIndexes(true)
	mustEncode(w, struct {
		Status string `json:"status"`
	}{Status: "ok"})
}
<|endoftext|>


<commit_before>package notifier

import (
	"fmt"
	"net/http"
	"os"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/oinume/lekcije/server/config"
	"github.com/oinume/lekcije/server/emailer"
	"github.com/oinume/lekcije/server/errors"
	"github.com/oinume/lekcije/server/fetcher"
	"github.com/oinume/lekcije/server/logger"
	"github.com/oinume/lekcije/server/model"
	"github.com/oinume/lekcije/server/util"
	"github.com/sendgrid/sendgrid-go"
	"github.com/sendgrid/sendgrid-go/helpers/mail"
	"github.com/uber-go/zap"
)

type Notifier struct {
	db             *gorm.DB
	fetcher        *fetcher.TeacherLessonFetcher
	dryRun         bool
	lessonService  *model.LessonService
	teachers       map[uint32]*model.Teacher
	fetchedLessons map[uint32][]*model.Lesson
	sender         emailer.Sender
	sync.Mutex
}

func NewNotifier(db *gorm.DB, fetcher *fetcher.TeacherLessonFetcher, dryRun bool) *Notifier {
	return &Notifier{
		db:             db,
		fetcher:        fetcher,
		dryRun:         dryRun,
		teachers:       make(map[uint32]*model.Teacher, 1000),
		fetchedLessons: make(map[uint32][]*model.Lesson, 1000),
		sender:         emailer.NewSendGridSender(http.DefaultClient),
	}
}

func (n *Notifier) SendNotification(user *model.User) error {
	followingTeacherService := model.NewFollowingTeacherService(n.db)
	n.lessonService = model.NewLessonService(n.db)

	teacherIDs, err := followingTeacherService.FindTeacherIDsByUserID(user.ID)
	if err != nil {
		return errors.Wrapperf(err, "Failed to FindTeacherIDsByUserID(): userID=%v", user.ID)
	}
	if len(teacherIDs) != 0 {
		logger.App.Info(
			"Target teachers",
			zap.Uint("userID", uint(user.ID)),
			zap.String("teacherIDs", strings.Join(util.Uint32ToStringSlice(teacherIDs...), ",")),
		)
	}

	availableLessonsPerTeacher := make(map[uint32][]*model.Lesson, 1000)
	wg := &sync.WaitGroup{}
	for _, teacherID := range teacherIDs {
		wg.Add(1)
		go func(teacherID uint32) {
			defer wg.Done()
			teacher, fetchedLessons, newAvailableLessons, err := n.fetchAndExtractNewAvailableLessons(teacherID)
			if err != nil {
				switch err.(type) {
				case *errors.NotFound:
					// TODO: update teacher table flag
					// TODO: Not need to log
					logger.App.Warn("Cannot find teacher", zap.Uint("teacherID", uint(teacherID)))
				default:
					logger.App.Error("Cannot fetch teacher", zap.Uint("teacherID", uint(teacherID)), zap.Error(err))
				}
				return
			}

			n.Lock()
			defer n.Unlock()
			n.teachers[teacherID] = teacher
			if _, ok := n.fetchedLessons[teacherID]; !ok {
				n.fetchedLessons[teacherID] = make([]*model.Lesson, 0, 5000)
			}
			n.fetchedLessons[teacherID] = append(n.fetchedLessons[teacherID], fetchedLessons...)
			if len(newAvailableLessons) > 0 {
				availableLessonsPerTeacher[teacherID] = newAvailableLessons
			}
		}(teacherID)

		if err != nil {
			return err
		}
	}
	wg.Wait()

	if err := n.sendNotificationToUser(user, availableLessonsPerTeacher); err != nil {
		return err
	}

	time.Sleep(500 * time.Millisecond)

	return nil
}

// Returns teacher, fetchedLessons, newAvailableLessons, error
func (n *Notifier) fetchAndExtractNewAvailableLessons(teacherID uint32) (
	*model.Teacher, []*model.Lesson, []*model.Lesson, error,
) {
	teacher, fetchedLessons, err := n.fetcher.Fetch(teacherID)
	if err != nil {
		logger.App.Error(
			"fetcher.Fetch",
			zap.Uint("teacherID", uint(teacherID)), zap.Error(err),
		)
		return nil, nil, nil, err
	}
	logger.App.Debug(
		"fetcher.Fetch",
		zap.Uint("teacherID", uint(teacher.ID)),
		zap.Int("lessons", len(fetchedLessons)),
	)

	//fmt.Printf("fetchedLessons ---\n")
	//for _, l := range fetchedLessons {
	//	fmt.Printf("teacherID=%v, datetime=%v, status=%v\n", l.TeacherId, l.Datetime, l.Status)
	//}

	now := time.Now().In(config.LocalTimezone())
	fromDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, config.LocalTimezone())
	toDate := fromDate.Add(24 * 6 * time.Hour)
	lastFetchedLessons, err := n.lessonService.FindLessons(teacher.ID, fromDate, toDate)
	if err != nil {
		return nil, nil, nil, err
	}
	//fmt.Printf("lastFetchedLessons ---\n")
	//for _, l := range lastFetchedLessons {
	//	fmt.Printf("teacherID=%v, datetime=%v, status=%v\n", l.TeacherId, l.Datetime, l.Status)
	//}

	newAvailableLessons := n.lessonService.GetNewAvailableLessons(lastFetchedLessons, fetchedLessons)
	//fmt.Printf("newAvailableLessons ---\n")
	//for _, l := range newAvailableLessons {
	//	fmt.Printf("teacherID=%v, datetime=%v, status=%v\n", l.TeacherId, l.Datetime, l.Status)
	//}
	return teacher, fetchedLessons, newAvailableLessons, nil
}

func (n *Notifier) sendNotificationToUser(
	user *model.User,
	lessonsPerTeacher map[uint32][]*model.Lesson,
) error {
	lessonsCount := 0
	var teacherIDs []int
	for teacherID, lessons := range lessonsPerTeacher {
		teacherIDs = append(teacherIDs, int(teacherID))
		lessonsCount += len(lessons)
	}
	if lessonsCount == 0 {
		// Don't send notification
		return nil
	}

	sort.Ints(teacherIDs)
	var teacherIDs2 []uint32
	var teacherNames []string
	for _, id := range teacherIDs {
		teacherIDs2 = append(teacherIDs2, uint32(id))
		teacherNames = append(teacherNames, n.teachers[uint32(id)].Name)
	}

	t := emailer.NewTemplate("notifier", getEmailTemplateJP())
	data := struct {
		To                string
		TeacherNames      string
		TeacherIDs        []uint32
		Teachers          map[uint32]*model.Teacher
		LessonsPerTeacher map[uint32][]*model.Lesson
		WebURL            string
	}{
		To:                user.Email.Raw(),
		TeacherNames:      strings.Join(teacherNames, ", "),
		TeacherIDs:        teacherIDs2,
		Teachers:          n.teachers,
		LessonsPerTeacher: lessonsPerTeacher,
		WebURL:            config.WebURL(),
	}
	email, err := emailer.NewEmailFromTemplate(t, data)
	if err != nil {
		return errors.InternalWrapf(err, "Failed to create emailer.Email from template")
	}
	//fmt.Printf("--- mail ---\n%s", email.BodyString())

	logger.App.Info("sendNotificationToUser", zap.String("email", user.Email.Raw()))
	return n.sender.Send(email)
}

func getEmailTemplateJP() string {
	return strings.TrimSpace(`
From: lekcije@lekcije.com
To: {{ .To }}
Subject: {{ .TeacherNames }}の空きレッスンがあります
Body: text/html
{{ range $teacherID := .TeacherIDs }}
{{- $teacher := index $.Teachers $teacherID -}}
--- {{ $teacher.Name }} ---
  {{- $lessons := index $.LessonsPerTeacher $teacherID }}
  {{- range $lesson := $lessons }}
{{ $lesson.Datetime.Format "2006-01-02 15:04" }}
  {{- end }}

レッスンの予約はこちらから:
PC
Mobile

{{ end }}
空きレッスンの通知の解除はこちら
	`)
}

//func getEmailTemplateEN() string {
//	return strings.TrimSpace(`
//{{- range $teacherID := .TeacherIDs }}
//{{- $teacher := index $.Teachers $teacherID -}}
//--- {{ $teacher.Name }} ---
//  {{- $lessons := index $.LessonsPerTeacher $teacherID }}
//  {{- range $lesson := $lessons }}
//{{ $lesson.Datetime.Format "2006-01-02 15:04" }}
//  {{- end }}
//
//Reserve here:
//PC
//Mobile
//{{ end }}
//Click here if you want to stop notification of the teacher.
//	`)
//}

func (n *Notifier) Close() {
	defer n.fetcher.Close()
	defer func() {
		if n.dryRun {
			return
		}
		for teacherID, lessons := range n.fetchedLessons {
			if _, err := n.lessonService.UpdateLessons(lessons); err != nil {
				logger.App.Error(
					"An error ocurred in Notifier.Close",
					zap.Error(err), zap.Uint("teacherID", uint(teacherID)),
				)
			}
		}
	}()
}

type NotificationSender interface {
	Send(user *model.User, subject, body string) error
}

type EmailNotificationSender struct{}

func (s *EmailNotificationSender) Send(user *model.User, subject, body string) error {
	from := mail.NewEmail("lekcije", "lekcije@lekcije.com")
	to := mail.NewEmail(user.Name, user.Email.Raw())
	content := mail.NewContent("text/html", strings.Replace(body, "\n", "
", -1))
	m := mail.NewV3MailInit(from, subject, to, content)

	req := sendgrid.GetRequest(
		os.Getenv("SENDGRID_API_KEY"),
		"/v3/mail/send",
		"https://api.sendgrid.com",
	)
	req.Method = "POST"
	req.Body = mail.GetRequestBody(m)
	resp, err := sendgrid.API(req)
	if err != nil {
		return errors.InternalWrapf(err, "Failed to send email by sendgrid")
	}
	if resp.StatusCode >= 300 {
		message := fmt.Sprintf(
			"Failed to send email by sendgrid: statusCode=%v, body=%v",
			resp.StatusCode, strings.Replace(resp.Body, "\n", "\\n", -1),
		)
		logger.App.Error(message)
		return errors.InternalWrapf(
			err,
			"Failed to send email by sendgrid: statusCode=%v",
			resp.StatusCode,
		)
	}

	return nil
}
<commit_msg>Delete unused NotificationSender<commit_after>package notifier

import (
	"net/http"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/oinume/lekcije/server/config"
	"github.com/oinume/lekcije/server/emailer"
	"github.com/oinume/lekcije/server/errors"
	"github.com/oinume/lekcije/server/fetcher"
	"github.com/oinume/lekcije/server/logger"
	"github.com/oinume/lekcije/server/model"
	"github.com/oinume/lekcije/server/util"
	"github.com/uber-go/zap"
)

type Notifier struct {
	db             *gorm.DB
	fetcher        *fetcher.TeacherLessonFetcher
	dryRun         bool
	lessonService  *model.LessonService
	teachers       map[uint32]*model.Teacher
	fetchedLessons map[uint32][]*model.Lesson
	sender         emailer.Sender
	sync.Mutex
}

func NewNotifier(db *gorm.DB, fetcher *fetcher.TeacherLessonFetcher, dryRun bool) *Notifier {
	return &Notifier{
		db:             db,
		fetcher:        fetcher,
		dryRun:         dryRun,
		teachers:       make(map[uint32]*model.Teacher, 1000),
		fetchedLessons: make(map[uint32][]*model.Lesson, 1000),
		sender:         emailer.NewSendGridSender(http.DefaultClient),
	}
}

func (n *Notifier) SendNotification(user *model.User) error {
	followingTeacherService := model.NewFollowingTeacherService(n.db)
	n.lessonService = model.NewLessonService(n.db)

	teacherIDs, err := followingTeacherService.FindTeacherIDsByUserID(user.ID)
	if err != nil {
		return errors.Wrapperf(err, "Failed to FindTeacherIDsByUserID(): userID=%v", user.ID)
	}
	if len(teacherIDs) != 0 {
		logger.App.Info(
			"Target teachers",
			zap.Uint("userID", uint(user.ID)),
			zap.String("teacherIDs", strings.Join(util.Uint32ToStringSlice(teacherIDs...), ",")),
		)
	}

	availableLessonsPerTeacher := make(map[uint32][]*model.Lesson, 1000)
	wg := &sync.WaitGroup{}
	for _, teacherID := range teacherIDs {
		wg.Add(1)
		go func(teacherID uint32) {
			defer wg.Done()
			teacher, fetchedLessons, newAvailableLessons, err := n.fetchAndExtractNewAvailableLessons(teacherID)
			if err != nil {
				switch err.(type) {
				case *errors.NotFound:
					// TODO: update teacher table flag
					// TODO: Not need to log
					logger.App.Warn("Cannot find teacher", zap.Uint("teacherID", uint(teacherID)))
				default:
					logger.App.Error("Cannot fetch teacher", zap.Uint("teacherID", uint(teacherID)), zap.Error(err))
				}
				return
			}

			n.Lock()
			defer n.Unlock()
			n.teachers[teacherID] = teacher
			if _, ok := n.fetchedLessons[teacherID]; !ok {
				n.fetchedLessons[teacherID] = make([]*model.Lesson, 0, 5000)
			}
			n.fetchedLessons[teacherID] = append(n.fetchedLessons[teacherID], fetchedLessons...)
			if len(newAvailableLessons) > 0 {
				availableLessonsPerTeacher[teacherID] = newAvailableLessons
			}
		}(teacherID)

		if err != nil {
			return err
		}
	}
	wg.Wait()

	if err := n.sendNotificationToUser(user, availableLessonsPerTeacher); err != nil {
		return err
	}

	time.Sleep(500 * time.Millisecond)

	return nil
}

// Returns teacher, fetchedLessons, newAvailableLessons, error
func (n *Notifier) fetchAndExtractNewAvailableLessons(teacherID uint32) (
	*model.Teacher, []*model.Lesson, []*model.Lesson, error,
) {
	teacher, fetchedLessons, err := n.fetcher.Fetch(teacherID)
	if err != nil {
		logger.App.Error(
			"fetcher.Fetch",
			zap.Uint("teacherID", uint(teacherID)), zap.Error(err),
		)
		return nil, nil, nil, err
	}
	logger.App.Debug(
		"fetcher.Fetch",
		zap.Uint("teacherID", uint(teacher.ID)),
		zap.Int("lessons", len(fetchedLessons)),
	)

	//fmt.Printf("fetchedLessons ---\n")
	//for _, l := range fetchedLessons {
	//	fmt.Printf("teacherID=%v, datetime=%v, status=%v\n", l.TeacherId, l.Datetime, l.Status)
	//}

	now := time.Now().In(config.LocalTimezone())
	fromDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, config.LocalTimezone())
	toDate := fromDate.Add(24 * 6 * time.Hour)
	lastFetchedLessons, err := n.lessonService.FindLessons(teacher.ID, fromDate, toDate)
	if err != nil {
		return nil, nil, nil, err
	}
	//fmt.Printf("lastFetchedLessons ---\n")
	//for _, l := range lastFetchedLessons {
	//	fmt.Printf("teacherID=%v, datetime=%v, status=%v\n", l.TeacherId, l.Datetime, l.Status)
	//}

	newAvailableLessons := n.lessonService.GetNewAvailableLessons(lastFetchedLessons, fetchedLessons)
	//fmt.Printf("newAvailableLessons ---\n")
	//for _, l := range newAvailableLessons {
	//	fmt.Printf("teacherID=%v, datetime=%v, status=%v\n", l.TeacherId, l.Datetime, l.Status)
	//}
	return teacher, fetchedLessons, newAvailableLessons, nil
}

func (n *Notifier) sendNotificationToUser(
	user *model.User,
	lessonsPerTeacher map[uint32][]*model.Lesson,
) error {
	lessonsCount := 0
	var teacherIDs []int
	for teacherID, lessons := range lessonsPerTeacher {
		teacherIDs = append(teacherIDs, int(teacherID))
		lessonsCount += len(lessons)
	}
	if lessonsCount == 0 {
		// Don't send notification
		return nil
	}

	sort.Ints(teacherIDs)
	var teacherIDs2 []uint32
	var teacherNames []string
	for _, id := range teacherIDs {
		teacherIDs2 = append(teacherIDs2, uint32(id))
		teacherNames = append(teacherNames, n.teachers[uint32(id)].Name)
	}

	t := emailer.NewTemplate("notifier", getEmailTemplateJP())
	data := struct {
		To                string
		TeacherNames      string
		TeacherIDs        []uint32
		Teachers          map[uint32]*model.Teacher
		LessonsPerTeacher map[uint32][]*model.Lesson
		WebURL            string
	}{
		To:                user.Email.Raw(),
		TeacherNames:      strings.Join(teacherNames, ", "),
		TeacherIDs:        teacherIDs2,
		Teachers:          n.teachers,
		LessonsPerTeacher: lessonsPerTeacher,
		WebURL:            config.WebURL(),
	}
	email, err := emailer.NewEmailFromTemplate(t, data)
	if err != nil {
		return errors.InternalWrapf(err, "Failed to create emailer.Email from template")
	}
	//fmt.Printf("--- mail ---\n%s", email.BodyString())

	logger.App.Info("sendNotificationToUser", zap.String("email", user.Email.Raw()))
	return n.sender.Send(email)
}

func getEmailTemplateJP() string {
	return strings.TrimSpace(`
From: lekcije@lekcije.com
To: {{ .To }}
Subject: {{ .TeacherNames }}の空きレッスンがあります
Body: text/html
{{ range $teacherID := .TeacherIDs }}
{{- $teacher := index $.Teachers $teacherID -}}
--- {{ $teacher.Name }} ---
  {{- $lessons := index $.LessonsPerTeacher $teacherID }}
  {{- range $lesson := $lessons }}
{{ $lesson.Datetime.Format "2006-01-02 15:04" }}
  {{- end }}

レッスンの予約はこちらから:
PC
Mobile

{{ end }}
空きレッスンの通知の解除はこちら
	`)
}

//func getEmailTemplateEN() string {
//	return strings.TrimSpace(`
//{{- range $teacherID := .TeacherIDs }}
//{{- $teacher := index $.Teachers $teacherID -}}
//--- {{ $teacher.Name }} ---
//  {{- $lessons := index $.LessonsPerTeacher $teacherID }}
//  {{- range $lesson := $lessons }}
//{{ $lesson.Datetime.Format "2006-01-02 15:04" }}
//  {{- end }}
//
//Reserve here:
//PC
//Mobile
//{{ end }}
//Click here if you want to stop notification of the teacher.
//	`)
//}

func (n *Notifier) Close() {
	defer n.fetcher.Close()
	defer func() {
		if n.dryRun {
			return
		}
		for teacherID, lessons := range n.fetchedLessons {
			if _, err := n.lessonService.UpdateLessons(lessons); err != nil {
				logger.App.Error(
					"An error ocurred in Notifier.Close",
					zap.Error(err), zap.Uint("teacherID", uint(teacherID)),
				)
			}
		}
	}()
}
<|endoftext|>


<commit_before>/*-
 * Copyright (c) 2016, 1&1 Internet SE
 * Copyright (c) 2016, Jörg Pernfuß
 *
 * Use of this source code is governed by a 2-clause BSD license
 * that can be found in the LICENSE file.
 */

package adm

import (
	"fmt"
	"strconv"

	"github.com/1and1/soma/lib/proto"
	resty "gopkg.in/resty.v0"
)

// LookupOncallId looks up the UUID for an oncall duty on the
// server with name s. Error is set if no such oncall duty was
// found or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupOncallId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return oncallIdByName(s)
}

// LookupOncallId looks up the UUID for a user on the server
// with username s. Error is set if no such user was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupUserId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return userIdByUserName(s)
}

// LookupTeamId looks up the UUID for a team on the server
// with teamname s. Error is set if no such team was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupTeamId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return teamIdByName(s)
}

// LookupRepoId looks up the UUID for a repository on the server
// with reponame s. Error is set if no such repository was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupRepoId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return repoIdByName(s)
}

// LookupBucketId looks up the UUID for a bucket on the server
// with bucketname s. Error is set if no such bucket was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupBucketId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return bucketIdByName(s)
}

// LookupServerId looks up the UUID for a server either in the
// local cache or on the server. Error is set if no such server
// was found or an error occured.
// If s is already a UUID, then s is immediately returned.
// If s is a Uint64 number, then the serverlookup is by AssetID.
// Otherwise s is the server name.
func LookupServerId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	if ok, num := isUint64(s); ok {
		return serverIdByAsset(s, num)
	}
	return serverIdByName(s)
}

// LookupPermIdRef looks up the UUID for a permission from
// the server. Error is set if no such permission was found or
// an error occured.
// If s is already a UUID, then is is immediately returned.
func LookupPermIdRef(s string, id *string) error {
	if IsUUID(s) {
		*id = s
		return nil
	}
	return permissionIdByName(s, id)
}

// LookupGrantIdRef lookup up the UUID of a permission grant from
// the server and fills it into the provided id pointer.
// Error is set if no such grant was found or an error occured.
func LookupGrantIdRef(rcptType, rcptId, permId, cat string,
	id *string) error {
	return grantIdFromServer(rcptType, rcptId, permId, cat, id)
}

// oncallIdByName implements the actual serverside lookup of the
// oncall duty UUID
func oncallIdByName(oncall string) (string, error) {
	req := proto.NewOncallFilter()
	req.Filter.Oncall = &proto.OncallFilter{Name: oncall}

	res, err := fetchFilter(req, `/filter/oncall/`)
	if err != nil {
		goto abort
	}

	// check the received record against the input
	if oncall != (*res.Oncalls)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			oncall, (*res.Oncalls)[0].Name)
		goto abort
	}
	return (*res.Oncalls)[0].Id, nil

abort:
	return ``, fmt.Errorf("OncallId lookup failed: %s", err.Error())
}

// userIdByUserName implements the actual serverside lookup of the
// user's UUID
func userIdByUserName(user string) (string, error) {
	req := proto.NewUserFilter()
	req.Filter.User.UserName = user

	res, err := fetchFilter(req, `/filter/users/`)
	if err != nil {
		goto abort
	}

	// check the received record against the input
	if user != (*res.Users)[0].UserName {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			user, (*res.Users)[0].UserName)
		goto abort
	}
	return (*res.Users)[0].Id, nil

abort:
	return ``, fmt.Errorf("UserId lookup failed: %s", err.Error())
}

// teamIdByName implements the actual serverside lookup of the
// team's UUID
func teamIdByName(team string) (string, error) {
	req := proto.NewTeamFilter()
	req.Filter.Team.Name = team

	res, err := fetchFilter(req, `/filter/teams/`)
	if err != nil {
		goto abort
	}

	// check the received record against the input
	if team != (*res.Teams)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			team, (*res.Teams)[0].Name)
		goto abort
	}
	return (*res.Teams)[0].Id, nil

abort:
	return ``, fmt.Errorf("TeamId lookup failed: %s", err.Error())
}

// repoIdByName implements the actual serverside lookup of the
// repo's UUID
func repoIdByName(repo string) (string, error) {
	req := proto.NewRepositoryFilter()
	req.Filter.Repository.Name = repo

	res, err := fetchFilter(req, `/filter/repository/`)
	if err != nil {
		goto abort
	}

	// check the received record against the input
	if repo != (*res.Repositories)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			repo, (*res.Repositories)[0].Name)
		goto abort
	}
	return (*res.Repositories)[0].Id, nil

abort:
	return ``, fmt.Errorf("RepositoryId lookup failed: %s",
		err.Error())
}

// bucketIdByName implements the actual serverside lookup of the
// bucket's UUID
func bucketIdByName(bucket string) (string, error) {
	req := proto.NewBucketFilter()
	req.Filter.Bucket.Name = bucket

	res, err := fetchFilter(req, `/filter/buckets/`)
	if err != nil {
		goto abort
	}

	// check the received record against the input
	if bucket != (*res.Buckets)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			bucket, (*res.Buckets)[0].Name)
		goto abort
	}
	return (*res.Buckets)[0].Id, nil

abort:
	return ``, fmt.Errorf("BucketId lookup failed: %s",
		err.Error())
}

// serverIdByName implements the actual lookup of the server UUID
// by name
func serverIdByName(s string) (string, error) {
	if m, err := cache.ServerByName(s); err == nil {
		return m[`id`], nil
	}
	req := proto.NewServerFilter()
	req.Filter.Server.Name = s

	res, err := fetchFilter(req, `/filter/servers/`)
	if err != nil {
		goto abort
	}

	if s != (*res.Servers)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			s, (*res.Servers)[0].Name)
		goto abort
	}
	// save server in cacheDB
	cache.Server(
		(*res.Servers)[0].Name,
		(*res.Servers)[0].Id,
		strconv.Itoa(int((*res.Servers)[0].AssetId)),
	)
	return (*res.Servers)[0].Id, nil

abort:
	return ``, fmt.Errorf("ServerId lookup failed: %s",
		err.Error())
}

// serverIdByAsset implements the actual lookup of the server UUID
// by numeric AssetID
func serverIdByAsset(s string, aid uint64) (string, error) {
	if m, err := cache.ServerByAsset(s); err == nil {
		return m[`id`], nil
	}
	req := proto.NewServerFilter()
	req.Filter.Server.AssetId = aid

	res, err := fetchFilter(req, `/filter/servers/`)
	if err != nil {
		goto abort
	}

	if aid != (*res.Servers)[0].AssetId {
		err = fmt.Errorf("AssetId mismatch: %d vs %d",
			aid, (*res.Servers)[0].AssetId)
		goto abort
	}
	// save server in cacheDB
	cache.Server(
		(*res.Servers)[0].Name,
		(*res.Servers)[0].Id,
		strconv.Itoa(int((*res.Servers)[0].AssetId)),
	)
	return (*res.Servers)[0].Id, nil

abort:
	return ``, fmt.Errorf("ServerId lookup failed: %s",
		err.Error())
}

// permissionIdByName implements the actual lookup of the permission
// UUID by name
func permissionIdByName(perm string, id *string) error {
	req := proto.NewPermissionFilter()
	req.Filter.Permission.Name = perm

	res, err := fetchFilter(req, `/filter/permission/`)
	if err != nil {
		goto abort
	}

	if perm != (*res.Permissions)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			perm, (*res.Permissions)[0].Name)
		goto abort
	}
	*id = (*res.Permissions)[0].Id
	return nil

abort:
	return fmt.Errorf("PermissionId lookup failed: %s",
		err.Error())
}

// grantIdFromServer implements the actual lookup of the grant UUID
func grantIdFromServer(rcptType, rcptId, permId, cat string,
	id *string) error {
	req := proto.NewGrantFilter()
	req.Filter.Grant.RecipientType = rcptType
	req.Filter.Grant.RecipientId = rcptId
	req.Filter.Grant.PermissionId = permId
	req.Filter.Grant.Category = cat

	res, err := fetchFilter(req, `/filter/grant/`)
	if err != nil {
		goto abort
	}

	if permId != (*res.Grants)[0].PermissionId {
		err = fmt.Errorf("PermissionId mismatch: %s vs %s",
			permId, (*res.Grants)[0].PermissionId)
		goto abort
	}
	*id = (*res.Grants)[0].Id

abort:
	return fmt.Errorf("GrantId lookup failed: %s",
		err.Error())
}

// fetchFilter is a helper used in the ...IdByFoo functions
func fetchFilter(req proto.Request, path string) (*proto.Result, error) {
	var (
		err  error
		resp *resty.Response
		res  *proto.Result
	)
	if resp, err = PostReqBody(req, path); err != nil {
		// transport errors
		return nil, err
	}

	if res, err = decodeResponse(resp); err != nil {
		// http code errors
		return nil, err
	}

	if err = checkApplicationError(res); err != nil {
		return nil, err
	}
	return res, nil
}

// checkApplicationError tests the server result for
// application errors
func checkApplicationError(result *proto.Result) error {
	if result.StatusCode >= 300 {
		var s string
		// application errors
		if result.StatusCode == 404 {
			s = fmt.Sprintf("Object lookup error: %d - %s",
				result.StatusCode, result.StatusText)
		} else {
			s = fmt.Sprintf("Application error: %d - %s",
				result.StatusCode, result.StatusText)
		}
		m := []string{s}

		if result.Errors != nil {
			m = append(m, *result.Errors...)
		}

		return fmt.Errorf(combineStrings(m...))
	}
	return nil
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Buckle up and add nilptr checks<commit_after>/*-
 * Copyright (c) 2016, 1&1 Internet SE
 * Copyright (c) 2016, Jörg Pernfuß
 *
 * Use of this source code is governed by a 2-clause BSD license
 * that can be found in the LICENSE file.
 */

package adm

import (
	"fmt"
	"strconv"

	"github.com/1and1/soma/lib/proto"
	resty "gopkg.in/resty.v0"
)

// LookupOncallId looks up the UUID for an oncall duty on the
// server with name s. Error is set if no such oncall duty was
// found or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupOncallId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return oncallIdByName(s)
}

// LookupOncallId looks up the UUID for a user on the server
// with username s. Error is set if no such user was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupUserId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return userIdByUserName(s)
}

// LookupTeamId looks up the UUID for a team on the server
// with teamname s. Error is set if no such team was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupTeamId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return teamIdByName(s)
}

// LookupRepoId looks up the UUID for a repository on the server
// with reponame s. Error is set if no such repository was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupRepoId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return repoIdByName(s)
}

// LookupBucketId looks up the UUID for a bucket on the server
// with bucketname s. Error is set if no such bucket was found
// or an error occured.
// If s is already a UUID, then s is immediately returned.
func LookupBucketId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	return bucketIdByName(s)
}

// LookupServerId looks up the UUID for a server either in the
// local cache or on the server. Error is set if no such server
// was found or an error occured.
// If s is already a UUID, then s is immediately returned.
// If s is a Uint64 number, then the serverlookup is by AssetID.
// Otherwise s is the server name.
func LookupServerId(s string) (string, error) {
	if IsUUID(s) {
		return s, nil
	}
	if ok, num := isUint64(s); ok {
		return serverIdByAsset(s, num)
	}
	return serverIdByName(s)
}

// LookupPermIdRef looks up the UUID for a permission from
// the server. Error is set if no such permission was found or
// an error occured.
// If s is already a UUID, then is is immediately returned.
func LookupPermIdRef(s string, id *string) error {
	if IsUUID(s) {
		*id = s
		return nil
	}
	return permissionIdByName(s, id)
}

// LookupGrantIdRef lookup up the UUID of a permission grant from
// the server and fills it into the provided id pointer.
// Error is set if no such grant was found or an error occured.
func LookupGrantIdRef(rcptType, rcptId, permId, cat string,
	id *string) error {
	return grantIdFromServer(rcptType, rcptId, permId, cat, id)
}

// oncallIdByName implements the actual serverside lookup of the
// oncall duty UUID
func oncallIdByName(oncall string) (string, error) {
	req := proto.NewOncallFilter()
	req.Filter.Oncall = &proto.OncallFilter{Name: oncall}

	res, err := fetchFilter(req, `/filter/oncall/`)
	if err != nil {
		goto abort
	}

	if res.Oncalls == nil || len(*res.Oncalls) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	// check the received record against the input
	if oncall != (*res.Oncalls)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			oncall, (*res.Oncalls)[0].Name)
		goto abort
	}
	return (*res.Oncalls)[0].Id, nil

abort:
	return ``, fmt.Errorf("OncallId lookup failed: %s", err.Error())
}

// userIdByUserName implements the actual serverside lookup of the
// user's UUID
func userIdByUserName(user string) (string, error) {
	req := proto.NewUserFilter()
	req.Filter.User.UserName = user

	res, err := fetchFilter(req, `/filter/users/`)
	if err != nil {
		goto abort
	}

	if res.Users == nil || len(*res.Users) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	// check the received record against the input
	if user != (*res.Users)[0].UserName {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			user, (*res.Users)[0].UserName)
		goto abort
	}
	return (*res.Users)[0].Id, nil

abort:
	return ``, fmt.Errorf("UserId lookup failed: %s", err.Error())
}

// teamIdByName implements the actual serverside lookup of the
// team's UUID
func teamIdByName(team string) (string, error) {
	req := proto.NewTeamFilter()
	req.Filter.Team.Name = team

	res, err := fetchFilter(req, `/filter/teams/`)
	if err != nil {
		goto abort
	}

	if res.Teams == nil || len(*res.Teams) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	// check the received record against the input
	if team != (*res.Teams)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			team, (*res.Teams)[0].Name)
		goto abort
	}
	return (*res.Teams)[0].Id, nil

abort:
	return ``, fmt.Errorf("TeamId lookup failed: %s", err.Error())
}

// repoIdByName implements the actual serverside lookup of the
// repo's UUID
func repoIdByName(repo string) (string, error) {
	req := proto.NewRepositoryFilter()
	req.Filter.Repository.Name = repo

	res, err := fetchFilter(req, `/filter/repository/`)
	if err != nil {
		goto abort
	}

	if res.Repositories == nil || len(*res.Repositories) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	// check the received record against the input
	if repo != (*res.Repositories)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			repo, (*res.Repositories)[0].Name)
		goto abort
	}
	return (*res.Repositories)[0].Id, nil

abort:
	return ``, fmt.Errorf("RepositoryId lookup failed: %s",
		err.Error())
}

// bucketIdByName implements the actual serverside lookup of the
// bucket's UUID
func bucketIdByName(bucket string) (string, error) {
	req := proto.NewBucketFilter()
	req.Filter.Bucket.Name = bucket

	res, err := fetchFilter(req, `/filter/buckets/`)
	if err != nil {
		goto abort
	}

	if res.Buckets == nil || len(*res.Buckets) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	// check the received record against the input
	if bucket != (*res.Buckets)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			bucket, (*res.Buckets)[0].Name)
		goto abort
	}
	return (*res.Buckets)[0].Id, nil

abort:
	return ``, fmt.Errorf("BucketId lookup failed: %s",
		err.Error())
}

// serverIdByName implements the actual lookup of the server UUID
// by name
func serverIdByName(s string) (string, error) {
	if m, err := cache.ServerByName(s); err == nil {
		return m[`id`], nil
	}
	req := proto.NewServerFilter()
	req.Filter.Server.Name = s

	res, err := fetchFilter(req, `/filter/servers/`)
	if err != nil {
		goto abort
	}

	if res.Servers == nil || len(*res.Servers) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	if s != (*res.Servers)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			s, (*res.Servers)[0].Name)
		goto abort
	}
	// save server in cacheDB
	cache.Server(
		(*res.Servers)[0].Name,
		(*res.Servers)[0].Id,
		strconv.Itoa(int((*res.Servers)[0].AssetId)),
	)
	return (*res.Servers)[0].Id, nil

abort:
	return ``, fmt.Errorf("ServerId lookup failed: %s",
		err.Error())
}

// serverIdByAsset implements the actual lookup of the server UUID
// by numeric AssetID
func serverIdByAsset(s string, aid uint64) (string, error) {
	if m, err := cache.ServerByAsset(s); err == nil {
		return m[`id`], nil
	}
	req := proto.NewServerFilter()
	req.Filter.Server.AssetId = aid

	res, err := fetchFilter(req, `/filter/servers/`)
	if err != nil {
		goto abort
	}

	if res.Servers == nil || len(*res.Servers) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	if aid != (*res.Servers)[0].AssetId {
		err = fmt.Errorf("AssetId mismatch: %d vs %d",
			aid, (*res.Servers)[0].AssetId)
		goto abort
	}
	// save server in cacheDB
	cache.Server(
		(*res.Servers)[0].Name,
		(*res.Servers)[0].Id,
		strconv.Itoa(int((*res.Servers)[0].AssetId)),
	)
	return (*res.Servers)[0].Id, nil

abort:
	return ``, fmt.Errorf("ServerId lookup failed: %s",
		err.Error())
}

// permissionIdByName implements the actual lookup of the permission
// UUID by name
func permissionIdByName(perm string, id *string) error {
	req := proto.NewPermissionFilter()
	req.Filter.Permission.Name = perm

	res, err := fetchFilter(req, `/filter/permission/`)
	if err != nil {
		goto abort
	}

	if res.Permissions == nil || len(*res.Permissions) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	if perm != (*res.Permissions)[0].Name {
		err = fmt.Errorf("Name mismatch: %s vs %s",
			perm, (*res.Permissions)[0].Name)
		goto abort
	}
	*id = (*res.Permissions)[0].Id
	return nil

abort:
	return fmt.Errorf("PermissionId lookup failed: %s",
		err.Error())
}

// grantIdFromServer implements the actual lookup of the grant UUID
func grantIdFromServer(rcptType, rcptId, permId, cat string,
	id *string) error {
	req := proto.NewGrantFilter()
	req.Filter.Grant.RecipientType = rcptType
	req.Filter.Grant.RecipientId = rcptId
	req.Filter.Grant.PermissionId = permId
	req.Filter.Grant.Category = cat

	res, err := fetchFilter(req, `/filter/grant/`)
	if err != nil {
		goto abort
	}

	if res.Grants == nil || len(*res.Grants) == 0 {
		err = fmt.Errorf(`no object returned`)
		goto abort
	}

	if permId != (*res.Grants)[0].PermissionId {
		err = fmt.Errorf("PermissionId mismatch: %s vs %s",
			permId, (*res.Grants)[0].PermissionId)
		goto abort
	}
	*id = (*res.Grants)[0].Id

abort:
	return fmt.Errorf("GrantId lookup failed: %s",
		err.Error())
}

// fetchFilter is a helper used in the ...IdByFoo functions
func fetchFilter(req proto.Request, path string) (*proto.Result, error) {
	var (
		err  error
		resp *resty.Response
		res  *proto.Result
	)
	if resp, err = PostReqBody(req, path); err != nil {
		// transport errors
		return nil, err
	}

	if res, err = decodeResponse(resp); err != nil {
		// http code errors
		return nil, err
	}

	if err = checkApplicationError(res); err != nil {
		return nil, err
	}
	return res, nil
}

// checkApplicationError tests the server result for
// application errors
func checkApplicationError(result *proto.Result) error {
	if result.StatusCode >= 300 {
		var s string
		// application errors
		if result.StatusCode == 404 {
			s = fmt.Sprintf("Object lookup error: %d - %s",
				result.StatusCode, result.StatusText)
		} else {
			s = fmt.Sprintf("Application error: %d - %s",
				result.StatusCode, result.StatusText)
		}
		m := []string{s}

		if result.Errors != nil {
			m = append(m, *result.Errors...)
		}

		return fmt.Errorf(combineStrings(m...))
	}
	return nil
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"bytes"

	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
	"github.com/decred/dcrd/wire"
)

// lotteryDataForNode is a helper function that returns winning tickets
// along with the ticket pool size and PRNG checksum for a given node.
//
// This function is NOT safe for concurrent access and MUST be called
// with the chainLock held for writes.
func (b *BlockChain) lotteryDataForNode(node *blockNode) ([]chainhash.Hash, int, [6]byte, error) {
	if node.height < b.chainParams.StakeEnabledHeight {
		return nil, 0, [6]byte{}, nil
	}
	stakeNode, err := b.fetchStakeNode(node)
	if err != nil {
		return nil, 0, [6]byte{}, err
	}

	return stakeNode.Winners(), stakeNode.PoolSize(), stakeNode.FinalState(), nil
}

// lotteryDataForBlock takes a node block hash and returns the next tickets
// eligible for voting, the number of tickets in the ticket pool, and the
// final state of the PRNG.
//
// This function is NOT safe for concurrent access and must have the chainLock
// held for write access.
func (b *BlockChain) lotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) {
	node := b.index.LookupNode(hash)
	if node == nil {
		return nil, 0, [6]byte{}, unknownBlockError(hash)
	}

	winningTickets, poolSize, finalState, err := b.lotteryDataForNode(node)
	if err != nil {
		return nil, 0, [6]byte{}, err
	}

	return winningTickets, poolSize, finalState, nil
}

// LotteryDataForBlock returns lottery data for a given block in the block
// chain, including side chain blocks.
//
// It is safe for concurrent access.
func (b *BlockChain) LotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) {
	b.chainLock.Lock()
	winningTickets, poolSize, finalState, err := b.lotteryDataForBlock(hash)
	b.chainLock.Unlock()
	return winningTickets, poolSize, finalState, err
}

// LiveTickets returns all currently live tickets from the stake database.
//
// This function is safe for concurrent access.
func (b *BlockChain) LiveTickets() ([]chainhash.Hash, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.LiveTickets(), nil
}

// MissedTickets returns all currently missed tickets from the stake database.
//
// This function is safe for concurrent access.
func (b *BlockChain) MissedTickets() ([]chainhash.Hash, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.MissedTickets(), nil
}

// TicketsWithAddress returns a slice of ticket hashes that are currently live
// corresponding to the given address.
//
// This function is safe for concurrent access.
func (b *BlockChain) TicketsWithAddress(address stdaddr.StakeAddress) ([]chainhash.Hash, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	var ticketsWithAddr []chainhash.Hash
	votingRightsScriptVer, votingRightsScript := address.VotingRightsScript()
	tickets := sn.LiveTickets()
	for _, hash := range tickets {
		outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake}
		utxo, err := b.utxoCache.FetchEntry(outpoint)
		if err != nil {
			return nil, err
		}

		if utxo.ScriptVersion() == votingRightsScriptVer &&
			bytes.Equal(utxo.PkScript(), votingRightsScript) {

			ticketsWithAddr = append(ticketsWithAddr, hash)
		}
	}

	return ticketsWithAddr, nil
}

// CheckLiveTicket returns whether or not a ticket exists in the live ticket
// treap of the best node.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckLiveTicket(hash chainhash.Hash) bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.ExistsLiveTicket(hash)
}

// CheckLiveTickets returns a slice of bools representing whether each ticket
// exists in the live ticket treap of the best node.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckLiveTickets(hashes []chainhash.Hash) []bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	existsSlice := make([]bool, len(hashes))
	for i := range hashes {
		existsSlice[i] = sn.ExistsLiveTicket(hashes[i])
	}

	return existsSlice
}

// CheckMissedTickets returns a slice of bools representing whether each ticket
// hash has been missed in the live ticket treap of the best node.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckMissedTickets(hashes []chainhash.Hash) []bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	existsSlice := make([]bool, len(hashes))
	for i := range hashes {
		existsSlice[i] = sn.ExistsMissedTicket(hashes[i])
	}

	return existsSlice
}

// CheckExpiredTicket returns whether or not a ticket was ever expired.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckExpiredTicket(hash chainhash.Hash) bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.ExistsExpiredTicket(hash)
}

// CheckExpiredTickets returns whether or not a ticket in a slice of
// tickets was ever expired.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckExpiredTickets(hashes []chainhash.Hash) []bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	existsSlice := make([]bool, len(hashes))
	for i := range hashes {
		existsSlice[i] = sn.ExistsExpiredTicket(hashes[i])
	}

	return existsSlice
}

// TicketPoolValue returns the current value of all the locked funds in the
// ticket pool.
//
// This function is safe for concurrent access.  All live tickets are at least
// 256 blocks deep on mainnet, so the UTXO set should generally always have
// the asked for transactions.
func (b *BlockChain) TicketPoolValue() (dcrutil.Amount, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	var amt int64
	for _, hash := range sn.LiveTickets() {
		outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake}
		utxo, err := b.utxoCache.FetchEntry(outpoint)
		if err != nil {
			return 0, err
		}

		amt += utxo.Amount()
	}
	return dcrutil.Amount(amt), nil
}
<commit_msg>blockchain: Remove unused MissedTickets.<commit_after>// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"bytes"

	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
	"github.com/decred/dcrd/wire"
)

// lotteryDataForNode is a helper function that returns winning tickets
// along with the ticket pool size and PRNG checksum for a given node.
//
// This function is NOT safe for concurrent access and MUST be called
// with the chainLock held for writes.
func (b *BlockChain) lotteryDataForNode(node *blockNode) ([]chainhash.Hash, int, [6]byte, error) {
	if node.height < b.chainParams.StakeEnabledHeight {
		return nil, 0, [6]byte{}, nil
	}
	stakeNode, err := b.fetchStakeNode(node)
	if err != nil {
		return nil, 0, [6]byte{}, err
	}

	return stakeNode.Winners(), stakeNode.PoolSize(), stakeNode.FinalState(), nil
}

// lotteryDataForBlock takes a node block hash and returns the next tickets
// eligible for voting, the number of tickets in the ticket pool, and the
// final state of the PRNG.
//
// This function is NOT safe for concurrent access and must have the chainLock
// held for write access.
func (b *BlockChain) lotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) {
	node := b.index.LookupNode(hash)
	if node == nil {
		return nil, 0, [6]byte{}, unknownBlockError(hash)
	}

	winningTickets, poolSize, finalState, err := b.lotteryDataForNode(node)
	if err != nil {
		return nil, 0, [6]byte{}, err
	}

	return winningTickets, poolSize, finalState, nil
}

// LotteryDataForBlock returns lottery data for a given block in the block
// chain, including side chain blocks.
//
// It is safe for concurrent access.
func (b *BlockChain) LotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) {
	b.chainLock.Lock()
	winningTickets, poolSize, finalState, err := b.lotteryDataForBlock(hash)
	b.chainLock.Unlock()
	return winningTickets, poolSize, finalState, err
}

// LiveTickets returns all currently live tickets from the stake database.
//
// This function is safe for concurrent access.
func (b *BlockChain) LiveTickets() ([]chainhash.Hash, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.LiveTickets(), nil
}

// TicketsWithAddress returns a slice of ticket hashes that are currently live
// corresponding to the given address.
//
// This function is safe for concurrent access.
func (b *BlockChain) TicketsWithAddress(address stdaddr.StakeAddress) ([]chainhash.Hash, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	var ticketsWithAddr []chainhash.Hash
	votingRightsScriptVer, votingRightsScript := address.VotingRightsScript()
	tickets := sn.LiveTickets()
	for _, hash := range tickets {
		outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake}
		utxo, err := b.utxoCache.FetchEntry(outpoint)
		if err != nil {
			return nil, err
		}

		if utxo.ScriptVersion() == votingRightsScriptVer &&
			bytes.Equal(utxo.PkScript(), votingRightsScript) {

			ticketsWithAddr = append(ticketsWithAddr, hash)
		}
	}

	return ticketsWithAddr, nil
}

// CheckLiveTicket returns whether or not a ticket exists in the live ticket
// treap of the best node.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckLiveTicket(hash chainhash.Hash) bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.ExistsLiveTicket(hash)
}

// CheckLiveTickets returns a slice of bools representing whether each ticket
// exists in the live ticket treap of the best node.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckLiveTickets(hashes []chainhash.Hash) []bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	existsSlice := make([]bool, len(hashes))
	for i := range hashes {
		existsSlice[i] = sn.ExistsLiveTicket(hashes[i])
	}

	return existsSlice
}

// CheckMissedTickets returns a slice of bools representing whether each ticket
// hash has been missed in the live ticket treap of the best node.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckMissedTickets(hashes []chainhash.Hash) []bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	existsSlice := make([]bool, len(hashes))
	for i := range hashes {
		existsSlice[i] = sn.ExistsMissedTicket(hashes[i])
	}

	return existsSlice
}

// CheckExpiredTicket returns whether or not a ticket was ever expired.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckExpiredTicket(hash chainhash.Hash) bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	return sn.ExistsExpiredTicket(hash)
}

// CheckExpiredTickets returns whether or not a ticket in a slice of
// tickets was ever expired.
//
// This function is safe for concurrent access.
func (b *BlockChain) CheckExpiredTickets(hashes []chainhash.Hash) []bool {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	existsSlice := make([]bool, len(hashes))
	for i := range hashes {
		existsSlice[i] = sn.ExistsExpiredTicket(hashes[i])
	}

	return existsSlice
}

// TicketPoolValue returns the current value of all the locked funds in the
// ticket pool.
//
// This function is safe for concurrent access.  All live tickets are at least
// 256 blocks deep on mainnet, so the UTXO set should generally always have
// the asked for transactions.
func (b *BlockChain) TicketPoolValue() (dcrutil.Amount, error) {
	b.chainLock.RLock()
	sn := b.bestChain.Tip().stakeNode
	b.chainLock.RUnlock()

	var amt int64
	for _, hash := range sn.LiveTickets() {
		outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake}
		utxo, err := b.utxoCache.FetchEntry(outpoint)
		if err != nil {
			return 0, err
		}

		amt += utxo.Amount()
	}
	return dcrutil.Amount(amt), nil
}
<|endoftext|>