<commit_before>package tordir

import (
	"errors"
	"fmt"
	"net"
	"regexp"
	"strconv"
	"time"
)

const (
	routerKeyword    = "router"
	bandwidthKeyword = "bandwidth"
	publishedKeyword = "published"
)

var requiredKeywords = []string{
	routerKeyword,
	bandwidthKeyword,
	publishedKeyword,
}

// Potential errors when constructing a server descriptor.
var (
	ErrServerDescriptorBadNickname = errors.New("invalid nickname")
	ErrServerDescriptorNotIPv4     = errors.New("require ipv4 address")
)

// ServerDescriptorMissingFieldError indicates that a required field is
// missing from a server descriptor.
type ServerDescriptorMissingFieldError string

func (e ServerDescriptorMissingFieldError) Error() string {
	return fmt.Sprintf("missing field '%s'", string(e))
}

// ServerDescriptor is a builder for a server descriptor to be published to
// directory servers.
type ServerDescriptor struct {
	router   *Item
	items    []*Item
	keywords map[string]bool
}

// NewServerDescriptor constructs an empty server descriptor.
func NewServerDescriptor() *ServerDescriptor {
	return &ServerDescriptor{
		items:    make([]*Item, 0),
		keywords: make(map[string]bool),
	}
}

func (d *ServerDescriptor) addItem(item *Item) {
	d.items = append(d.items, item)
	d.keywords[item.Keyword] = true
}

// XXX cite
var nicknameRx = regexp.MustCompile(`^[[:alnum:]]{1,19}$`)

// SetRouter sets the router description. This is required.
// XXX cite
func (d *ServerDescriptor) SetRouter(nickname string, addr net.IP, orPort, dirPort uint16) error {
	if !nicknameRx.MatchString(nickname) {
		return ErrServerDescriptorBadNickname
	}

	addr = addr.To4()
	if addr == nil {
		return ErrServerDescriptorNotIPv4
	}

	args := []string{
		nickname,
		addr.String(),
		strconv.FormatUint(uint64(orPort), 10),
		"0", // SOCKSPort
		strconv.FormatUint(uint64(dirPort), 10),
	}
	d.router = NewItem(routerKeyword, args)
	d.keywords[routerKeyword] = true
	return nil
}

// SetBandwidth sets the bandwidth of the server.
// XXX cite
func (d *ServerDescriptor) SetBandwidth(avg, burst, observed int) error {
	args := []string{
		strconv.Itoa(avg),
		strconv.Itoa(burst),
		strconv.Itoa(observed),
	}
	d.addItem(NewItem(bandwidthKeyword, args))
	return nil
}

// SetPublishedTime sets the time the descriptor was published.
// XXX cite
func (d *ServerDescriptor) SetPublishedTime(t time.Time) error {
	args := []string{
		t.In(time.UTC).Format("2006-01-02 15:04:05"),
	}
	d.addItem(NewItem(publishedKeyword, args))
	return nil
}

// Validate checks whether the descriptor is valid.
func (d *ServerDescriptor) Validate() error {
	for _, keyword := range requiredKeywords {
		_, ok := d.keywords[keyword]
		if !ok {
			return ServerDescriptorMissingFieldError(keyword)
		}
	}
	return nil
}

// Document generates the Document for this descriptor.
func (d *ServerDescriptor) Document() (*Document, error) {
	err := d.Validate()
	if err != nil {
		return nil, err
	}

	doc := &Document{}
	doc.AddItem(d.router)
	for _, item := range d.items {
		doc.AddItem(item)
	}
	return doc, nil
}
<commit_msg>cite references<commit_after>package tordir

import (
	"errors"
	"fmt"
	"net"
	"regexp"
	"strconv"
	"time"
)

const (
	routerKeyword    = "router"
	bandwidthKeyword = "bandwidth"
	publishedKeyword = "published"
)

var requiredKeywords = []string{
	routerKeyword,
	bandwidthKeyword,
	publishedKeyword,
}

// Potential errors when constructing a server descriptor.
var (
	ErrServerDescriptorBadNickname = errors.New("invalid nickname")
	ErrServerDescriptorNotIPv4     = errors.New("require ipv4 address")
)

// ServerDescriptorMissingFieldError indicates that a required field is
// missing from a server descriptor.
type ServerDescriptorMissingFieldError string

func (e ServerDescriptorMissingFieldError) Error() string {
	return fmt.Sprintf("missing field '%s'", string(e))
}

// ServerDescriptor is a builder for a server descriptor to be published to
// directory servers.
type ServerDescriptor struct {
	router   *Item
	items    []*Item
	keywords map[string]bool
}

// NewServerDescriptor constructs an empty server descriptor.
func NewServerDescriptor() *ServerDescriptor {
	return &ServerDescriptor{
		items:    make([]*Item, 0),
		keywords: make(map[string]bool),
	}
}

func (d *ServerDescriptor) addItem(item *Item) {
	d.items = append(d.items, item)
	d.keywords[item.Keyword] = true
}

// Reference: https://github.com/torproject/torspec/blob/master/dir-spec.txt#L1180-L1181
//
//	   nickname ::= between 1 and 19 alphanumeric characters ([A-Za-z0-9]),
//	      case-insensitive.
//
var nicknameRx = regexp.MustCompile(`^[[:alnum:]]{1,19}$`)

// SetRouter sets the router description. This is required.
//
// Reference: https://github.com/torproject/torspec/blob/master/dir-spec.txt#L379-L394
//
//	     "router" nickname address ORPort SOCKSPort DirPort NL
//
//	       [At start, exactly once.]
//
//	       Indicates the beginning of a server descriptor.  "nickname" must be a
//	       valid router nickname as specified in section 2.1.3.  "address" must
//	       be an IPv4
//	       address in dotted-quad format.  The last three numbers indicate the
//	       TCP ports at which this OR exposes functionality. ORPort is a port at
//	       which this OR accepts TLS connections for the main OR protocol;
//	       SOCKSPort is deprecated and should always be 0; and DirPort is the
//	       port at which this OR accepts directory-related HTTP connections.  If
//	       any port is not supported, the value 0 is given instead of a port
//	       number.  (At least one of DirPort and ORPort SHOULD be set;
//	       authorities MAY reject any descriptor with both DirPort and ORPort of
//	       0.)
//
func (d *ServerDescriptor) SetRouter(nickname string, addr net.IP, orPort, dirPort uint16) error {
	if !nicknameRx.MatchString(nickname) {
		return ErrServerDescriptorBadNickname
	}

	addr = addr.To4()
	if addr == nil {
		return ErrServerDescriptorNotIPv4
	}

	args := []string{
		nickname,
		addr.String(),
		strconv.FormatUint(uint64(orPort), 10),
		"0", // SOCKSPort
		strconv.FormatUint(uint64(dirPort), 10),
	}
	d.router = NewItem(routerKeyword, args)
	d.keywords[routerKeyword] = true
	return nil
}

// SetBandwidth sets the bandwidth of the server.
//
// Reference: https://github.com/torproject/torspec/blob/master/dir-spec.txt#L419-L430
//
//	    "bandwidth" bandwidth-avg bandwidth-burst bandwidth-observed NL
//
//	       [Exactly once]
//
//	       Estimated bandwidth for this router, in bytes per second.  The
//	       "average" bandwidth is the volume per second that the OR is willing to
//	       sustain over long periods; the "burst" bandwidth is the volume that
//	       the OR is willing to sustain in very short intervals.  The "observed"
//	       value is an estimate of the capacity this relay can handle.  The
//	       relay remembers the max bandwidth sustained output over any ten
//	       second period in the past day, and another sustained input.  The
//	       "observed" value is the lesser of these two numbers.
//
func (d *ServerDescriptor) SetBandwidth(avg, burst, observed int) error {
	args := []string{
		strconv.Itoa(avg),
		strconv.Itoa(burst),
		strconv.Itoa(observed),
	}
	d.addItem(NewItem(bandwidthKeyword, args))
	return nil
}

// SetPublishedTime sets the time the descriptor was published.
//
// Reference: https://github.com/torproject/torspec/blob/master/dir-spec.txt#L440-L445
//
//	    "published" YYYY-MM-DD HH:MM:SS NL
//
//	       [Exactly once]
//
//	       The time, in UTC, when this descriptor (and its corresponding
//	       extra-info document if any)  was generated.
//
func (d *ServerDescriptor) SetPublishedTime(t time.Time) error {
	args := []string{
		t.In(time.UTC).Format("2006-01-02 15:04:05"),
	}
	d.addItem(NewItem(publishedKeyword, args))
	return nil
}

// Validate checks whether the descriptor is valid.
func (d *ServerDescriptor) Validate() error {
	for _, keyword := range requiredKeywords {
		_, ok := d.keywords[keyword]
		if !ok {
			return ServerDescriptorMissingFieldError(keyword)
		}
	}
	return nil
}

// Document generates the Document for this descriptor.
func (d *ServerDescriptor) Document() (*Document, error) {
	err := d.Validate()
	if err != nil {
		return nil, err
	}

	doc := &Document{}
	doc.AddItem(d.router)
	for _, item := range d.items {
		doc.AddItem(item)
	}
	return doc, nil
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	https://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 bannedapi

import (
	"errors"
	"flag"
	"fmt"
	"go/token"
	"go/types"
	"path/filepath"
	"strings"

	"github.com/google/go-safeweb/cmd/bancheck/config"
	"golang.org/x/tools/go/analysis"
)

// NewAnalyzer returns an analyzer that checks for usage of banned APIs.
func NewAnalyzer() *analysis.Analyzer {
	fs := flag.NewFlagSet("", flag.ExitOnError)
	fs.String("configs", "", "Config files with banned APIs separated by a comma")

	a := &analysis.Analyzer{
		Name:  "bannedAPI",
		Doc:   "Checks for usage of banned APIs",
		Run:   checkBannedAPIs,
		Flags: *fs,
	}

	return a
}

func checkBannedAPIs(pass *analysis.Pass) (interface{}, error) {
	cfgFiles := pass.Analyzer.Flags.Lookup("configs").Value.String()
	if cfgFiles == "" {
		return nil, errors.New("missing config files")
	}

	cfg, err := config.ReadConfigs(strings.Split(cfgFiles, ","))
	if err != nil {
		return nil, err
	}

	checkBannedImports(pass, bannedAPIMap(cfg.Imports))
	checkBannedFunctions(pass, bannedAPIMap(cfg.Functions))

	return nil, nil
}

func checkBannedImports(pass *analysis.Pass, bannedImports map[string][]config.BannedAPI) (interface{}, error) {
	for _, f := range pass.Files {
		for _, i := range f.Imports {
			importName := strings.Trim(i.Path.Value, "\"")
			err := reportIfBanned(importName, bannedImports, i.Pos(), pass)
			if err != nil {
				return false, err
			}
		}
	}
	return nil, nil
}

func checkBannedFunctions(pass *analysis.Pass, bannedFns map[string][]config.BannedAPI) (interface{}, error) {
	for id, obj := range pass.TypesInfo.Uses {
		fn, ok := obj.(*types.Func)
		if !ok {
			continue
		}

		fnName := fmt.Sprintf("%s.%s", fn.Pkg().Path(), fn.Name())
		err := reportIfBanned(fnName, bannedFns, id.Pos(), pass)
		if err != nil {
			return false, err
		}
	}
	return nil, nil
}

func reportIfBanned(apiName string, bannedAPIs map[string][]config.BannedAPI, position token.Pos, pass *analysis.Pass) error {
	bannedAPICfgs, isBanned := bannedAPIs[apiName]
	if !isBanned {
		return nil
	}
	pkgAllowed, err := isPkgAllowed(pass.Pkg, bannedAPICfgs)
	if err != nil {
		return err
	}
	if pkgAllowed {
		return nil
	}
	for _, bannedAPICfg := range bannedAPICfgs {
		pass.Report(analysis.Diagnostic{
			Pos:     position,
			Message: fmt.Sprintf("Banned API found %q. Additional info: %s", apiName, bannedAPICfg.Msg),
		})
	}
	return nil
}

// isPkgAllowed checks if the Go package should be exempted from reporting banned API usages.
func isPkgAllowed(pkg *types.Package, bannedAPI []config.BannedAPI) (bool, error) {
	for _, fn := range bannedAPI {
		for _, e := range fn.Exemptions {
			match, err := filepath.Match(e.AllowedPkg, pkg.Path())
			if err != nil {
				return false, err
			}
			if match {
				return true, nil
			}
		}
	}
	return false, nil
}

// bannedAPIMap builds a mapping of fully qualified API name to a list of
// all its config.BannedAPI entries.
func bannedAPIMap(bannedAPIs []config.BannedAPI) map[string][]config.BannedAPI {
	m := make(map[string][]config.BannedAPI)
	for _, API := range bannedAPIs {
		m[API.Name] = append(m[API.Name], API)
	}
	return m
}
<commit_msg>Add godoc comment to the bannedapi package.<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	https://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 bannedapi provides the tools for doing static analysis
// and checking for usage of banned APIs.
package bannedapi

import (
	"errors"
	"flag"
	"fmt"
	"go/token"
	"go/types"
	"path/filepath"
	"strings"

	"github.com/google/go-safeweb/cmd/bancheck/config"
	"golang.org/x/tools/go/analysis"
)

// NewAnalyzer returns an analyzer that checks for usage of banned APIs.
func NewAnalyzer() *analysis.Analyzer {
	fs := flag.NewFlagSet("", flag.ExitOnError)
	fs.String("configs", "", "Config files with banned APIs separated by a comma")

	a := &analysis.Analyzer{
		Name:  "bannedAPI",
		Doc:   "Checks for usage of banned APIs",
		Run:   checkBannedAPIs,
		Flags: *fs,
	}

	return a
}

func checkBannedAPIs(pass *analysis.Pass) (interface{}, error) {
	cfgFiles := pass.Analyzer.Flags.Lookup("configs").Value.String()
	if cfgFiles == "" {
		return nil, errors.New("missing config files")
	}

	cfg, err := config.ReadConfigs(strings.Split(cfgFiles, ","))
	if err != nil {
		return nil, err
	}

	checkBannedImports(pass, bannedAPIMap(cfg.Imports))
	checkBannedFunctions(pass, bannedAPIMap(cfg.Functions))

	return nil, nil
}

func checkBannedImports(pass *analysis.Pass, bannedImports map[string][]config.BannedAPI) (interface{}, error) {
	for _, f := range pass.Files {
		for _, i := range f.Imports {
			importName := strings.Trim(i.Path.Value, "\"")
			err := reportIfBanned(importName, bannedImports, i.Pos(), pass)
			if err != nil {
				return false, err
			}
		}
	}
	return nil, nil
}

func checkBannedFunctions(pass *analysis.Pass, bannedFns map[string][]config.BannedAPI) (interface{}, error) {
	for id, obj := range pass.TypesInfo.Uses {
		fn, ok := obj.(*types.Func)
		if !ok {
			continue
		}

		fnName := fmt.Sprintf("%s.%s", fn.Pkg().Path(), fn.Name())
		err := reportIfBanned(fnName, bannedFns, id.Pos(), pass)
		if err != nil {
			return false, err
		}
	}
	return nil, nil
}

func reportIfBanned(apiName string, bannedAPIs map[string][]config.BannedAPI, position token.Pos, pass *analysis.Pass) error {
	bannedAPICfgs, isBanned := bannedAPIs[apiName]
	if !isBanned {
		return nil
	}
	pkgAllowed, err := isPkgAllowed(pass.Pkg, bannedAPICfgs)
	if err != nil {
		return err
	}
	if pkgAllowed {
		return nil
	}
	for _, bannedAPICfg := range bannedAPICfgs {
		pass.Report(analysis.Diagnostic{
			Pos:     position,
			Message: fmt.Sprintf("Banned API found %q. Additional info: %s", apiName, bannedAPICfg.Msg),
		})
	}
	return nil
}

// isPkgAllowed checks if the Go package should be exempted from reporting banned API usages.
func isPkgAllowed(pkg *types.Package, bannedAPI []config.BannedAPI) (bool, error) {
	for _, fn := range bannedAPI {
		for _, e := range fn.Exemptions {
			match, err := filepath.Match(e.AllowedPkg, pkg.Path())
			if err != nil {
				return false, err
			}
			if match {
				return true, nil
			}
		}
	}
	return false, nil
}

// bannedAPIMap builds a mapping of fully qualified API name to a list of
// all its config.BannedAPI entries.
func bannedAPIMap(bannedAPIs []config.BannedAPI) map[string][]config.BannedAPI {
	m := make(map[string][]config.BannedAPI)
	for _, API := range bannedAPIs {
		m[API.Name] = append(m[API.Name], API)
	}
	return m
}
<|endoftext|>


<commit_before>package collectors

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"bosun.org/metadata"
	"bosun.org/opentsdb"
	"bosun.org/slog"
	"bosun.org/util"
)

type ProgramCollector struct {
	Path     string
	Interval time.Duration

	TagOverride
}

func InitPrograms(cpath string) {
	cdir, err := os.Open(cpath)
	if err != nil {
		slog.Infoln(err)
		return
	}
	idirs, err := cdir.Readdir(0)
	if err != nil {
		slog.Infoln(err)
		return
	}
	for _, idir := range idirs {
		idirname := idir.Name()
		i, err := strconv.Atoi(idirname)
		if err != nil || i < 0 {
			if idirname != "etc" && idirname != "lib" {
				slog.Infoln("invalid collector folder name:", idirname)
			}
			continue
		}
		interval := time.Second * time.Duration(i)
		dir, err := os.Open(filepath.Join(cdir.Name(), idirname))
		if err != nil {
			slog.Infoln(err)
			continue
		}
		files, err := dir.Readdir(0)
		if err != nil {
			slog.Infoln(err)
			continue
		}
		for _, file := range files {
			if !isExecutable(file) {
				continue
			}
			collectors = append(collectors, &ProgramCollector{
				Path:     filepath.Join(dir.Name(), file.Name()),
				Interval: interval,
			})
		}
	}
}

func isExecutable(f os.FileInfo) bool {
	switch runtime.GOOS {
	case "windows":
		exts := strings.Split(os.Getenv("PATHEXT"), ";")
		fileExt := filepath.Ext(strings.ToUpper(f.Name()))
		for _, ext := range exts {
			if filepath.Ext(strings.ToUpper(ext)) == fileExt {
				return true
			}
		}
		return false
	default:
		return f.Mode()&0111 != 0
	}
}

func (c *ProgramCollector) Run(dpchan chan<- *opentsdb.DataPoint, quit <-chan struct{}) {
	if c.Interval == 0 {
		for {
			next := time.After(DefaultFreq)
			if err := c.runProgram(dpchan); err != nil {
				slog.Infoln(err)
			}
			<-next
			slog.Infoln("restarting", c.Path)
		}
	} else {
		for {
			next := time.After(c.Interval)
			c.runProgram(dpchan)
			select {
			case <-next:
			case <-quit:
				return
			}

		}
	}
}

func (c *ProgramCollector) Init() {
}

var setupExternalCommand = func(cmd *exec.Cmd) {}

func (c *ProgramCollector) runProgram(dpchan chan<- *opentsdb.DataPoint) (progError error) {
	cmd := exec.Command(c.Path)
	setupExternalCommand(cmd)
	pr, pw := io.Pipe()
	s := bufio.NewScanner(pr)
	cmd.Stdout = pw
	er, ew := io.Pipe()
	cmd.Stderr = ew
	if err := cmd.Start(); err != nil {
		return err
	}
	go func() {
		progError = cmd.Wait()
		pw.Close()
		ew.Close()
	}()
	go func() {
		es := bufio.NewScanner(er)
		for es.Scan() {
			line := strings.TrimSpace(es.Text())
			slog.Error(line)
		}
	}()
	for s.Scan() {
		var errs []error
		t := strings.TrimSpace(s.Text())
		if len(t) == 0 {
			continue
		}
		if dp, err := parseTcollectorValue(t); err == nil {
			dpchan <- dp
			continue
		} else {
			errs = append(errs, fmt.Errorf("tcollector: %v", err))
		}
		var dp opentsdb.DataPoint
		if err := json.Unmarshal([]byte(t), &dp); err != nil {
			errs = append(errs, fmt.Errorf("opentsdb.DataPoint: %v", err))
		} else if dp.Valid() {
			if dp.Tags == nil {
				dp.Tags = opentsdb.TagSet{}
			}
			setExternalTags(dp.Tags)
			c.ApplyTagOverrides(dp.Tags)
			dpchan <- &dp
			continue
		} else {
			errs = append(errs, fmt.Errorf("opentsdb.DataPoint: invalid data"))
		}
		var m metadata.Metasend
		if err := json.Unmarshal([]byte(t), &m); err != nil {
			errs = append(errs, fmt.Errorf("metadata.Metasend: %v", err))
		} else {
			if m.Tags == nil {
				m.Tags = opentsdb.TagSet{}
			}
			setExternalTags(m.Tags)
			if m.Value == "" || m.Name == "" || (m.Metric == "" && len(m.Tags) == 0) {
				errs = append(errs, fmt.Errorf("metadata.Metasend: invalid data"))
			} else {
				metadata.AddMeta(m.Metric, m.Tags, m.Name, m.Value, false)
				continue
			}
		}
		slog.Errorf("%s: unparseable line: %s", c.Path, t)
		for _, e := range errs {
			slog.Error(e)
		}
	}
	if err := s.Err(); err != nil {
		return err
	}
	return
}

// setExternalTags adds and deletes system-level tags to tags. The host
// tag is set to the hostname if unspecified, or removed if present and
// empty. Command line tags (in AddTags) are then added.
func setExternalTags(tags opentsdb.TagSet) {
	if v, ok := tags["host"]; ok && v == "" {
		delete(tags, "host")
	} else if v == "" {
		tags["host"] = util.Hostname
	}
	for k, v := range AddTags {
		if _, ok := tags[k]; !ok {
			tags[k] = v
		}
	}
}

// parseTcollectorValue parses a tcollector-style line into a data point.
func parseTcollectorValue(line string) (*opentsdb.DataPoint, error) {
	sp := strings.Fields(line)
	if len(sp) < 3 {
		return nil, fmt.Errorf("bad line: %s", line)
	}
	ts, err := strconv.ParseInt(sp[1], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("bad timestamp: %s", sp[1])
	}
	val, err := strconv.ParseFloat(sp[2], 64)
	if err != nil {
		return nil, fmt.Errorf("bad value: %s", sp[2])
	}
	if !opentsdb.ValidTSDBString(sp[0]) {
		return nil, fmt.Errorf("bad metric: %s", sp[0])
	}
	dp := opentsdb.DataPoint{
		Metric:    sp[0],
		Timestamp: ts,
		Value:     val,
	}
	tags := opentsdb.TagSet{}
	for _, tag := range sp[3:] {
		ts, err := opentsdb.ParseTags(tag)
		if err != nil {
			return nil, fmt.Errorf("bad tag, metric %s: %v: %v", sp[0], tag, err)
		}
		tags.Merge(ts)
	}
	setExternalTags(tags)
	dp.Tags = tags
	return &dp, nil
}

func (c *ProgramCollector) Name() string {
	return c.Path
}
<commit_msg>cmd/scollector: Adding .PS1 to supported program extensions and changing command execution<commit_after>package collectors

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"bosun.org/metadata"
	"bosun.org/opentsdb"
	"bosun.org/slog"
	"bosun.org/util"
)

type ProgramCollector struct {
	Path     string
	Interval time.Duration

	TagOverride
}

func InitPrograms(cpath string) {
	cdir, err := os.Open(cpath)
	if err != nil {
		slog.Infoln(err)
		return
	}
	idirs, err := cdir.Readdir(0)
	if err != nil {
		slog.Infoln(err)
		return
	}
	for _, idir := range idirs {
		idirname := idir.Name()
		i, err := strconv.Atoi(idirname)
		if err != nil || i < 0 {
			if idirname != "etc" && idirname != "lib" {
				slog.Infoln("invalid collector folder name:", idirname)
			}
			continue
		}
		interval := time.Second * time.Duration(i)
		dir, err := os.Open(filepath.Join(cdir.Name(), idirname))
		if err != nil {
			slog.Infoln(err)
			continue
		}
		files, err := dir.Readdir(0)
		if err != nil {
			slog.Infoln(err)
			continue
		}
		for _, file := range files {
			if !isExecutable(file) {
				continue
			}
			collectors = append(collectors, &ProgramCollector{
				Path:     filepath.Join(dir.Name(), file.Name()),
				Interval: interval,
			})
		}
	}
}

func isExecutable(f os.FileInfo) bool {
	switch runtime.GOOS {
	case "windows":
		exts := strings.Split(os.Getenv("PATHEXT"), ";")
		exts = append(exts, ".PS1")
		fileExt := filepath.Ext(strings.ToUpper(f.Name()))
		for _, ext := range exts {
			if filepath.Ext(strings.ToUpper(ext)) == fileExt {
				return true
			}
		}
		return false
	default:
		return f.Mode()&0111 != 0
	}
}

func (c *ProgramCollector) Run(dpchan chan<- *opentsdb.DataPoint, quit <-chan struct{}) {
	if c.Interval == 0 {
		for {
			next := time.After(DefaultFreq)
			if err := c.runProgram(dpchan); err != nil {
				slog.Infoln(err)
			}
			<-next
			slog.Infoln("restarting", c.Path)
		}
	} else {
		for {
			next := time.After(c.Interval)
			c.runProgram(dpchan)
			select {
			case <-next:
			case <-quit:
				return
			}

		}
	}
}

func (c *ProgramCollector) Init() {
}

var setupExternalCommand = func(cmd *exec.Cmd) {}

func (c *ProgramCollector) runProgram(dpchan chan<- *opentsdb.DataPoint) (progError error) {
	var cmd *exec.Cmd
	if runtime.GOOS == "windows" && strings.EqualFold(filepath.Ext(c.Path), ".ps1") {
		cmd = exec.Command("powershell", "-NoProfile", "-NoLogo", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", c.Path)
	} else {
		cmd = exec.Command(c.Path)
	}
	setupExternalCommand(cmd)
	pr, pw := io.Pipe()
	s := bufio.NewScanner(pr)
	cmd.Stdout = pw
	er, ew := io.Pipe()
	cmd.Stderr = ew
	if err := cmd.Start(); err != nil {
		return err
	}
	go func() {
		progError = cmd.Wait()
		pw.Close()
		ew.Close()
	}()
	go func() {
		es := bufio.NewScanner(er)
		for es.Scan() {
			line := strings.TrimSpace(es.Text())
			slog.Error(line)
		}
	}()
	for s.Scan() {
		var errs []error
		t := strings.TrimSpace(s.Text())
		if len(t) == 0 {
			continue
		}
		if dp, err := parseTcollectorValue(t); err == nil {
			dpchan <- dp
			continue
		} else {
			errs = append(errs, fmt.Errorf("tcollector: %v", err))
		}
		var dp opentsdb.DataPoint
		if err := json.Unmarshal([]byte(t), &dp); err != nil {
			errs = append(errs, fmt.Errorf("opentsdb.DataPoint: %v", err))
		} else if dp.Valid() {
			if dp.Tags == nil {
				dp.Tags = opentsdb.TagSet{}
			}
			setExternalTags(dp.Tags)
			c.ApplyTagOverrides(dp.Tags)
			dpchan <- &dp
			continue
		} else {
			errs = append(errs, fmt.Errorf("opentsdb.DataPoint: invalid data"))
		}
		var m metadata.Metasend
		if err := json.Unmarshal([]byte(t), &m); err != nil {
			errs = append(errs, fmt.Errorf("metadata.Metasend: %v", err))
		} else {
			if m.Tags == nil {
				m.Tags = opentsdb.TagSet{}
			}
			setExternalTags(m.Tags)
			if m.Value == "" || m.Name == "" || (m.Metric == "" && len(m.Tags) == 0) {
				errs = append(errs, fmt.Errorf("metadata.Metasend: invalid data"))
			} else {
				metadata.AddMeta(m.Metric, m.Tags, m.Name, m.Value, false)
				continue
			}
		}
		slog.Errorf("%s: unparseable line: %s", c.Path, t)
		for _, e := range errs {
			slog.Error(e)
		}
	}
	if err := s.Err(); err != nil {
		return err
	}
	return
}

// setExternalTags adds and deletes system-level tags to tags. The host
// tag is set to the hostname if unspecified, or removed if present and
// empty. Command line tags (in AddTags) are then added.
func setExternalTags(tags opentsdb.TagSet) {
	if v, ok := tags["host"]; ok && v == "" {
		delete(tags, "host")
	} else if v == "" {
		tags["host"] = util.Hostname
	}
	for k, v := range AddTags {
		if _, ok := tags[k]; !ok {
			tags[k] = v
		}
	}
}

// parseTcollectorValue parses a tcollector-style line into a data point.
func parseTcollectorValue(line string) (*opentsdb.DataPoint, error) {
	sp := strings.Fields(line)
	if len(sp) < 3 {
		return nil, fmt.Errorf("bad line: %s", line)
	}
	ts, err := strconv.ParseInt(sp[1], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("bad timestamp: %s", sp[1])
	}
	val, err := strconv.ParseFloat(sp[2], 64)
	if err != nil {
		return nil, fmt.Errorf("bad value: %s", sp[2])
	}
	if !opentsdb.ValidTSDBString(sp[0]) {
		return nil, fmt.Errorf("bad metric: %s", sp[0])
	}
	dp := opentsdb.DataPoint{
		Metric:    sp[0],
		Timestamp: ts,
		Value:     val,
	}
	tags := opentsdb.TagSet{}
	for _, tag := range sp[3:] {
		ts, err := opentsdb.ParseTags(tag)
		if err != nil {
			return nil, fmt.Errorf("bad tag, metric %s: %v: %v", sp[0], tag, err)
		}
		tags.Merge(ts)
	}
	setExternalTags(tags)
	dp.Tags = tags
	return &dp, nil
}

func (c *ProgramCollector) Name() string {
	return c.Path
}
<|endoftext|>


<commit_before>package gaurun

import (
	"fmt"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/RobotsAndPencils/buford/push"
)

var (
	// PusherCountAll is the shared value between workers
	PusherCountAll int64

	// PusherWg is global wait group for pusher worker.
	// It increments when new pusher is swapned and decrements when job is done.
	//
	// This is used to block main process to shutdown while pusher is still working.
	PusherWg sync.WaitGroup
)

func init() {
	PusherCountAll = 0
}

func StartPushWorkers(workerNum, queueNum int64) {
	QueueNotification = make(chan RequestGaurunNotification, queueNum)
	for i := int64(0); i < workerNum; i++ {
		go pushNotificationWorker()
	}
}

func isExternalServerError(err error, platform int) bool {
	switch platform {
	case PlatFormIos:
		if err == push.ErrIdleTimeout || err == push.ErrShutdown || err == push.ErrInternalServerError || err == push.ErrServiceUnavailable {
			return true
		}
	case PlatFormAndroid:
		if err.Error() == "Unavailable" || err.Error() == "InternalServerError" || strings.Contains(err.Error(), "Timeout") {
			return true
		}
	default:
		// not through
	}
	return false
}

func pushSync(pusher func(req RequestGaurunNotification) error, req RequestGaurunNotification, retryMax int) {
	PusherWg.Add(1)
	defer PusherWg.Done()
Retry:
	err := pusher(req)
	if err != nil && req.Retry < retryMax && isExternalServerError(err, req.Platform) {
		req.Retry++
		goto Retry
	}
}

func pushAsync(pusher func(req RequestGaurunNotification) error, req RequestGaurunNotification, retryMax int, pusherCount *int64) {
Retry:
	PusherWg.Add(1)
	defer PusherWg.Done()

	err := pusher(req)
	if err != nil && req.Retry < retryMax && isExternalServerError(err, req.Platform) {
		req.Retry++
		goto Retry
	}

	atomic.AddInt64(pusherCount, -1)
	atomic.AddInt64(&PusherCountAll, -1)
}

func pushNotificationWorker() {
	var (
		retryMax    int
		pusher      func(req RequestGaurunNotification) error
		pusherCount int64
	)

	// pusherCount is the independent value between workers
	pusherCount = 0

	for {
		notification := <-QueueNotification

		switch notification.Platform {
		case PlatFormIos:
			pusher = pushNotificationIos
			retryMax = ConfGaurun.Ios.RetryMax
		case PlatFormAndroid:
			pusher = pushNotificationAndroid
			retryMax = ConfGaurun.Android.RetryMax
		default:
			LogError.Warn(fmt.Sprintf("invalid platform: %d", notification.Platform))
			continue
		}

		if atomic.LoadInt64(&ConfGaurun.Core.PusherMax) <= 0 {
			pushSync(pusher, notification, retryMax)
			continue
		}

		if atomic.LoadInt64(&pusherCount) < atomic.LoadInt64(&ConfGaurun.Core.PusherMax) {
			// Do not increment pusherCount and PusherCountAll in pushAsync().
			// Because pusherCount and PusherCountAll are sometimes over pusherMax
			// as the increment in goroutine runs asynchronously.
			atomic.AddInt64(&pusherCount, 1)
			atomic.AddInt64(&PusherCountAll, 1)

			go pushAsync(pusher, notification, retryMax, &pusherCount)
			continue
		} else {
			pushSync(pusher, notification, retryMax)
			continue
		}
	}
}
<commit_msg>Fix where to increment<commit_after>package gaurun

import (
	"fmt"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/RobotsAndPencils/buford/push"
)

var (
	// PusherCountAll is the shared value between workers
	PusherCountAll int64

	// PusherWg is global wait group for pusher worker.
	// It increments when new pusher is swapned and decrements when job is done.
	//
	// This is used to block main process to shutdown while pusher is still working.
	PusherWg sync.WaitGroup
)

func init() {
	PusherCountAll = 0
}

func StartPushWorkers(workerNum, queueNum int64) {
	QueueNotification = make(chan RequestGaurunNotification, queueNum)
	for i := int64(0); i < workerNum; i++ {
		go pushNotificationWorker()
	}
}

func isExternalServerError(err error, platform int) bool {
	switch platform {
	case PlatFormIos:
		if err == push.ErrIdleTimeout || err == push.ErrShutdown || err == push.ErrInternalServerError || err == push.ErrServiceUnavailable {
			return true
		}
	case PlatFormAndroid:
		if err.Error() == "Unavailable" || err.Error() == "InternalServerError" || strings.Contains(err.Error(), "Timeout") {
			return true
		}
	default:
		// not through
	}
	return false
}

func pushSync(pusher func(req RequestGaurunNotification) error, req RequestGaurunNotification, retryMax int) {
	PusherWg.Add(1)
	defer PusherWg.Done()
Retry:
	err := pusher(req)
	if err != nil && req.Retry < retryMax && isExternalServerError(err, req.Platform) {
		req.Retry++
		goto Retry
	}
}

func pushAsync(pusher func(req RequestGaurunNotification) error, req RequestGaurunNotification, retryMax int, pusherCount *int64) {
	defer PusherWg.Done()
Retry:
	err := pusher(req)
	if err != nil && req.Retry < retryMax && isExternalServerError(err, req.Platform) {
		req.Retry++
		goto Retry
	}

	atomic.AddInt64(pusherCount, -1)
	atomic.AddInt64(&PusherCountAll, -1)
}

func pushNotificationWorker() {
	var (
		retryMax    int
		pusher      func(req RequestGaurunNotification) error
		pusherCount int64
	)

	// pusherCount is the independent value between workers
	pusherCount = 0

	for {
		notification := <-QueueNotification

		switch notification.Platform {
		case PlatFormIos:
			pusher = pushNotificationIos
			retryMax = ConfGaurun.Ios.RetryMax
		case PlatFormAndroid:
			pusher = pushNotificationAndroid
			retryMax = ConfGaurun.Android.RetryMax
		default:
			LogError.Warn(fmt.Sprintf("invalid platform: %d", notification.Platform))
			continue
		}

		if atomic.LoadInt64(&ConfGaurun.Core.PusherMax) <= 0 {
			pushSync(pusher, notification, retryMax)
			continue
		}

		if atomic.LoadInt64(&pusherCount) < atomic.LoadInt64(&ConfGaurun.Core.PusherMax) {
			// Do not increment pusherCount and PusherCountAll in pushAsync().
			// Because pusherCount and PusherCountAll are sometimes over pusherMax
			// as the increment in goroutine runs asynchronously.
			atomic.AddInt64(&pusherCount, 1)
			atomic.AddInt64(&PusherCountAll, 1)
			PusherWg.Add(1)
			go pushAsync(pusher, notification, retryMax, &pusherCount)
			continue
		} else {
			pushSync(pusher, notification, retryMax)
			continue
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package generic

import "reflect"

// MultiIndex returns a slice w such that w[i] = v[indexes[i]].
func MultiIndex(v Slice, indexes []int) Slice {
	switch v := v.(type) {
	case []int:
		res := make([]int, len(indexes))
		for i, x := range indexes {
			res[i] = v[x]
		}
		return res

	case []float64:
		res := make([]float64, len(indexes))
		for i, x := range indexes {
			res[i] = v[x]
		}
		return res

	case []string:
		res := make([]string, len(indexes))
		for i, x := range indexes {
			res[i] = v[x]
		}
		return res
	}

	rv := reflectSlice(v)
	res := reflect.MakeSlice(rv.Type(), len(indexes), len(indexes))
	for i, x := range indexes {
		res.Index(i).Set(rv.Index(x))
	}
	return res.Interface()
}
<commit_msg>generic: CopyShuffle operation<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package generic

import "reflect"

// MultiIndex returns a slice w such that w[i] = v[indexes[i]].
func MultiIndex(v Slice, indexes []int) Slice {
	switch v := v.(type) {
	case []int:
		res := make([]int, len(indexes))
		for i, x := range indexes {
			res[i] = v[x]
		}
		return res

	case []float64:
		res := make([]float64, len(indexes))
		for i, x := range indexes {
			res[i] = v[x]
		}
		return res

	case []string:
		res := make([]string, len(indexes))
		for i, x := range indexes {
			res[i] = v[x]
		}
		return res
	}

	rv := reflectSlice(v)
	res := reflect.MakeSlice(rv.Type(), len(indexes), len(indexes))
	for i, x := range indexes {
		res.Index(i).Set(rv.Index(x))
	}
	return res.Interface()
}

// CopyIndex assigns out[i] = in[indexes[i]]. in and out must have the
// same types and len(out) must be >= len(indexes). If in and out
// overlap, the results are undefined.
func CopyIndex(out, in Slice, indexes []int) {
	// TODO: Maybe they should only have to be assignable?
	if it, ot := reflect.TypeOf(in), reflect.TypeOf(out); it != ot {
		panic(&TypeError{it, ot, "must be the same type"})
	}

	switch in := in.(type) {
	case []int:
		out := out.([]int)
		for i, x := range indexes {
			out[i] = in[x]
		}
		return

	case []float64:
		out := out.([]float64)
		for i, x := range indexes {
			out[i] = in[x]
		}
		return

	case []string:
		out := out.([]string)
		for i, x := range indexes {
			out[i] = in[x]
		}
		return
	}

	inv, outv := reflectSlice(in), reflectSlice(out)
	for i, x := range indexes {
		outv.Index(i).Set(inv.Index(x))
	}
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package backend

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/handler/pprof"
	"github.com/webx-top/echo/middleware"
	"github.com/webx-top/echo/middleware/language"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/echo/middleware/session"
	"github.com/webx-top/echo/subdomains"

	"github.com/admpub/events"
	"github.com/admpub/events/emitter"
	"github.com/admpub/log"
	"github.com/admpub/nging/application/cmd/event"
	"github.com/admpub/nging/application/handler"
	"github.com/admpub/nging/application/library/config"
	ngingMW "github.com/admpub/nging/application/middleware"
)

const (
	DefaultTemplateDir   = `./template/backend`
	DefaultAssetsDir     = `./public/assets`
	DefaultAssetsURLPath = `/public/assets/backend`
)

var (
	TemplateDir      = DefaultTemplateDir //模板文件夹
	AssetsDir        = DefaultAssetsDir   //素材文件夹
	AssetsURLPath    = DefaultAssetsURLPath
	DefaultAvatarURL = AssetsURLPath + `/images/user_128.png`
	RendererDo       = func(driver.Driver) {}
	ParseStrings     = map[string]string{}
	ParseStringFuncs = map[string]func() string{}
	SkippedGzipPaths = map[string]bool{}
	GzipSkipper      = func(skippedPaths map[string]bool) func(c echo.Context) bool {
		return func(c echo.Context) bool {
			upath := c.Request().URL().Path()
			skipped, _ := skippedPaths[upath]
			return skipped
		}
	}
	DefaultLocalHostNames = []string{
		`127.0.0.1`, `localhost`,
	}
)

func MakeSubdomains(domain string, appends []string) string {
	var prefix string
	if pos := strings.Index(domain, `://`); pos > 0 {
		pos += 3
		prefix = domain[:pos]
		if pos+1 <= len(domain) {
			domain = domain[pos+1:]
		} else {
			domain = ``
		}
	}
	domain, _ = com.SplitHost(domain)
	port := fmt.Sprintf("%d", config.DefaultCLIConfig.Port)
	newDomain := prefix + domain + `,` + domain + `:` + port
	for _, hostName := range appends {
		if hostName == domain {
			continue
		}
		newDomain += `,` + hostName + `:` + port
	}
	return newDomain
}

func init() {
	echo.Set(`BackendPrefix`, handler.BackendPrefix)
	echo.Set(`GlobalPrefix`, handler.GlobalPrefix)
	event.OnStart(0, func() {
		handler.GlobalPrefix = echo.String(`GlobalPrefix`)
		handler.BackendPrefix = echo.String(`BackendPrefix`)
		handler.FrontendPrefix = echo.String(`FrontendPrefix`)
		ngingMW.DefaultAvatarURL = DefaultAssetsURLPath
		e := handler.Echo()
		e.SetPrefix(handler.GlobalPrefix)
		handler.SetRootGroup(handler.BackendPrefix)
		subdomains.Default.Default = `backend`
		domainName := subdomains.Default.Default
		backendDomain := config.DefaultCLIConfig.BackendDomain
		if len(backendDomain) > 0 {
			domainName += `@` + MakeSubdomains(backendDomain, DefaultLocalHostNames)
		}
		subdomains.Default.Add(domainName, e)

		e.Use(middleware.Log(), middleware.Recover())
		skippedGzipPaths := map[string]bool{
			e.Prefix() + `/server/cmdSend/info`:      true,
			e.Prefix() + `/download/progress/info`:   true,
			e.Prefix() + `/debug/pprof/`:             true,
			e.Prefix() + `/debug/pprof/allocs`:       true,
			e.Prefix() + `/debug/pprof/block`:        true,
			e.Prefix() + `/debug/pprof/cmdline`:      true,
			e.Prefix() + `/debug/pprof/goroutine`:    true,
			e.Prefix() + `/debug/pprof/heap`:         true,
			e.Prefix() + `/debug/pprof/mutex`:        true,
			e.Prefix() + `/debug/pprof/profile`:      true,
			e.Prefix() + `/debug/pprof/threadcreate`: true,
			e.Prefix() + `/debug/pprof/trace`:        true,
		}
		for k, v := range skippedGzipPaths {
			SkippedGzipPaths[k] = v
		}
		e.Use(middleware.Gzip(&middleware.GzipConfig{
			Skipper: GzipSkipper(SkippedGzipPaths),
		}))
		e.Use(func(h echo.Handler) echo.HandlerFunc {
			return func(c echo.Context) error {
				c.Response().Header().Set(`Server`, event.SofewareName+`/`+config.Version.Number)
				return h.Handle(c)
			}
		})

		// 注册静态资源文件(网站素材文件)
		e.Use(event.StaticMW) //打包的静态资源
		// 上传文件资源(改到manager中用File函数实现)
		// e.Use(middleware.Static(&middleware.StaticOptions{
		// 	Root: helper.UploadDir,
		// 	Path: helper.UploadURLPath,
		// }))

		// 启用session
		e.Use(session.Middleware(config.SessionOptions))
		// 启用多语言支持
		config.DefaultConfig.Language.SetFSFunc(event.LangFSFunc)
		e.Use(language.New(&config.DefaultConfig.Language).Middleware())

		// 启用Validation
		e.Use(middleware.Validate(echo.NewValidation))

		// 事物支持
		e.Use(ngingMW.Tansaction())
		// 注册模板引擎
		renderOptions := &render.Config{
			TmplDir: TemplateDir,
			Engine:  `standard`,
			ParseStrings: map[string]string{
				`__ASSETS__`: AssetsURLPath,
				`__TMPL__`:   TemplateDir,
			},
			ParseStringFuncs: map[string]func() string{
				`__BACKEND__`:  func() string { return subdomains.Default.URL(handler.BackendPrefix, `backend`) },
				`__FRONTEND__`: func() string { return subdomains.Default.URL(handler.FrontendPrefix, `frontend`) },
			},
			DefaultHTTPErrorCode: http.StatusOK,
			Reload:               true,
			ErrorPages:           config.DefaultConfig.Sys.ErrorPages,
		}
		if ParseStrings != nil {
			for key, val := range ParseStrings {
				renderOptions.ParseStrings[key] = val
			}
		}
		if ParseStringFuncs != nil {
			for key, val := range ParseStringFuncs {
				renderOptions.ParseStringFuncs[key] = val
			}
		}
		if RendererDo != nil {
			renderOptions.AddRendererDo(RendererDo)
		}
		renderOptions.AddFuncSetter(ngingMW.ErrorPageFunc)
		renderOptions.ApplyTo(e, event.BackendTmplMgr)
		//RendererDo(renderOptions.Renderer())
		emitter.DefaultCondEmitter.On(`clearCache`, events.Callback(func(_ events.Event) error {
			log.Debug(`clear: Backend Template Object Cache`)
			renderOptions.Renderer().ClearCache()
			return nil
		}))
		e.Get(`/favicon.ico`, event.FaviconHandler)
		if event.Develop {
			pprof.Wrap(e)
		}
		Initialize()
	})
}
<commit_msg>improved<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package backend

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/handler/pprof"
	"github.com/webx-top/echo/middleware"
	"github.com/webx-top/echo/middleware/language"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/echo/middleware/session"
	"github.com/webx-top/echo/subdomains"

	"github.com/admpub/events"
	"github.com/admpub/events/emitter"
	"github.com/admpub/log"
	"github.com/admpub/nging/application/cmd/event"
	"github.com/admpub/nging/application/handler"
	"github.com/admpub/nging/application/library/config"
	ngingMW "github.com/admpub/nging/application/middleware"
)

const (
	DefaultTemplateDir   = `./template/backend`
	DefaultAssetsDir     = `./public/assets`
	DefaultAssetsURLPath = `/public/assets/backend`
)

var (
	TemplateDir      = DefaultTemplateDir //模板文件夹
	AssetsDir        = DefaultAssetsDir   //素材文件夹
	AssetsURLPath    = DefaultAssetsURLPath
	DefaultAvatarURL = AssetsURLPath + `/images/user_128.png`
	RendererDo       = func(driver.Driver) {}
	ParseStrings     = map[string]string{}
	ParseStringFuncs = map[string]func() string{}
	SkippedGzipPaths = map[string]bool{}
	GzipSkipper      = func(skippedPaths map[string]bool) func(c echo.Context) bool {
		return func(c echo.Context) bool {
			upath := c.Request().URL().Path()
			skipped, _ := skippedPaths[upath]
			return skipped
		}
	}
	DefaultLocalHostNames = []string{
		`127.0.0.1`, `localhost`,
	}
)

func MakeSubdomains(domain string, appends []string) string {
	var prefix string
	if pos := strings.Index(domain, `://`); pos > 0 {
		pos += 3
		if pos < len(domain) {
			prefix = domain[:pos]
			domain = domain[pos:]
		} else {
			prefix = domain
			domain = ``
		}
	}
	domain, _ = com.SplitHost(domain)
	port := fmt.Sprintf("%d", config.DefaultCLIConfig.Port)
	newDomain := prefix + domain + `,` + domain + `:` + port
	for _, hostName := range appends {
		if hostName == domain {
			continue
		}
		newDomain += `,` + hostName + `:` + port
	}
	return newDomain
}

func init() {
	echo.Set(`BackendPrefix`, handler.BackendPrefix)
	echo.Set(`GlobalPrefix`, handler.GlobalPrefix)
	event.OnStart(0, func() {
		handler.GlobalPrefix = echo.String(`GlobalPrefix`)
		handler.BackendPrefix = echo.String(`BackendPrefix`)
		handler.FrontendPrefix = echo.String(`FrontendPrefix`)
		ngingMW.DefaultAvatarURL = DefaultAssetsURLPath
		e := handler.Echo()
		e.SetPrefix(handler.GlobalPrefix)
		handler.SetRootGroup(handler.BackendPrefix)
		subdomains.Default.Default = `backend`
		domainName := subdomains.Default.Default
		backendDomain := config.DefaultCLIConfig.BackendDomain
		if len(backendDomain) > 0 {
			domainName += `@` + MakeSubdomains(backendDomain, DefaultLocalHostNames)
		}
		subdomains.Default.Add(domainName, e)

		e.Use(middleware.Log(), middleware.Recover())
		skippedGzipPaths := map[string]bool{
			e.Prefix() + `/server/cmdSend/info`:      true,
			e.Prefix() + `/download/progress/info`:   true,
			e.Prefix() + `/debug/pprof/`:             true,
			e.Prefix() + `/debug/pprof/allocs`:       true,
			e.Prefix() + `/debug/pprof/block`:        true,
			e.Prefix() + `/debug/pprof/cmdline`:      true,
			e.Prefix() + `/debug/pprof/goroutine`:    true,
			e.Prefix() + `/debug/pprof/heap`:         true,
			e.Prefix() + `/debug/pprof/mutex`:        true,
			e.Prefix() + `/debug/pprof/profile`:      true,
			e.Prefix() + `/debug/pprof/threadcreate`: true,
			e.Prefix() + `/debug/pprof/trace`:        true,
		}
		for k, v := range skippedGzipPaths {
			SkippedGzipPaths[k] = v
		}
		e.Use(middleware.Gzip(&middleware.GzipConfig{
			Skipper: GzipSkipper(SkippedGzipPaths),
		}))
		e.Use(func(h echo.Handler) echo.HandlerFunc {
			return func(c echo.Context) error {
				c.Response().Header().Set(`Server`, event.SofewareName+`/`+config.Version.Number)
				return h.Handle(c)
			}
		})

		// 注册静态资源文件(网站素材文件)
		e.Use(event.StaticMW) //打包的静态资源
		// 上传文件资源(改到manager中用File函数实现)
		// e.Use(middleware.Static(&middleware.StaticOptions{
		// 	Root: helper.UploadDir,
		// 	Path: helper.UploadURLPath,
		// }))

		// 启用session
		e.Use(session.Middleware(config.SessionOptions))
		// 启用多语言支持
		config.DefaultConfig.Language.SetFSFunc(event.LangFSFunc)
		e.Use(language.New(&config.DefaultConfig.Language).Middleware())

		// 启用Validation
		e.Use(middleware.Validate(echo.NewValidation))

		// 事物支持
		e.Use(ngingMW.Tansaction())
		// 注册模板引擎
		renderOptions := &render.Config{
			TmplDir: TemplateDir,
			Engine:  `standard`,
			ParseStrings: map[string]string{
				`__ASSETS__`: AssetsURLPath,
				`__TMPL__`:   TemplateDir,
			},
			ParseStringFuncs: map[string]func() string{
				`__BACKEND__`:  func() string { return subdomains.Default.URL(handler.BackendPrefix, `backend`) },
				`__FRONTEND__`: func() string { return subdomains.Default.URL(handler.FrontendPrefix, `frontend`) },
			},
			DefaultHTTPErrorCode: http.StatusOK,
			Reload:               true,
			ErrorPages:           config.DefaultConfig.Sys.ErrorPages,
		}
		if ParseStrings != nil {
			for key, val := range ParseStrings {
				renderOptions.ParseStrings[key] = val
			}
		}
		if ParseStringFuncs != nil {
			for key, val := range ParseStringFuncs {
				renderOptions.ParseStringFuncs[key] = val
			}
		}
		if RendererDo != nil {
			renderOptions.AddRendererDo(RendererDo)
		}
		renderOptions.AddFuncSetter(ngingMW.ErrorPageFunc)
		renderOptions.ApplyTo(e, event.BackendTmplMgr)
		//RendererDo(renderOptions.Renderer())
		emitter.DefaultCondEmitter.On(`clearCache`, events.Callback(func(_ events.Event) error {
			log.Debug(`clear: Backend Template Object Cache`)
			renderOptions.Renderer().ClearCache()
			return nil
		}))
		e.Get(`/favicon.ico`, event.FaviconHandler)
		if event.Develop {
			pprof.Wrap(e)
		}
		Initialize()
	})
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package backend

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/handler/pprof"
	"github.com/webx-top/echo/middleware"
	"github.com/webx-top/echo/middleware/language"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/echo/middleware/session"
	"github.com/webx-top/echo/subdomains"

	"github.com/arl/statsviz"

	"github.com/admpub/log"
	"github.com/admpub/nging/v3/application/cmd/event"
	"github.com/admpub/nging/v3/application/handler"
	"github.com/admpub/nging/v3/application/library/common"
	"github.com/admpub/nging/v3/application/library/config"
	"github.com/admpub/nging/v3/application/library/formbuilder"
	ngingMW "github.com/admpub/nging/v3/application/middleware"
)

const (
	DefaultTemplateDir   = `./template/backend`
	DefaultAssetsDir     = `./public/assets`
	DefaultAssetsURLPath = `/public/assets/backend`
)

var (
	TemplateDir      = DefaultTemplateDir //模板文件夹
	AssetsDir        = DefaultAssetsDir   //素材文件夹
	AssetsURLPath    = DefaultAssetsURLPath
	DefaultAvatarURL = AssetsURLPath + `/images/user_128.png`
	RendererDo       = func(driver.Driver) {}
	ParseStrings     = map[string]string{}
	ParseStringFuncs = map[string]func() string{}
	SkippedGzipPaths = map[string]bool{}
	GzipSkipper      = func(skippedPaths map[string]bool) func(c echo.Context) bool {
		return func(c echo.Context) bool {
			skipped, _ := skippedPaths[c.Request().URL().Path()]
			if !skipped {
				skipped, _ = skippedPaths[c.Path()]
			}
			return skipped
		}
	}
	DefaultLocalHostNames = []string{
		`127.0.0.1`, `localhost`,
	}
)

func MakeSubdomains(domain string, appends []string) string {
	domainList := strings.Split(domain, `,`)
	domain = domainList[0]
	if pos := strings.Index(domain, `://`); pos > 0 {
		pos += 3
		if pos < len(domain) {
			domain = domain[pos:]
		} else {
			domain = ``
		}
	}
	var myPort string
	domain, myPort = com.SplitHost(domain)
	if len(myPort) == 0 && len(domainList) > 1 {
		_, myPort = com.SplitHost(domainList[1])
	}
	port := fmt.Sprintf("%d", config.DefaultCLIConfig.Port)
	newDomainList := []string{}
	if !com.InSlice(domain+`:`+port, domainList) {
		newDomainList = append(newDomainList, domain+`:`+port)
	}
	if myPort == port {
		myPort = ``
	}
	if len(myPort) > 0 {
		if !com.InSlice(domain+`:`+myPort, domainList) {
			newDomainList = append(newDomainList, domain+`:`+myPort)
		}
	}
	for _, hostName := range appends {
		if hostName == domain {
			continue
		}
		if !com.InSlice(hostName+`:`+port, domainList) {
			newDomainList = append(newDomainList, hostName+`:`+port)
		}
		if len(myPort) > 0 {
			if !com.InSlice(hostName+`:`+myPort, domainList) {
				newDomainList = append(newDomainList, hostName+`:`+myPort)
			}
		}
	}
	if len(newDomainList) > 0 {
		domainList = append(domainList, newDomainList...)
	}
	return strings.Join(domainList, `,`)
}

func init() {
	echo.Set(`BackendPrefix`, handler.BackendPrefix)
	echo.Set(`GlobalPrefix`, handler.GlobalPrefix)
	event.OnStart(0, func() {
		handler.GlobalPrefix = echo.String(`GlobalPrefix`)
		handler.BackendPrefix = echo.String(`BackendPrefix`)
		handler.FrontendPrefix = echo.String(`FrontendPrefix`)
		ngingMW.DefaultAvatarURL = DefaultAssetsURLPath
		e := handler.Echo()
		e.SetPrefix(handler.GlobalPrefix)
		handler.SetRootGroup(handler.BackendPrefix)
		subdomains.Default.Default = `backend`
		subdomains.Default.Boot = `backend`
		domainName := subdomains.Default.Default
		backendDomain := config.DefaultCLIConfig.BackendDomain
		if len(backendDomain) > 0 {
			domainName += `@` + MakeSubdomains(backendDomain, DefaultLocalHostNames)
		}
		subdomains.Default.Add(domainName, e)

		e.Use(middleware.Log(), middleware.Recover())
		e.Use(middleware.Gzip(&middleware.GzipConfig{
			Skipper: GzipSkipper(SkippedGzipPaths),
		}))
		e.Use(func(h echo.Handler) echo.HandlerFunc {
			return func(c echo.Context) error {
				c.Response().Header().Set(`Server`, event.SoftwareName+`/`+config.Version.Number)
				return h.Handle(c)
			}
		})

		// 注册静态资源文件(网站素材文件)
		e.Use(event.StaticMW) //打包的静态资源
		// 上传文件资源(改到manager中用File函数实现)
		// e.Use(middleware.Static(&middleware.StaticOptions{
		// 	Root: helper.UploadDir,
		// 	Path: helper.UploadURLPath,
		// }))

		// 启用session
		e.Use(session.Middleware(config.SessionOptions))
		// 启用多语言支持
		config.DefaultConfig.Language.SetFSFunc(event.LangFSFunc)
		i18n := language.New(&config.DefaultConfig.Language)
		e.Use(i18n.Middleware())

		// 启用Validation
		e.Use(middleware.Validate(echo.NewValidation))

		// 事物支持
		e.Use(ngingMW.Transaction())
		// 注册模板引擎
		renderOptions := &render.Config{
			TmplDir: TemplateDir,
			Engine:  `standard`,
			ParseStrings: map[string]string{
				`__TMPL__`: TemplateDir,
			},
			DefaultHTTPErrorCode: http.StatusOK,
			Reload:               true,
			ErrorPages:           config.DefaultConfig.Sys.ErrorPages,
			ErrorProcessors:      common.ErrorProcessors,
		}
		for key, val := range ParseStrings {
			renderOptions.ParseStrings[key] = val
		}
		for key, val := range ParseStringFuncs {
			renderOptions.ParseStringFuncs[key] = val
		}
		if RendererDo != nil {
			renderOptions.AddRendererDo(RendererDo)
		}
		renderOptions.AddFuncSetter(BackendURLFunc)
		renderOptions.AddFuncSetter(ngingMW.ErrorPageFunc)
		renderOptions.ApplyTo(e, event.BackendTmplMgr)
		renderOptions.Renderer().MonitorEvent(func(file string) {
			if strings.HasSuffix(file, `.form.json`) {
				if formbuilder.DelCachedConfig(file) {
					log.Debug(`delete: cache form config: `, file)
				}
			}
		})
		//RendererDo(renderOptions.Renderer())
		echo.On(`clearCache`, func(_ echo.H) error {
			log.Debug(`clear: Backend Template Object Cache`)
			renderOptions.Renderer().ClearCache()
			formbuilder.ClearCache()
			return nil
		})
		e.Get(`/favicon.ico`, event.FaviconHandler)
		i18n.Handler(e, `App.i18n`)
		if event.Develop {
			pprof.Wrap(e)
			mux := http.NewServeMux()
			_ = statsviz.Register(mux)
			// Use echo WrapHandler to wrap statsviz ServeMux as echo HandleFunc
			e.Get("/debug/statsviz/", echo.WrapHandler(mux))
			// Serve static content for statsviz UI
			e.Get("/debug/statsviz/*", echo.WrapHandler(mux))
		}
		Initialize()
	})
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package backend

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/handler/pprof"
	"github.com/webx-top/echo/middleware"
	"github.com/webx-top/echo/middleware/language"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/echo/middleware/session"
	"github.com/webx-top/echo/subdomains"

	"github.com/arl/statsviz"

	"github.com/admpub/log"
	"github.com/admpub/nging/v3/application/cmd/event"
	"github.com/admpub/nging/v3/application/handler"
	"github.com/admpub/nging/v3/application/library/common"
	"github.com/admpub/nging/v3/application/library/config"
	"github.com/admpub/nging/v3/application/library/formbuilder"
	ngingMW "github.com/admpub/nging/v3/application/middleware"
)

const (
	DefaultTemplateDir   = `./template/backend`
	DefaultAssetsDir     = `./public/assets`
	DefaultAssetsURLPath = `/public/assets/backend`
)

var (
	TemplateDir      = DefaultTemplateDir //模板文件夹
	AssetsDir        = DefaultAssetsDir   //素材文件夹
	AssetsURLPath    = DefaultAssetsURLPath
	DefaultAvatarURL = AssetsURLPath + `/images/user_128.png`
	RendererDo       = func(driver.Driver) {}
	ParseStrings     = map[string]string{}
	ParseStringFuncs = map[string]func() string{}
	SkippedGzipPaths = map[string]bool{}
	GzipSkipper      = func(skippedPaths map[string]bool) func(c echo.Context) bool {
		return func(c echo.Context) bool {
			skipped, _ := skippedPaths[c.Request().URL().Path()]
			if !skipped {
				skipped, _ = skippedPaths[c.Path()]
			}
			return skipped
		}
	}
	DefaultLocalHostNames = []string{
		`127.0.0.1`, `localhost`,
	}
)

func MakeSubdomains(domain string, appends []string) []string {
	domainList := strings.Split(domain, `,`)
	domain = domainList[0]
	if pos := strings.Index(domain, `://`); pos > 0 {
		pos += 3
		if pos < len(domain) {
			domain = domain[pos:]
		} else {
			domain = ``
		}
	}
	var myPort string
	domain, myPort = com.SplitHost(domain)
	if len(myPort) == 0 && len(domainList) > 1 {
		_, myPort = com.SplitHost(domainList[1])
	}
	port := strconv.Itoa(config.DefaultCLIConfig.Port)
	newDomainList := []string{}
	if !com.InSlice(domain+`:`+port, domainList) {
		newDomainList = append(newDomainList, domain+`:`+port)
	}
	if myPort == port {
		myPort = ``
	}
	if len(myPort) > 0 {
		if !com.InSlice(domain+`:`+myPort, domainList) {
			newDomainList = append(newDomainList, domain+`:`+myPort)
		}
	}
	for _, hostName := range appends {
		if hostName == domain {
			continue
		}
		if !com.InSlice(hostName+`:`+port, domainList) {
			newDomainList = append(newDomainList, hostName+`:`+port)
		}
		if len(myPort) > 0 {
			if !com.InSlice(hostName+`:`+myPort, domainList) {
				newDomainList = append(newDomainList, hostName+`:`+myPort)
			}
		}
	}
	if len(newDomainList) > 0 {
		domainList = append(domainList, newDomainList...)
	}
	return domainList
}

func init() {
	echo.Set(`BackendPrefix`, handler.BackendPrefix)
	echo.Set(`GlobalPrefix`, handler.GlobalPrefix)
	event.OnStart(0, func() {
		handler.GlobalPrefix = echo.String(`GlobalPrefix`)
		handler.BackendPrefix = echo.String(`BackendPrefix`)
		handler.FrontendPrefix = echo.String(`FrontendPrefix`)
		ngingMW.DefaultAvatarURL = DefaultAssetsURLPath
		e := handler.Echo()
		e.SetPrefix(handler.GlobalPrefix)
		handler.SetRootGroup(handler.BackendPrefix)
		subdomains.Default.Default = `backend`
		subdomains.Default.Boot = `backend`
		domainName := subdomains.Default.Default
		backendDomain := config.DefaultCLIConfig.BackendDomain
		if len(backendDomain) > 0 {
			domainName += `@` + strings.Join(MakeSubdomains(backendDomain, DefaultLocalHostNames), `,`)
		}
		subdomains.Default.Add(domainName, e)

		e.Use(middleware.Log(), middleware.Recover())
		e.Use(middleware.Gzip(&middleware.GzipConfig{
			Skipper: GzipSkipper(SkippedGzipPaths),
		}))
		e.Use(func(h echo.Handler) echo.HandlerFunc {
			return func(c echo.Context) error {
				c.Response().Header().Set(`Server`, event.SoftwareName+`/`+config.Version.Number)
				return h.Handle(c)
			}
		})

		// 注册静态资源文件(网站素材文件)
		e.Use(event.StaticMW) //打包的静态资源
		// 上传文件资源(改到manager中用File函数实现)
		// e.Use(middleware.Static(&middleware.StaticOptions{
		// 	Root: helper.UploadDir,
		// 	Path: helper.UploadURLPath,
		// }))

		// 启用session
		e.Use(session.Middleware(config.SessionOptions))
		// 启用多语言支持
		config.DefaultConfig.Language.SetFSFunc(event.LangFSFunc)
		i18n := language.New(&config.DefaultConfig.Language)
		e.Use(i18n.Middleware())

		// 启用Validation
		e.Use(middleware.Validate(echo.NewValidation))

		// 事物支持
		e.Use(ngingMW.Transaction())
		// 注册模板引擎
		renderOptions := &render.Config{
			TmplDir: TemplateDir,
			Engine:  `standard`,
			ParseStrings: map[string]string{
				`__TMPL__`: TemplateDir,
			},
			DefaultHTTPErrorCode: http.StatusOK,
			Reload:               true,
			ErrorPages:           config.DefaultConfig.Sys.ErrorPages,
			ErrorProcessors:      common.ErrorProcessors,
		}
		for key, val := range ParseStrings {
			renderOptions.ParseStrings[key] = val
		}
		for key, val := range ParseStringFuncs {
			renderOptions.ParseStringFuncs[key] = val
		}
		if RendererDo != nil {
			renderOptions.AddRendererDo(RendererDo)
		}
		renderOptions.AddFuncSetter(BackendURLFunc)
		renderOptions.AddFuncSetter(ngingMW.ErrorPageFunc)
		renderOptions.ApplyTo(e, event.BackendTmplMgr)
		renderOptions.Renderer().MonitorEvent(func(file string) {
			if strings.HasSuffix(file, `.form.json`) {
				if formbuilder.DelCachedConfig(file) {
					log.Debug(`delete: cache form config: `, file)
				}
			}
		})
		//RendererDo(renderOptions.Renderer())
		echo.On(`clearCache`, func(_ echo.H) error {
			log.Debug(`clear: Backend Template Object Cache`)
			renderOptions.Renderer().ClearCache()
			formbuilder.ClearCache()
			return nil
		})
		e.Get(`/favicon.ico`, event.FaviconHandler)
		i18n.Handler(e, `App.i18n`)
		if event.Develop {
			pprof.Wrap(e)
			mux := http.NewServeMux()
			_ = statsviz.Register(mux)
			// Use echo WrapHandler to wrap statsviz ServeMux as echo HandleFunc
			e.Get("/debug/statsviz/", echo.WrapHandler(mux))
			// Serve static content for statsviz UI
			e.Get("/debug/statsviz/*", echo.WrapHandler(mux))
		}
		Initialize()
	})
}
<|endoftext|>


<commit_before>package tradeoffer

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Philipp15b/go-steam/community"
	"github.com/Philipp15b/go-steam/economy/inventory"
	"github.com/Philipp15b/go-steam/netutil"
	"github.com/Philipp15b/go-steam/steamid"
	"net/http"
	"strconv"
)

type APIKey string

const apiUrl = "http://api.steampowered.com/IEconService/%s/v%d"

type Client struct {
	client    *http.Client
	key       APIKey
	sessionId string
}

func NewClient(key APIKey, sessionId, steamLogin string) *Client {
	c := &Client{
		new(http.Client),
		key,
		sessionId,
	}
	community.SetCookies(c.client, sessionId, steamLogin)
	return c
}

func (c *Client) GetOffers() (*TradeOffers, error) {
	resp, err := c.client.Get(fmt.Sprintf(apiUrl, "GetTradeOffers", 1) + "?" + netutil.ToUrlValues(map[string]string{
		"key":                 string(c.key),
		"get_sent_offers":     "1",
		"get_received_offers": "1",
	}).Encode())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	t := new(struct {
		Response *TradeOffers
	})
	err = json.NewDecoder(resp.Body).Decode(t)
	if err != nil {
		return nil, err
	}
	return t.Response, nil
}

type actionResult struct {
	Success bool
	Error   string
}

func (c *Client) action(method string, version uint, id TradeOfferId) error {
	resp, err := c.client.Do(netutil.NewPostForm(fmt.Sprintf(apiUrl, method, version), netutil.ToUrlValues(map[string]string{
		"key":          string(c.key),
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	})))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New(method + " error: status code not 200")
	}
	return nil
}

func (c *Client) Decline(id TradeOfferId) error {
	return c.action("DeclineTradeOffer", 1, id)
}

func (c *Client) Cancel(id TradeOfferId) error {
	return c.action("CancelTradeOffer", 1, id)
}

func (c *Client) Accept(id TradeOfferId) error {
	resp, err := c.client.PostForm(fmt.Sprintf("http://steamcommunity.com/tradeoffer/%d/accept", id), netutil.ToUrlValues(map[string]string{
		"sessionid":    c.sessionId,
		"serverid":     "1",
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	}))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("accept error: status code not 200")
	}
	return nil
}

func (c *Client) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetOwnInventory(c.client, contextId, appId)
}

func (c *Client) GetTheirInventory(other steamid.SteamId, contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetFullInventory(func() (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, nil)
	}, func(start uint) (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, &start)
	})
}

func (c *Client) getPartialTheirInventory(other steamid.SteamId, contextId uint64, appId uint32, start *uint) (*inventory.PartialInventory, error) {
	data := map[string]string{
		"sessionid": c.sessionId,
		"partner":   fmt.Sprintf("%d", other),
		"contextid": strconv.FormatUint(contextId, 10),
		"appid":     strconv.FormatUint(uint64(appId), 10),
	}
	if start != nil {
		data["start"] = strconv.FormatUint(uint64(*start), 10)
	}

	const baseUrl = "http://steamcommunity.com/tradeoffer/new/"
	req, err := http.NewRequest("GET", baseUrl+"partnerinventory/?"+netutil.ToUrlValues(data).Encode(), nil)
	if err != nil {
		panic(err)
	}
	req.Header.Add("Referer", baseUrl+"?partner="+fmt.Sprintf("%d", other))

	return inventory.DoInventoryRequest(c.client, req)
}
<commit_msg>Trade Offers: Add Create method to Client<commit_after>package tradeoffer

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Philipp15b/go-steam/community"
	"github.com/Philipp15b/go-steam/economy/inventory"
	"github.com/Philipp15b/go-steam/netutil"
	"github.com/Philipp15b/go-steam/steamid"
	"net/http"
	"strconv"
)

type APIKey string

const apiUrl = "http://api.steampowered.com/IEconService/%s/v%d"

type Client struct {
	client    *http.Client
	key       APIKey
	sessionId string
}

func NewClient(key APIKey, sessionId, steamLogin string) *Client {
	c := &Client{
		new(http.Client),
		key,
		sessionId,
	}
	community.SetCookies(c.client, sessionId, steamLogin)
	return c
}

func (c *Client) GetOffers() (*TradeOffers, error) {
	resp, err := c.client.Get(fmt.Sprintf(apiUrl, "GetTradeOffers", 1) + "?" + netutil.ToUrlValues(map[string]string{
		"key":                 string(c.key),
		"get_sent_offers":     "1",
		"get_received_offers": "1",
	}).Encode())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	t := new(struct {
		Response *TradeOffers
	})
	err = json.NewDecoder(resp.Body).Decode(t)
	if err != nil {
		return nil, err
	}
	return t.Response, nil
}

type actionResult struct {
	Success bool
	Error   string
}

func (c *Client) action(method string, version uint, id TradeOfferId) error {
	resp, err := c.client.Do(netutil.NewPostForm(fmt.Sprintf(apiUrl, method, version), netutil.ToUrlValues(map[string]string{
		"key":          string(c.key),
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	})))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New(method + " error: status code not 200")
	}
	return nil
}

func (c *Client) Decline(id TradeOfferId) error {
	return c.action("DeclineTradeOffer", 1, id)
}

func (c *Client) Cancel(id TradeOfferId) error {
	return c.action("CancelTradeOffer", 1, id)
}

func (c *Client) Accept(id TradeOfferId) error {
	resp, err := c.client.PostForm(fmt.Sprintf("http://steamcommunity.com/tradeoffer/%d/accept", id), netutil.ToUrlValues(map[string]string{
		"sessionid":    c.sessionId,
		"serverid":     "1",
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	}))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("accept error: status code not 200")
	}
	return nil
}

type TradeItem struct {
	AppId     uint32
	ContextId uint64
	Amount    uint
	AssetId   uint64
}

// Sends a new trade offer to the given Steam user. You can optionally specify an access token if you've got one.
// In addition, `countered` can be non-nil, indicating the trade offer this is a counter for.
func (c *Client) Create(other steamid.SteamId, accessToken *string, myItems, theirItems []TradeItem, countered *TradeOfferId, message string) error {
	to := map[string]interface{}{
		"newversion": "true",
		"version":    "2",
		"me": map[string]interface{}{
			"assets":   myItems,
			"currency": make([]struct{}, 0),
			"ready":    "false",
		},
		"them": map[string]interface{}{
			"assets":   theirItems,
			"currency": make([]struct{}, 0),
			"ready":    "false",
		},
	}

	jto, err := json.Marshal(to)
	if err != nil {
		panic(err)
	}

	data := map[string]string{
		"sessionid":         c.sessionId,
		"serverid":          "1",
		"partner":           fmt.Sprintf("%d", other),
		"tradeoffermessage": message,
		"json_tradeoffer":   string(jto),
	}

	var referer string
	if countered != nil {
		referer = fmt.Sprintf("http://steamcommunity.com/tradeoffer/%d/", *countered)
		data["tradeofferid_countered"] = fmt.Sprintf("%d", *countered)
	} else {
		referer = fmt.Sprintf("http://steamcommunity.com/tradeoffer/new?partner=%d", other)
	}

	req := netutil.NewPostForm("http://steamcommunity.com/tradeoffer/new/send", netutil.ToUrlValues(data))
	req.Header.Add("Referer", referer)

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("accept error: status code not 200")
	}
	return nil
}

func (c *Client) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetOwnInventory(c.client, contextId, appId)
}

func (c *Client) GetTheirInventory(other steamid.SteamId, contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetFullInventory(func() (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, nil)
	}, func(start uint) (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, &start)
	})
}

func (c *Client) getPartialTheirInventory(other steamid.SteamId, contextId uint64, appId uint32, start *uint) (*inventory.PartialInventory, error) {
	data := map[string]string{
		"sessionid": c.sessionId,
		"partner":   fmt.Sprintf("%d", other),
		"contextid": strconv.FormatUint(contextId, 10),
		"appid":     strconv.FormatUint(uint64(appId), 10),
	}
	if start != nil {
		data["start"] = strconv.FormatUint(uint64(*start), 10)
	}

	const baseUrl = "http://steamcommunity.com/tradeoffer/new/"
	req, err := http.NewRequest("GET", baseUrl+"partnerinventory/?"+netutil.ToUrlValues(data).Encode(), nil)
	if err != nil {
		panic(err)
	}
	req.Header.Add("Referer", baseUrl+"?partner="+fmt.Sprintf("%d", other))

	return inventory.DoInventoryRequest(c.client, req)
}
<|endoftext|>


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

//go:build !nopowersupplyclass
// +build !nopowersupplyclass

package collector

/*
#cgo LDFLAGS: -framework IOKit -framework CoreFoundation
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/ps/IOPSKeys.h>
#include <CoreFoundation/CFArray.h>

// values collected from IOKit Power Source APIs
// Functions documentation available at
// https://developer.apple.com/documentation/iokit/iopowersources_h
// CFDictionary keys definition
// https://developer.apple.com/documentation/iokit/iopskeys_h/defines
struct macos_powersupply {
   char *Name;
   char *PowerSourceState;
   char *Type;
   char *TransportType;
   char *BatteryHealth;
   char *HardwareSerialNumber;

   int *PowerSourceID;
   int *CurrentCapacity;
   int *MaxCapacity;
   int *DesignCapacity;
   int *NominalCapacity;

   int *TimeToEmpty;
   int *TimeToFullCharge;

   int *Voltage;
   int *Current;

   int *Temperature;

   // boolean values
   int *IsCharged;
   int *IsCharging;
   int *InternalFailure;
   int *IsPresent;
};

int *CFDictionaryGetInt(CFDictionaryRef theDict, const void *key) {
    CFNumberRef tmp;
    int *value;

    tmp = CFDictionaryGetValue(theDict, key);

    if (tmp == NULL)
        return NULL;

    value = (int*)malloc(sizeof(int));
    if (CFNumberGetValue(tmp, kCFNumberIntType, value)) {
        return value;
    }

    free(value);
    return NULL;
}

int *CFDictionaryGetBoolean(CFDictionaryRef theDict, const void *key) {
    CFBooleanRef tmp;
    int *value;

    tmp = CFDictionaryGetValue(theDict, key);

    if (tmp == NULL)
        return NULL;

    value = (int*)malloc(sizeof(int));
    if (CFBooleanGetValue(tmp)) {
        *value = 1;
    } else {
        *value = 0;
    }

    return value;
}

char *CFDictionaryGetSring(CFDictionaryRef theDict, const void *key) {
    CFStringRef tmp;
    CFIndex size;
    char *value;

    tmp = CFDictionaryGetValue(theDict, key);

    if (tmp == NULL)
        return NULL;

    size = CFStringGetLength(tmp) + 1;
    value = (char*)malloc(size);

    if(CFStringGetCString(tmp, value, size, kCFStringEncodingUTF8)) {
         return value;
    }

    free(value);
    return NULL;
}

struct macos_powersupply* getPowerSupplyInfo(CFDictionaryRef powerSourceInformation) {
    struct macos_powersupply *ret;

    if (powerSourceInformation == NULL)
        return NULL;

    ret = (struct macos_powersupply*)malloc(sizeof(struct macos_powersupply));

    ret->PowerSourceID    = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSPowerSourceIDKey));
    ret->CurrentCapacity  = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSCurrentCapacityKey));
    ret->MaxCapacity      = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSMaxCapacityKey));
    ret->DesignCapacity   = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSDesignCapacityKey));
    ret->NominalCapacity  = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSNominalCapacityKey));
    ret->TimeToEmpty      = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTimeToEmptyKey));
    ret->TimeToFullCharge = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTimeToFullChargeKey));
    ret->Voltage          = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSVoltageKey));
    ret->Current          = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSCurrentKey));
    ret->Temperature      = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTemperatureKey));

    ret->Name = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSNameKey));
    ret->PowerSourceState = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSPowerSourceStateKey));
    ret->Type = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSTypeKey));
    ret->TransportType = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSTransportTypeKey));
    ret->BatteryHealth = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSBatteryHealthKey));
    ret->HardwareSerialNumber = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSHardwareSerialNumberKey));

    ret->IsCharged       = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsChargedKey));
    ret->IsCharging      = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsChargingKey));
    ret->InternalFailure = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSInternalFailureKey));
    ret->IsPresent       = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsPresentKey));

    return ret;
}

void releasePowerSupply(struct macos_powersupply *ps) {
    free(ps->Name);
    free(ps->PowerSourceState);
    free(ps->Type);
    free(ps->TransportType);
    free(ps->BatteryHealth);
    free(ps->HardwareSerialNumber);

    free(ps->PowerSourceID);
    free(ps->CurrentCapacity);
    free(ps->MaxCapacity);
    free(ps->DesignCapacity);
    free(ps->NominalCapacity);
    free(ps->TimeToEmpty);
    free(ps->TimeToFullCharge);
    free(ps->Voltage);
    free(ps->Current);
    free(ps->Temperature);

    free(ps->IsCharged);
    free(ps->IsCharging);
    free(ps->InternalFailure);
    free(ps->IsPresent);

    free(ps);
}
*/
import "C"

import (
	"fmt"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error {
	psList, err := getPowerSourceList()
	if err != nil {
		return fmt.Errorf("couldn't get IOPPowerSourcesList: %w", err)
	}

	for _, info := range psList {
		labels := getPowerSourceDescriptorLabels(info)
		powerSupplyName := labels["power_supply"]

		if c.ignoredPattern.MatchString(powerSupplyName) {
			continue
		}

		for name, value := range getPowerSourceDescriptorMap(info) {
			if value == nil {
				continue
			}

			ch <- prometheus.MustNewConstMetric(
				prometheus.NewDesc(
					prometheus.BuildFQName(namespace, c.subsystem, name),
					fmt.Sprintf("IOKit Power Source information field %s for <power_supply>.", name),
					[]string{"power_supply"}, nil,
				),
				prometheus.GaugeValue, *value, powerSupplyName,
			)
		}

		pushEnumMetric(
			ch,
			getPowerSourceDescriptorState(info),
			"power_source_state",
			c.subsystem,
			powerSupplyName,
		)

		pushEnumMetric(
			ch,
			getPowerSourceDescriptorBatteryHealth(info),
			"battery_health",
			c.subsystem,
			powerSupplyName,
		)

		var (
			keys   []string
			values []string
		)
		for name, value := range labels {
			if value != "" {
				keys = append(keys, name)
				values = append(values, value)
			}
		}
		fieldDesc := prometheus.NewDesc(
			prometheus.BuildFQName(namespace, c.subsystem, "info"),
			"IOKit Power Source information for <power_supply>.",
			keys,
			nil,
		)
		ch <- prometheus.MustNewConstMetric(fieldDesc, prometheus.GaugeValue, 1.0, values...)

		C.releasePowerSupply(info)
	}

	return nil
}

// getPowerSourceList fetches information from IOKit APIs
//
// Data is provided as opaque CoreFoundation references
// C.getPowerSupplyInfo will convert those objects in something
// easily manageable in Go.
// https://developer.apple.com/documentation/iokit/iopowersources_h
func getPowerSourceList() ([]*C.struct_macos_powersupply, error) {
	infos, err := C.IOPSCopyPowerSourcesInfo()
	if err != nil {
		return nil, err
	}
	defer C.CFRelease(infos)

	psList, err := C.IOPSCopyPowerSourcesList(infos)
	if err != nil {
		return nil, err
	}

	if psList == C.CFArrayRef(0) {
		return nil, nil
	}
	defer C.CFRelease(C.CFTypeRef(psList))

	size, err := C.CFArrayGetCount(psList)
	if err != nil {
		return nil, err
	}

	ret := make([]*C.struct_macos_powersupply, size)
	for i := C.CFIndex(0); i < size; i++ {
		ps, err := C.CFArrayGetValueAtIndex(psList, i)
		if err != nil {
			return nil, err
		}

		dict, err := C.IOPSGetPowerSourceDescription(infos, (C.CFTypeRef)(ps))
		if err != nil {
			return nil, err
		}

		info, err := C.getPowerSupplyInfo(dict)
		if err != nil {
			return nil, err
		}

		ret[int(i)] = info
	}

	return ret, nil
}

func getPowerSourceDescriptorMap(info *C.struct_macos_powersupply) map[string]*float64 {
	return map[string]*float64{
		"current_capacity":      convertValue(info.CurrentCapacity),
		"max_capacity":          convertValue(info.MaxCapacity),
		"design_capacity":       convertValue(info.DesignCapacity),
		"nominal_capacity":      convertValue(info.NominalCapacity),
		"time_to_empty_seconds": minutesToSeconds(info.TimeToEmpty),
		"time_to_full_seconds":  minutesToSeconds(info.TimeToFullCharge),
		"voltage_volt":          scaleValue(info.Voltage, 1e3),
		"current_ampere":        scaleValue(info.Current, 1e3),
		"temp_celsius":          convertValue(info.Temperature),
		"present":               convertValue(info.IsPresent),
		"charging":              convertValue(info.IsCharging),
		"charged":               convertValue(info.IsCharged),
		"internal_failure":      convertValue(info.InternalFailure),
	}
}

func getPowerSourceDescriptorLabels(info *C.struct_macos_powersupply) map[string]string {
	return map[string]string{
		"id":             strconv.FormatInt(int64(*info.PowerSourceID), 10),
		"power_supply":   C.GoString(info.Name),
		"type":           C.GoString(info.Type),
		"transport_type": C.GoString(info.TransportType),
		"serial_number":  C.GoString(info.HardwareSerialNumber),
	}
}

func getPowerSourceDescriptorState(info *C.struct_macos_powersupply) map[string]float64 {
	stateMap := map[string]float64{
		"Off Line":      0,
		"AC Power":      0,
		"Battery Power": 0,
	}

	// This field is always present
	// https://developer.apple.com/documentation/iokit/kiopspowersourcestatekey
	stateMap[C.GoString(info.PowerSourceState)] = 1

	return stateMap
}

func getPowerSourceDescriptorBatteryHealth(info *C.struct_macos_powersupply) map[string]float64 {
	// This field is optional
	// https://developer.apple.com/documentation/iokit/kiopsBatteryHealthkey
	if info.BatteryHealth == nil {
		return nil
	}

	stateMap := map[string]float64{
		"Good": 0,
		"Fair": 0,
		"Poor": 0,
	}

	stateMap[C.GoString(info.BatteryHealth)] = 1

	return stateMap
}

func convertValue(value *C.int) *float64 {
	if value == nil {
		return nil
	}

	ret := new(float64)
	*ret = (float64)(*value)
	return ret
}

func scaleValue(value *C.int, scale float64) *float64 {
	ret := convertValue(value)
	if ret == nil {
		return nil
	}

	*ret /= scale

	return ret
}

// minutesToSeconds converts *C.int minutes into *float64 seconds.
//
// Only positive values will be scaled to seconds, because negative ones
// have special meanings. I.e. -1 indicates "Still Calculating the Time"
func minutesToSeconds(minutes *C.int) *float64 {
	ret := convertValue(minutes)
	if ret == nil {
		return nil
	}

	if *ret > 0 {
		*ret *= 60
	}

	return ret
}

func pushEnumMetric(ch chan<- prometheus.Metric, values map[string]float64, name, subsystem, powerSupply string) {
	for state, value := range values {
		ch <- prometheus.MustNewConstMetric(
			prometheus.NewDesc(
				prometheus.BuildFQName(namespace, subsystem, name),
				fmt.Sprintf("IOKit Power Source information field %s for <power_supply>.", name),
				[]string{"power_supply", "state"}, nil,
			),
			prometheus.GaugeValue, value, powerSupply, state,
		)
	}
}
<commit_msg>powersupplyclass_darwin: enable builds against older macOS SDK<commit_after>// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !nopowersupplyclass
// +build !nopowersupplyclass

package collector

/*
#cgo LDFLAGS: -framework IOKit -framework CoreFoundation
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFRunLoop.h>
#include <CoreFoundation/CFString.h>
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/ps/IOPSKeys.h>

// values collected from IOKit Power Source APIs
// Functions documentation available at
// https://developer.apple.com/documentation/iokit/iopowersources_h
// CFDictionary keys definition
// https://developer.apple.com/documentation/iokit/iopskeys_h/defines
struct macos_powersupply {
   char *Name;
   char *PowerSourceState;
   char *Type;
   char *TransportType;
   char *BatteryHealth;
   char *HardwareSerialNumber;

   int *PowerSourceID;
   int *CurrentCapacity;
   int *MaxCapacity;
   int *DesignCapacity;
   int *NominalCapacity;

   int *TimeToEmpty;
   int *TimeToFullCharge;

   int *Voltage;
   int *Current;

   int *Temperature;

   // boolean values
   int *IsCharged;
   int *IsCharging;
   int *InternalFailure;
   int *IsPresent;
};

int *CFDictionaryGetInt(CFDictionaryRef theDict, const void *key) {
    CFNumberRef tmp;
    int *value;

    tmp = CFDictionaryGetValue(theDict, key);

    if (tmp == NULL)
        return NULL;

    value = (int*)malloc(sizeof(int));
    if (CFNumberGetValue(tmp, kCFNumberIntType, value)) {
        return value;
    }

    free(value);
    return NULL;
}

int *CFDictionaryGetBoolean(CFDictionaryRef theDict, const void *key) {
    CFBooleanRef tmp;
    int *value;

    tmp = CFDictionaryGetValue(theDict, key);

    if (tmp == NULL)
        return NULL;

    value = (int*)malloc(sizeof(int));
    if (CFBooleanGetValue(tmp)) {
        *value = 1;
    } else {
        *value = 0;
    }

    return value;
}

char *CFDictionaryGetSring(CFDictionaryRef theDict, const void *key) {
    CFStringRef tmp;
    CFIndex size;
    char *value;

    tmp = CFDictionaryGetValue(theDict, key);

    if (tmp == NULL)
        return NULL;

    size = CFStringGetLength(tmp) + 1;
    value = (char*)malloc(size);

    if(CFStringGetCString(tmp, value, size, kCFStringEncodingUTF8)) {
         return value;
    }

    free(value);
    return NULL;
}

struct macos_powersupply* getPowerSupplyInfo(CFDictionaryRef powerSourceInformation) {
    struct macos_powersupply *ret;

    if (powerSourceInformation == NULL)
        return NULL;

    ret = (struct macos_powersupply*)malloc(sizeof(struct macos_powersupply));

    ret->PowerSourceID    = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSPowerSourceIDKey));
    ret->CurrentCapacity  = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSCurrentCapacityKey));
    ret->MaxCapacity      = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSMaxCapacityKey));
    ret->DesignCapacity   = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSDesignCapacityKey));
    ret->NominalCapacity  = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSNominalCapacityKey));
    ret->TimeToEmpty      = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTimeToEmptyKey));
    ret->TimeToFullCharge = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTimeToFullChargeKey));
    ret->Voltage          = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSVoltageKey));
    ret->Current          = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSCurrentKey));
    ret->Temperature      = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTemperatureKey));

    ret->Name = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSNameKey));
    ret->PowerSourceState = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSPowerSourceStateKey));
    ret->Type = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSTypeKey));
    ret->TransportType = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSTransportTypeKey));
    ret->BatteryHealth = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSBatteryHealthKey));
    ret->HardwareSerialNumber = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSHardwareSerialNumberKey));

    ret->IsCharged       = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsChargedKey));
    ret->IsCharging      = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsChargingKey));
    ret->InternalFailure = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSInternalFailureKey));
    ret->IsPresent       = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsPresentKey));

    return ret;
}

void releasePowerSupply(struct macos_powersupply *ps) {
    free(ps->Name);
    free(ps->PowerSourceState);
    free(ps->Type);
    free(ps->TransportType);
    free(ps->BatteryHealth);
    free(ps->HardwareSerialNumber);

    free(ps->PowerSourceID);
    free(ps->CurrentCapacity);
    free(ps->MaxCapacity);
    free(ps->DesignCapacity);
    free(ps->NominalCapacity);
    free(ps->TimeToEmpty);
    free(ps->TimeToFullCharge);
    free(ps->Voltage);
    free(ps->Current);
    free(ps->Temperature);

    free(ps->IsCharged);
    free(ps->IsCharging);
    free(ps->InternalFailure);
    free(ps->IsPresent);

    free(ps);
}
*/
import "C"

import (
	"fmt"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error {
	psList, err := getPowerSourceList()
	if err != nil {
		return fmt.Errorf("couldn't get IOPPowerSourcesList: %w", err)
	}

	for _, info := range psList {
		labels := getPowerSourceDescriptorLabels(info)
		powerSupplyName := labels["power_supply"]

		if c.ignoredPattern.MatchString(powerSupplyName) {
			continue
		}

		for name, value := range getPowerSourceDescriptorMap(info) {
			if value == nil {
				continue
			}

			ch <- prometheus.MustNewConstMetric(
				prometheus.NewDesc(
					prometheus.BuildFQName(namespace, c.subsystem, name),
					fmt.Sprintf("IOKit Power Source information field %s for <power_supply>.", name),
					[]string{"power_supply"}, nil,
				),
				prometheus.GaugeValue, *value, powerSupplyName,
			)
		}

		pushEnumMetric(
			ch,
			getPowerSourceDescriptorState(info),
			"power_source_state",
			c.subsystem,
			powerSupplyName,
		)

		pushEnumMetric(
			ch,
			getPowerSourceDescriptorBatteryHealth(info),
			"battery_health",
			c.subsystem,
			powerSupplyName,
		)

		var (
			keys   []string
			values []string
		)
		for name, value := range labels {
			if value != "" {
				keys = append(keys, name)
				values = append(values, value)
			}
		}
		fieldDesc := prometheus.NewDesc(
			prometheus.BuildFQName(namespace, c.subsystem, "info"),
			"IOKit Power Source information for <power_supply>.",
			keys,
			nil,
		)
		ch <- prometheus.MustNewConstMetric(fieldDesc, prometheus.GaugeValue, 1.0, values...)

		C.releasePowerSupply(info)
	}

	return nil
}

// getPowerSourceList fetches information from IOKit APIs
//
// Data is provided as opaque CoreFoundation references
// C.getPowerSupplyInfo will convert those objects in something
// easily manageable in Go.
// https://developer.apple.com/documentation/iokit/iopowersources_h
func getPowerSourceList() ([]*C.struct_macos_powersupply, error) {
	infos, err := C.IOPSCopyPowerSourcesInfo()
	if err != nil {
		return nil, err
	}
	defer C.CFRelease(infos)

	psList, err := C.IOPSCopyPowerSourcesList(infos)
	if err != nil {
		return nil, err
	}

	if psList == C.CFArrayRef(0) {
		return nil, nil
	}
	defer C.CFRelease(C.CFTypeRef(psList))

	size, err := C.CFArrayGetCount(psList)
	if err != nil {
		return nil, err
	}

	ret := make([]*C.struct_macos_powersupply, size)
	for i := C.CFIndex(0); i < size; i++ {
		ps, err := C.CFArrayGetValueAtIndex(psList, i)
		if err != nil {
			return nil, err
		}

		dict, err := C.IOPSGetPowerSourceDescription(infos, (C.CFTypeRef)(ps))
		if err != nil {
			return nil, err
		}

		info, err := C.getPowerSupplyInfo(dict)
		if err != nil {
			return nil, err
		}

		ret[int(i)] = info
	}

	return ret, nil
}

func getPowerSourceDescriptorMap(info *C.struct_macos_powersupply) map[string]*float64 {
	return map[string]*float64{
		"current_capacity":      convertValue(info.CurrentCapacity),
		"max_capacity":          convertValue(info.MaxCapacity),
		"design_capacity":       convertValue(info.DesignCapacity),
		"nominal_capacity":      convertValue(info.NominalCapacity),
		"time_to_empty_seconds": minutesToSeconds(info.TimeToEmpty),
		"time_to_full_seconds":  minutesToSeconds(info.TimeToFullCharge),
		"voltage_volt":          scaleValue(info.Voltage, 1e3),
		"current_ampere":        scaleValue(info.Current, 1e3),
		"temp_celsius":          convertValue(info.Temperature),
		"present":               convertValue(info.IsPresent),
		"charging":              convertValue(info.IsCharging),
		"charged":               convertValue(info.IsCharged),
		"internal_failure":      convertValue(info.InternalFailure),
	}
}

func getPowerSourceDescriptorLabels(info *C.struct_macos_powersupply) map[string]string {
	return map[string]string{
		"id":             strconv.FormatInt(int64(*info.PowerSourceID), 10),
		"power_supply":   C.GoString(info.Name),
		"type":           C.GoString(info.Type),
		"transport_type": C.GoString(info.TransportType),
		"serial_number":  C.GoString(info.HardwareSerialNumber),
	}
}

func getPowerSourceDescriptorState(info *C.struct_macos_powersupply) map[string]float64 {
	stateMap := map[string]float64{
		"Off Line":      0,
		"AC Power":      0,
		"Battery Power": 0,
	}

	// This field is always present
	// https://developer.apple.com/documentation/iokit/kiopspowersourcestatekey
	stateMap[C.GoString(info.PowerSourceState)] = 1

	return stateMap
}

func getPowerSourceDescriptorBatteryHealth(info *C.struct_macos_powersupply) map[string]float64 {
	// This field is optional
	// https://developer.apple.com/documentation/iokit/kiopsBatteryHealthkey
	if info.BatteryHealth == nil {
		return nil
	}

	stateMap := map[string]float64{
		"Good": 0,
		"Fair": 0,
		"Poor": 0,
	}

	stateMap[C.GoString(info.BatteryHealth)] = 1

	return stateMap
}

func convertValue(value *C.int) *float64 {
	if value == nil {
		return nil
	}

	ret := new(float64)
	*ret = (float64)(*value)
	return ret
}

func scaleValue(value *C.int, scale float64) *float64 {
	ret := convertValue(value)
	if ret == nil {
		return nil
	}

	*ret /= scale

	return ret
}

// minutesToSeconds converts *C.int minutes into *float64 seconds.
//
// Only positive values will be scaled to seconds, because negative ones
// have special meanings. I.e. -1 indicates "Still Calculating the Time"
func minutesToSeconds(minutes *C.int) *float64 {
	ret := convertValue(minutes)
	if ret == nil {
		return nil
	}

	if *ret > 0 {
		*ret *= 60
	}

	return ret
}

func pushEnumMetric(ch chan<- prometheus.Metric, values map[string]float64, name, subsystem, powerSupply string) {
	for state, value := range values {
		ch <- prometheus.MustNewConstMetric(
			prometheus.NewDesc(
				prometheus.BuildFQName(namespace, subsystem, name),
				fmt.Sprintf("IOKit Power Source information field %s for <power_supply>.", name),
				[]string{"power_supply", "state"}, nil,
			),
			prometheus.GaugeValue, value, powerSupply, state,
		)
	}
}
<|endoftext|>