<commit_before>package conns

import (
	"net"
	"sync"
	"common"
	"encoding/gob"
	"io"
)

type connWrap struct {
	Raddr string
	conn net.Conn
	ErrCh chan error
	enc *gob.Encoder
}

var conns = map[string]*connWrap{}
var connsL = sync.RWMutex{}

// IsConnected returns whether or not a connection to the given address is
// currently active
func IsConnected(n string) bool {
	connsL.RLock()
	defer connsL.RUnlock()
	_,ok := conns[n]
	return ok
}

// Add connects to the give remote address and adds it to the connection table,
// at the same time setting up a connection spin to read errors on the
// connection. Returns the connection wrap object assuming there were no errors
func Add(raddr string) (*connWrap,error) {
	connsL.Lock()
	defer connsL.Unlock()

	if cw,ok := conns[raddr]; ok {
		return cw,nil
	}

	c,err := net.Dial("tcp",raddr)
	if err != nil {
		return nil,err
	}

	cw := &connWrap{
		Raddr: raddr,
		conn: c,
		ErrCh: make(chan error),
		enc: gob.NewEncoder(c),
	}
	conns[raddr] = cw

	go connReadSpin(cw)

	return cw,nil

}

func connReadSpin(cw *connWrap) {
	for {
		// We'll never actually get any data here, but you gotta block on
		// something right?
		b := make([]byte,1)
		_,err := cw.conn.Read(b)

		if err == io.EOF {
			break
		}
	}

	Remove(cw.Raddr)
	close(cw.ErrCh)
	cw.conn.Close()
}

// Get gets the connWrap struct for an address, and bool for whether it was
// actually in the table
func Get(raddr string) (*connWrap,bool) {
	connsL.RLock()
	defer connsL.RUnlock()

	cw,err := conns[raddr]
	return cw,err
}

// Remove removes a connection from the table and closes down the connection
// spin connection
func Remove(raddr string) bool {
	connsL.Lock()
	defer connsL.Unlock()

	if cw,ok := conns[raddr]; ok {
		//TODO if someone Removes then immediately Adds a connection it could
		//possibly get Remove'd again by connReadSpin
		cw.conn.Close()
		delete(conns,raddr)
		return true
	}

	return false
}

// Send sends a message to the remote location if the channel is in the table
// and currently available for sending
func Send(raddr string, msg interface{}) error {
	connsL.RLock()
	defer connsL.RUnlock()

	if cw,ok := conns[raddr]; ok {
		return sendDirect(cw,msg)
	}

	return nil
}

func sendDirect(cw *connWrap, msg interface{}) error {
	msgwrap := &common.MsgWrap{ msg }
	return cw.enc.Encode(msgwrap)
}

// SendAll loops through and asynchronously sends a message to all currently
// active connections
func SendAll(msg interface{}) {
	connsL.RLock()
	defer connsL.RUnlock()

	for _,cw := range conns {
		go sendDirect(cw,msg)
	}
}

// Register registers a type as a potential decode unmarshalling type. This must
// be done for all message types that will be sent.
func Register(something interface{}) {
	gob.Register(something)
}
<commit_msg>made Send return better error<commit_after>package conns

import (
	"net"
	"sync"
	"common"
	"encoding/gob"
	"io"
	"errors"
)

type connWrap struct {
	Raddr string
	conn net.Conn
	ErrCh chan error
	enc *gob.Encoder
}

var conns = map[string]*connWrap{}
var connsL = sync.RWMutex{}

// IsConnected returns whether or not a connection to the given address is
// currently active
func IsConnected(n string) bool {
	connsL.RLock()
	defer connsL.RUnlock()
	_,ok := conns[n]
	return ok
}

// Add connects to the give remote address and adds it to the connection table,
// at the same time setting up a connection spin to read errors on the
// connection. Returns the connection wrap object assuming there were no errors
func Add(raddr string) (*connWrap,error) {
	connsL.Lock()
	defer connsL.Unlock()

	if cw,ok := conns[raddr]; ok {
		return cw,nil
	}

	c,err := net.Dial("tcp",raddr)
	if err != nil {
		return nil,err
	}

	cw := &connWrap{
		Raddr: raddr,
		conn: c,
		ErrCh: make(chan error),
		enc: gob.NewEncoder(c),
	}
	conns[raddr] = cw

	go connReadSpin(cw)

	return cw,nil

}

func connReadSpin(cw *connWrap) {
	for {
		// We'll never actually get any data here, but you gotta block on
		// something right?
		b := make([]byte,1)
		_,err := cw.conn.Read(b)

		if err == io.EOF {
			break
		}
	}

	Remove(cw.Raddr)
	close(cw.ErrCh)
	cw.conn.Close()
}

// Get gets the connWrap struct for an address, and bool for whether it was
// actually in the table
func Get(raddr string) (*connWrap,bool) {
	connsL.RLock()
	defer connsL.RUnlock()

	cw,err := conns[raddr]
	return cw,err
}

// Remove removes a connection from the table and closes down the connection
// spin connection
func Remove(raddr string) bool {
	connsL.Lock()
	defer connsL.Unlock()

	if cw,ok := conns[raddr]; ok {
		//TODO if someone Removes then immediately Adds a connection it could
		//possibly get Remove'd again by connReadSpin
		cw.conn.Close()
		delete(conns,raddr)
		return true
	}

	return false
}

// Send sends a message to the remote location if the channel is in the table
// and currently available for sending
func Send(raddr string, msg interface{}) error {
	connsL.RLock()
	defer connsL.RUnlock()

	if cw,ok := conns[raddr]; ok {
		return sendDirect(cw,msg)
	} else {
		return errors.New("connection not established")
	}
}

func sendDirect(cw *connWrap, msg interface{}) error {
	msgwrap := &common.MsgWrap{ msg }
	return cw.enc.Encode(msgwrap)
}

// SendAll loops through and asynchronously sends a message to all currently
// active connections
func SendAll(msg interface{}) {
	connsL.RLock()
	defer connsL.RUnlock()

	for _,cw := range conns {
		go sendDirect(cw,msg)
	}
}

// Register registers a type as a potential decode unmarshalling type. This must
// be done for all message types that will be sent.
func Register(something interface{}) {
	gob.Register(something)
}
<|endoftext|>


<commit_before>// -----------------  update-geodb.go  -----------------
// The directory `static` is just for keeping static resources and tools.
// This file will NOT be compiled into deblocus executable file.
// This is a independent tool for generating or updating the `deblocus/geo/geodb.go`.
// Usage:
//     cd deblocus/static/update-geodb
//     go run update-geodb.go
// -----------------------------------------------------
package main

import (
	"bufio"
	"bytes"
	"compress/zlib"
	"fmt"
	"os"
	"strconv"

	"github.com/Lafeng/deblocus/geo"
)

const (
	db_file   = "../../geo/geodb.go"
	self_name = "update-geodb.go"
	self_dir  = "deblocus/static/update-geodb"
)

func throwIf(cond bool, e ...interface{}) {
	if cond {
		var err string
		if f, y := e[0].(string); y {
			err = fmt.Sprintf(f, e[1:]...)
		} else {
			err = fmt.Sprint(e...)
		}
		panic(err)
	}
}

func main() {
	if IsNotExist(self_name) {
		fmt.Println("Please change cwd to", self_dir)
		os.Exit(1)
	}

	dst := init_files()
	defer func() {
		dst.Close()
		if e := recover(); e == nil {
			os.Rename(dst.Name(), db_file)
		} else {
			panic(e)
		}
	}()
	dw := build()
	dw.writeDone(dst)
	fmt.Println("Update done.")
}

func init_files() *os.File {
	throwIf(IsNotExist(geo.GEO2_IPV4_FILE), geo.GEO2_IPV4_FILE)
	throwIf(IsNotExist(geo.GEO2_LOC_FILE), geo.GEO2_LOC_FILE)
	dstFile, e := os.OpenFile(db_file+".tmp", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
	throwIf(e != nil, "OpenFile %s %v", db_file, e)
	return dstFile
}

func build() *dbWriter {
	reader := new(geo.GeoLite2Reader)
	tab := reader.ReadToRoutingTable()
	t, b, p := geo.Serialize(tab)
	dw := newDbWriter()
	dw.writeEntry(t)
	dw.writeEntry(b)
	dw.writeEntry(p)
	return dw
}

type dbWriter struct {
	entries []*bytes.Buffer
	lens    []int
	cnt     int
}

func newDbWriter() *dbWriter {
	return &dbWriter{
		entries: make([]*bytes.Buffer, 0),
		lens:    make([]int, 0),
	}
}

func (dw *dbWriter) writeEntry(data []byte) {
	_len := len(data)
	dw.lens = append(dw.lens, _len)
	buf := new(bytes.Buffer)
	if _len <= 0 {
		dw.entries = append(dw.entries, buf)
	} else {
		w := zlib.NewWriter(buf)
		w.Write(data)
		w.Flush()
		w.Close()
		dw.entries = append(dw.entries, buf)
	}
}

func (dw *dbWriter) writeDone(fp *os.File) {
	bufw := bufio.NewWriter(fp)
	bufw.WriteString(header)
	// write lens
	bufw.WriteString(fmt.Sprintf("\tvar lens = %#v\n", dw.lens))
	fnReturn := "\treturn "
	// each entry
	for i, e := range dw.entries {
		if e.Len() > 0 {
			fnReturn += fmt.Sprintf("decompress(db%d, lens[%d]), ", i, i)
		} else {
			fnReturn += fmt.Sprintf("db%d, ", i)
		}
		bufw.WriteString(fmt.Sprintf("\tvar db%d = []byte(", i))
		bufw.WriteString(strconv.QuoteToASCII(string(e.Bytes())))
		bufw.WriteString(")\n")
	}
	// func return
	bufw.WriteString(fnReturn[:len(fnReturn)-2])
	bufw.WriteString("\n}\n")
	bufw.WriteString(footer)
	bufw.Flush()
}

func IsNotExist(file string) bool {
	_, err := os.Stat(file)
	return os.IsNotExist(err)
}

var header = `package geo

import (
	"bytes"
	"compress/zlib"
	"fmt"
	"io"
)

func buildGeoDB() ([]byte, []byte, []byte) {
`

var footer = `

func decompress(b []byte, lens int) []byte {
	zr, e := zlib.NewReader(bytes.NewReader(b))
	if e != nil {
		panic(e)
	}
	var nw int
	w := make([]byte, lens)
	for nw < lens {
		n, e := zr.Read(w[nw:])
		nw += n
		if e != nil {
			if e == io.EOF {
				break
			}
			panic(e)
		}
	}
	if nw != lens {
		panic(fmt.Errorf("expected len=%d but read len=%d", lens, nw))
	}
	return w
}
`
<commit_msg>gofmt<commit_after>// -----------------  update-geodb.go  -----------------
// The directory `static` is just for keeping static resources and tools.
// This file will NOT be compiled into deblocus executable file.
// This is a independent tool for generating or updating the `deblocus/geo/geodb.go`.
// Usage:
//     cd deblocus/static/update-geodb
//     go run update-geodb.go
// -----------------------------------------------------
package main

import (
	"bufio"
	"bytes"
	"compress/zlib"
	"fmt"
	"os"
	"strconv"

	"github.com/Lafeng/deblocus/geo"
)

const (
	db_file   = "../../geo/geodb.go"
	self_name = "update-geodb.go"
	self_dir  = "deblocus/static/update-geodb"
)

func throwIf(cond bool, e ...interface{}) {
	if cond {
		var err string
		if f, y := e[0].(string); y {
			err = fmt.Sprintf(f, e[1:]...)
		} else {
			err = fmt.Sprint(e...)
		}
		panic(err)
	}
}

func main() {
	if IsNotExist(self_name) {
		fmt.Println("Please change cwd to", self_dir)
		os.Exit(1)
	}

	dst := init_files()
	defer func() {
		dst.Close()
		if e := recover(); e == nil {
			os.Rename(dst.Name(), db_file)
		} else {
			panic(e)
		}
	}()
	dw := build()
	dw.writeDone(dst)
	fmt.Println("Update done.")
}

func init_files() *os.File {
	throwIf(IsNotExist(geo.GEO2_IPV4_FILE), geo.GEO2_IPV4_FILE)
	throwIf(IsNotExist(geo.GEO2_LOC_FILE), geo.GEO2_LOC_FILE)
	dstFile, e := os.OpenFile(db_file+".tmp", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
	throwIf(e != nil, "OpenFile %s %v", db_file, e)
	return dstFile
}

func build() *dbWriter {
	reader := new(geo.GeoLite2Reader)
	tab := reader.ReadToRoutingTable()
	t, b, p := geo.Serialize(tab)
	dw := newDbWriter()
	dw.writeEntry(t)
	dw.writeEntry(b)
	dw.writeEntry(p)
	return dw
}

type dbWriter struct {
	entries []*bytes.Buffer
	lens    []int
	cnt     int
}

func newDbWriter() *dbWriter {
	return &dbWriter{
		entries: make([]*bytes.Buffer, 0),
		lens:    make([]int, 0),
	}
}

func (dw *dbWriter) writeEntry(data []byte) {
	_len := len(data)
	dw.lens = append(dw.lens, _len)
	buf := new(bytes.Buffer)
	if _len <= 0 {
		dw.entries = append(dw.entries, buf)
	} else {
		w := zlib.NewWriter(buf)
		w.Write(data)
		w.Flush()
		w.Close()
		dw.entries = append(dw.entries, buf)
	}
}

func (dw *dbWriter) writeDone(fp *os.File) {
	bufw := bufio.NewWriter(fp)
	bufw.WriteString(header)
	// write lens
	bufw.WriteString(fmt.Sprintf("\tvar lens = %#v\n", dw.lens))
	fnReturn := "\treturn "
	// each entry
	for i, e := range dw.entries {
		if e.Len() > 0 {
			fnReturn += fmt.Sprintf("decompress(db%d, lens[%d]), ", i, i)
		} else {
			fnReturn += fmt.Sprintf("db%d, ", i)
		}
		bufw.WriteString(fmt.Sprintf("\tvar db%d = []byte(", i))
		bufw.WriteString(strconv.QuoteToASCII(string(e.Bytes())))
		bufw.WriteString(")\n")
	}
	// func return
	bufw.WriteString(fnReturn[:len(fnReturn)-2])
	bufw.WriteString("\n}\n")
	bufw.WriteString(footer)
	bufw.Flush()
}

func IsNotExist(file string) bool {
	_, err := os.Stat(file)
	return os.IsNotExist(err)
}

var header = `package geo

import (
	"bytes"
	"compress/zlib"
	"fmt"
	"io"
)

func buildGeoDB() ([]byte, []byte, []byte) {
`

var footer = `
func decompress(b []byte, lens int) []byte {
	zr, e := zlib.NewReader(bytes.NewReader(b))
	if e != nil {
		panic(e)
	}
	var nw int
	w := make([]byte, lens)
	for nw < lens {
		n, e := zr.Read(w[nw:])
		nw += n
		if e != nil {
			if e == io.EOF {
				break
			}
			panic(e)
		}
	}
	if nw != lens {
		panic(fmt.Errorf("expected len=%d but read len=%d", lens, nw))
	}
	return w
}
`
<|endoftext|>


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

package prometheus

import (
	"errors"
	"fmt"
	"github.com/openconfig/gnmi-gateway/gateway/configuration"
	"github.com/openconfig/gnmi-gateway/gateway/openconfig"
	"github.com/openconfig/gnmi/cache"
	"github.com/openconfig/gnmi/ctree"
	gnmipb "github.com/openconfig/gnmi/proto/gnmi"
	prom "github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"strings"
)

func NewPrometheusExporter(config *configuration.GatewayConfig) *PrometheusExporter {
	return &PrometheusExporter{
		config:     config,
		deltaCalc:  NewDeltaCalculator(),
		metrics:    make(map[Hash]prom.Metric),
		typeLookup: new(openconfig.TypeLookup),
	}
}

type PrometheusExporter struct {
	config     *configuration.GatewayConfig
	cache      *cache.Cache
	deltaCalc  *DeltaCalculator
	metrics    map[Hash]prom.Metric
	typeLookup *openconfig.TypeLookup
}

func (e *PrometheusExporter) Export(leaf *ctree.Leaf) {
	notification := leaf.Value().(*gnmipb.Notification)
	for _, update := range notification.Update {
		value, isNumber := GetNumberValues(update.Val)
		if !isNumber {
			continue
		}
		metricName, labels := UpdateToMetricNameAndLabels(update)
		metricHash := NewStringMapHash(metricName, labels)

		metric, exists := e.metrics[metricHash]
		if !exists {
			var path []string
			for _, elem := range update.Path.Elem {
				path = append(path, elem.Name)
			}
			metricType := e.typeLookup.GetTypeByPath(path)

			switch metricType {
			case "counter64":
				metric = promauto.NewCounter(prom.CounterOpts{
					Name:        metricName,
					ConstLabels: labels,
				})
			case "gauge32":
			default:
				metric = promauto.NewGauge(prom.GaugeOpts{
					Name:        metricName,
					ConstLabels: labels,
				})
			}
			e.metrics[metricHash] = metric
		}

		switch m := metric.(type) {
		case prom.Counter:
			delta, _ := e.deltaCalc.Calc(metricHash, value)
			m.Add(delta)
		case prom.Gauge:
			m.Set(value)
		}
	}
}

func (e *PrometheusExporter) Start(cache *cache.Cache) error {
	e.config.Log.Info().Msg("Starting Prometheus exporter.")
	if e.config.OpenConfigDirectory == "" {
		return errors.New("value is not set for OpenConfigDirectory configuration")
	}
	e.cache = cache
	err := e.typeLookup.LoadAllModules(e.config.OpenConfigDirectory)
	if err != nil {
		e.config.Log.Error().Err(err).Msgf("Unable to load OpenConfig modules in %s: %v", e.config.OpenConfigDirectory, err)
		return err
	}

	e.config.Log.Info().Msg("Starting Prometheus exporter.")
	e.cache.SetClient(e.Export)
	go e.runHttpServer()
	return nil
}

func (e *PrometheusExporter) runHttpServer() {
	var errCount = 0
	var lastError error
	for {
		e.config.Log.Info().Msg("Starting Prometheus HTTP server.")
		http.Handle("/metrics", promhttp.Handler())
		err := http.ListenAndServe(":59100", nil)
		if err != nil {
			e.config.Log.Error().Err(err).Msgf("Prometheus HTTP server stopped with an error: %v", err)
			if err.Error() == lastError.Error() {
				errCount = errCount + 1
				if errCount >= 3 {
					panic(fmt.Errorf("too many errors returned by Prometheus HTTP server: %s", err.Error()))
				}
			} else {
				errCount = 0
				lastError = err
			}
		}
	}
}

func GetNumberValues(tv *gnmipb.TypedValue) (float64, bool) {
	if tv != nil && tv.Value != nil {
		switch tv.Value.(type) {
		case *gnmipb.TypedValue_StringVal:
			return 0, false
		case *gnmipb.TypedValue_IntVal:
			return float64(tv.GetIntVal()), true
		case *gnmipb.TypedValue_UintVal:
			return float64(tv.GetUintVal()), true
		case *gnmipb.TypedValue_BoolVal:
			if tv.GetBoolVal() {
				return 1, true
			} else {
				return 0, false
			}
		case *gnmipb.TypedValue_FloatVal:
			return float64(tv.GetFloatVal()), true
		case *gnmipb.TypedValue_LeaflistVal:
			return 0, false
		case *gnmipb.TypedValue_BytesVal:
			return 0, false
		default:
			return 0, false
		}
	}
	return 0, false
}

func UpdateToMetricNameAndLabels(update *gnmipb.Update) (string, map[string]string) {
	metricName := ""
	labels := make(map[string]string)
	for _, elem := range update.Path.Elem {
		elemName := strings.ReplaceAll(elem.Name, "-", "_")
		if metricName == "" {
			metricName = elemName
		} else {
			metricName = metricName + "_" + elemName
		}

		for key, value := range elem.Key {
			labelKey := metricName + "_" + strings.ReplaceAll(key, "-", "_")
			labels[labelKey] = value
		}
	}
	return metricName, labels
}
<commit_msg>Add target label to Prometheus metrics.<commit_after>// Copyright 2020 Netflix Inc
// Author: Colin McIntosh (colin@netflix.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prometheus

import (
	"errors"
	"fmt"
	"github.com/openconfig/gnmi-gateway/gateway/configuration"
	"github.com/openconfig/gnmi-gateway/gateway/openconfig"
	"github.com/openconfig/gnmi/cache"
	"github.com/openconfig/gnmi/ctree"
	gnmipb "github.com/openconfig/gnmi/proto/gnmi"
	prom "github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"strings"
)

func NewPrometheusExporter(config *configuration.GatewayConfig) *PrometheusExporter {
	return &PrometheusExporter{
		config:     config,
		deltaCalc:  NewDeltaCalculator(),
		metrics:    make(map[Hash]prom.Metric),
		typeLookup: new(openconfig.TypeLookup),
	}
}

type PrometheusExporter struct {
	config     *configuration.GatewayConfig
	cache      *cache.Cache
	deltaCalc  *DeltaCalculator
	metrics    map[Hash]prom.Metric
	typeLookup *openconfig.TypeLookup
}

func (e *PrometheusExporter) Export(leaf *ctree.Leaf) {
	notification := leaf.Value().(*gnmipb.Notification)
	for _, update := range notification.Update {
		value, isNumber := GetNumberValues(update.Val)
		if !isNumber {
			continue
		}
		metricName, labels := UpdateToMetricNameAndLabels(notification.GetPrefix(), update)
		metricHash := NewStringMapHash(metricName, labels)

		metric, exists := e.metrics[metricHash]
		if !exists {
			var path []string
			for _, elem := range update.Path.Elem {
				path = append(path, elem.Name)
			}
			metricType := e.typeLookup.GetTypeByPath(path)

			switch metricType {
			case "counter64":
				metric = promauto.NewCounter(prom.CounterOpts{
					Name:        metricName,
					ConstLabels: labels,
				})
			case "gauge32":
			default:
				metric = promauto.NewGauge(prom.GaugeOpts{
					Name:        metricName,
					ConstLabels: labels,
				})
			}
			e.metrics[metricHash] = metric
		}

		switch m := metric.(type) {
		case prom.Counter:
			delta, _ := e.deltaCalc.Calc(metricHash, value)
			m.Add(delta)
		case prom.Gauge:
			m.Set(value)
		}
	}
}

func (e *PrometheusExporter) Start(cache *cache.Cache) error {
	e.config.Log.Info().Msg("Starting Prometheus exporter.")
	if e.config.OpenConfigDirectory == "" {
		return errors.New("value is not set for OpenConfigDirectory configuration")
	}
	e.cache = cache
	err := e.typeLookup.LoadAllModules(e.config.OpenConfigDirectory)
	if err != nil {
		e.config.Log.Error().Err(err).Msgf("Unable to load OpenConfig modules in %s: %v", e.config.OpenConfigDirectory, err)
		return err
	}

	e.config.Log.Info().Msg("Starting Prometheus exporter.")
	e.cache.SetClient(e.Export)
	go e.runHttpServer()
	return nil
}

func (e *PrometheusExporter) runHttpServer() {
	var errCount = 0
	var lastError error
	for {
		e.config.Log.Info().Msg("Starting Prometheus HTTP server.")
		http.Handle("/metrics", promhttp.Handler())
		err := http.ListenAndServe(":59100", nil)
		if err != nil {
			e.config.Log.Error().Err(err).Msgf("Prometheus HTTP server stopped with an error: %v", err)
			if err.Error() == lastError.Error() {
				errCount = errCount + 1
				if errCount >= 3 {
					panic(fmt.Errorf("too many errors returned by Prometheus HTTP server: %s", err.Error()))
				}
			} else {
				errCount = 0
				lastError = err
			}
		}
	}
}

func GetNumberValues(tv *gnmipb.TypedValue) (float64, bool) {
	if tv != nil && tv.Value != nil {
		switch tv.Value.(type) {
		case *gnmipb.TypedValue_StringVal:
			return 0, false
		case *gnmipb.TypedValue_IntVal:
			return float64(tv.GetIntVal()), true
		case *gnmipb.TypedValue_UintVal:
			return float64(tv.GetUintVal()), true
		case *gnmipb.TypedValue_BoolVal:
			if tv.GetBoolVal() {
				return 1, true
			} else {
				return 0, false
			}
		case *gnmipb.TypedValue_FloatVal:
			return float64(tv.GetFloatVal()), true
		case *gnmipb.TypedValue_LeaflistVal:
			return 0, false
		case *gnmipb.TypedValue_BytesVal:
			return 0, false
		default:
			return 0, false
		}
	}
	return 0, false
}

func UpdateToMetricNameAndLabels(prefix *gnmipb.Path, update *gnmipb.Update) (string, map[string]string) {
	metricName := ""
	labels := make(map[string]string)

	if prefix != nil {
		target := prefix.GetTarget()
		if target != "" {
			labels["target"] = target
		}
	}

	for _, elem := range update.Path.Elem {
		elemName := strings.ReplaceAll(elem.Name, "-", "_")
		if metricName == "" {
			metricName = elemName
		} else {
			metricName = metricName + "_" + elemName
		}

		for key, value := range elem.Key {
			labelKey := metricName + "_" + strings.ReplaceAll(key, "-", "_")
			labels[labelKey] = value
		}
	}
	return metricName, labels
}
<|endoftext|>


<commit_before>// Package pomf provides a simple file hosting pomf compatible web application
packagep pomf

import (
	"crypto/sha1"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"time"

	// random string generation package
	"github.com/dchest/uniuri"
	// mysql database package
	_ "github.com/go-sql-driver/mysql"
)

const (
	// LENGTH is used to specify filename length
	LENGTH = 6
	// PORT is the port pomf will listen on
	PORT = ":8080"
	// UPDIRECTORY specifies what directory pomf will upload to
	UPDIRECTORY = "/tmp/"
	// GRILLDIRECTORY directory for Kawaii anime grills
	GRILLDIRECTORY = "pomf/build/img/"
	// POMFDIRECTORY is the directory for static pomf files
	POMFDIRECTORY = "pomf/build"
	// UPADDRESS Domain to serve static files from
	UPADDRESS = "http://localhost"
	// dbUSERNAME Database username
	dbUSERNAME = ""
	// dbNAME database name
	dbNAME = ""
	// dbPASSWORD database password
	dbPASSWORD = ""
	// DATABASE connection constant
	DATABASE = dbUSERNAME + ":" + dbPASSWORD + "@/" + dbNAME + "?charset=utf8"
	// MAXSIZE in bytes
	MAXSIZE = 10 * 1024 * 1024
)

// Result information struct
type Result struct {
	URL  string `json:"url"`
	Name string `json:"name"`
	Hash string `json:"hash"`
	Size int64  `json:"size"`
}

// Response Pomf compatible Response struct
type Response struct {
	Success     bool     `json:"success"`
	ErrorCode   int      `json:"errorcode,omitempty"`
	Description string   `json:"description,omitempty"`
	Files       []Result `json:"files,omitempty"`
}

// generateName returns a random string that isn't in the database
func generateName() (string, error) {
	// generate a random string
	name := uniuri.NewLen(LENGTH)
	// open database connection
	db, err := sql.Open("mysql", DATABASE)
	if err != nil {
		// return error and empty string
		return "", err
	}

	var id string
	// Query database for randomly generated string
	err = db.QueryRow("select id from files where id=?", name).Scan(&id)
	// if string doesn't exist call generateName again
	if err != sql.ErrNoRows {
		generateName()
	}
	// close database connection
	db.Close()

	// return randomly generated string and no error
	return name, nil
}

// respond outputs Response struct in user specified formats
// supported formats are xml, json, text and html
func respond(w http.ResponseWriter, output string, resp Response) {
	if resp.ErrorCode != 0 {
		resp.Files = []Result{}
		resp.Success = false
	} else {
		resp.Success = true
	}

	switch output {
	case "json":

		w.Header().Set("Content-Type", "application/json")
		err := json.NewEncoder(w).Encode(resp)

		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	case "xml":
		x, err := xml.MarshalIndent(resp, "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/xml")
		w.Write(x)

	case "html":
		w.Header().Set("Content-Type", "text/html")
		for _, file := range resp.Files {
			io.WriteString(w, ""+file.URL+"
")
		}

	case "gyazo", "text":
		w.Header().Set("Content-Type", "plain/text")
		for _, file := range resp.Files {
			io.WriteString(w, file.URL+"\n")
		}

	case "csv":
		w.Header().Set("Content-Type", "text/csv")
		io.WriteString(w, "name, url, hash, size\n")
		for _, file := range resp.Files {
			io.WriteString(w, file.Name+","+file.URL+","+file.Hash+","+strconv.FormatInt(file.Size, 10)+"\n")
		}

	default:
		w.Header().Set("Content-Type", "application/json")
		err := json.NewEncoder(w).Encode(resp)

		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

	}

}

// grillHandler randomly selects a kawaii grill
func grillHandler(w http.ResponseWriter, r *http.Request) {
	// read kawaii grill directory
	kawaii, err := ioutil.ReadDir(GRILLDIRECTORY)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	// redirect to a randomly selected kawaii grill
	http.Redirect(w, r, "/img/"+kawaii[rand.Intn(len(kawaii))].Name(), http.StatusFound)
}

// uploadHandler constructs the response struct
func uploadHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	output := r.FormValue("output")
	reader, err := r.MultipartReader()
	resp := Response{Files: []Result{}}
	if err != nil {
		resp.ErrorCode = http.StatusInternalServerError
		resp.Description = err.Error()
		respond(w, output, resp)
		return
	}

	db, err := sql.Open("mysql", DATABASE)
	if err != nil {
		resp.ErrorCode = http.StatusInternalServerError
		resp.Description = err.Error()
		return
	}

	for {
		part, err := reader.NextPart()
		if err == io.EOF {
			break
		}

		if part.FileName() == "" {
			continue
		}

		// generate random filename
		s, err := generateName()
		if err != nil {
			resp.ErrorCode = http.StatusInternalServerError
			resp.Description = err.Error()
			break
		}
		// get file extension
		extName := filepath.Ext(part.FileName())
		// create new filename with random name and extension
		filename := s + extName
		// create a new file ready for user to upload to
		dst, err := os.Create(UPDIRECTORY + filename)
		if err != nil {
			resp.ErrorCode = http.StatusInternalServerError
			resp.Description = err.Error()
			respond(w, output, resp)
			break
		}
		defer dst.Close()

		// Prepare sha1 hash
		h := sha1.New()

		// async copy uploaded data to be hashed
		t := io.TeeReader(part, h)

		// save uploaded data to created file
		_, err = io.Copy(dst, t)
		if err != nil {
			resp.ErrorCode = http.StatusInternalServerError
			resp.Description = err.Error()
			respond(w, output, resp)
			break
		}
		// hash data
		hash := h.Sum(nil)
		// convert data to human readable format
		sha1 := base64.URLEncoding.EncodeToString(hash)
		stat, _ := dst.Stat()
		// get filesize
		size := stat.Size()

		// check to see if filesize is larger than MAXSIZE
		if size > MAXSIZE {
			resp.ErrorCode = http.StatusRequestEntityTooLarge
			resp.Description = err.Error()
			break
		}

		// save original name
		originalname := part.FileName()
		// query database to see if file exists
		err = db.QueryRow("select originalname, filename, size from files where hash=?", sha1).Scan(&originalname, &filename, &size)
		// prepare Result struct
		res := Result{
			URL:  UPADDRESS + "/" + filename,
			Name: originalname,
			Hash: sha1,
			Size: size,
		}
		// if file does not exist insert data into the files table
		if err == sql.ErrNoRows {
			// prepare statement
			query, err := db.Prepare("INSERT into files(hash, originalname, filename, size, date) values(?, ?, ?, ?, ?)")
			if err != nil {
				resp.ErrorCode = http.StatusInternalServerError
				resp.Description = err.Error()
				break
			}
			// execute statement with all necessary variables
			_, err = query.Exec(res.Hash, res.Name, filename, res.Size, time.Now().Format("2016-01-02"))
			if err != nil {
				resp.ErrorCode = http.StatusInternalServerError
				resp.Description = err.Error()
				break
			}
		}
		// append file to response struct
		resp.Files = append(resp.Files, res)
	}
	// call repond function
	respond(w, output, resp)
}

func main() {
	http.HandleFunc("/upload.php", uploadHandler)
	http.HandleFunc("/grill.php", grillHandler)
	http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(POMFDIRECTORY))))
	err := http.ListenAndServe(PORT, nil)
	if err != nil {
		panic(err)
	}

}
<commit_msg>Rename package<commit_after>// Package pomf provides a simple file hosting pomf compatible web application
package pomf

import (
	"crypto/sha1"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"time"

	// random string generation package
	"github.com/dchest/uniuri"
	// mysql database package
	_ "github.com/go-sql-driver/mysql"
)

const (
	// LENGTH is used to specify filename length
	LENGTH = 6
	// PORT is the port pomf will listen on
	PORT = ":8080"
	// UPDIRECTORY specifies what directory pomf will upload to
	UPDIRECTORY = "/tmp/"
	// GRILLDIRECTORY directory for Kawaii anime grills
	GRILLDIRECTORY = "pomf/build/img/"
	// POMFDIRECTORY is the directory for static pomf files
	POMFDIRECTORY = "pomf/build"
	// UPADDRESS Domain to serve static files from
	UPADDRESS = "http://localhost"
	// dbUSERNAME Database username
	dbUSERNAME = ""
	// dbNAME database name
	dbNAME = ""
	// dbPASSWORD database password
	dbPASSWORD = ""
	// DATABASE connection constant
	DATABASE = dbUSERNAME + ":" + dbPASSWORD + "@/" + dbNAME + "?charset=utf8"
	// MAXSIZE in bytes
	MAXSIZE = 10 * 1024 * 1024
)

// Result information struct
type Result struct {
	URL  string `json:"url"`
	Name string `json:"name"`
	Hash string `json:"hash"`
	Size int64  `json:"size"`
}

// Response Pomf compatible Response struct
type Response struct {
	Success     bool     `json:"success"`
	ErrorCode   int      `json:"errorcode,omitempty"`
	Description string   `json:"description,omitempty"`
	Files       []Result `json:"files,omitempty"`
}

// generateName returns a random string that isn't in the database
func generateName() (string, error) {
	// generate a random string
	name := uniuri.NewLen(LENGTH)
	// open database connection
	db, err := sql.Open("mysql", DATABASE)
	if err != nil {
		// return error and empty string
		return "", err
	}

	var id string
	// Query database for randomly generated string
	err = db.QueryRow("select id from files where id=?", name).Scan(&id)
	// if string doesn't exist call generateName again
	if err != sql.ErrNoRows {
		generateName()
	}
	// close database connection
	db.Close()

	// return randomly generated string and no error
	return name, nil
}

// respond outputs Response struct in user specified formats
// supported formats are xml, json, text and html
func respond(w http.ResponseWriter, output string, resp Response) {
	if resp.ErrorCode != 0 {
		resp.Files = []Result{}
		resp.Success = false
	} else {
		resp.Success = true
	}

	switch output {
	case "json":

		w.Header().Set("Content-Type", "application/json")
		err := json.NewEncoder(w).Encode(resp)

		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	case "xml":
		x, err := xml.MarshalIndent(resp, "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/xml")
		w.Write(x)

	case "html":
		w.Header().Set("Content-Type", "text/html")
		for _, file := range resp.Files {
			io.WriteString(w, ""+file.URL+"
")
		}

	case "gyazo", "text":
		w.Header().Set("Content-Type", "plain/text")
		for _, file := range resp.Files {
			io.WriteString(w, file.URL+"\n")
		}

	case "csv":
		w.Header().Set("Content-Type", "text/csv")
		io.WriteString(w, "name, url, hash, size\n")
		for _, file := range resp.Files {
			io.WriteString(w, file.Name+","+file.URL+","+file.Hash+","+strconv.FormatInt(file.Size, 10)+"\n")
		}

	default:
		w.Header().Set("Content-Type", "application/json")
		err := json.NewEncoder(w).Encode(resp)

		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

	}

}

// grillHandler randomly selects a kawaii grill
func grillHandler(w http.ResponseWriter, r *http.Request) {
	// read kawaii grill directory
	kawaii, err := ioutil.ReadDir(GRILLDIRECTORY)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	// redirect to a randomly selected kawaii grill
	http.Redirect(w, r, "/img/"+kawaii[rand.Intn(len(kawaii))].Name(), http.StatusFound)
}

// uploadHandler constructs the response struct
func uploadHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	output := r.FormValue("output")
	reader, err := r.MultipartReader()
	resp := Response{Files: []Result{}}
	if err != nil {
		resp.ErrorCode = http.StatusInternalServerError
		resp.Description = err.Error()
		respond(w, output, resp)
		return
	}

	db, err := sql.Open("mysql", DATABASE)
	if err != nil {
		resp.ErrorCode = http.StatusInternalServerError
		resp.Description = err.Error()
		return
	}

	for {
		part, err := reader.NextPart()
		if err == io.EOF {
			break
		}

		if part.FileName() == "" {
			continue
		}

		// generate random filename
		s, err := generateName()
		if err != nil {
			resp.ErrorCode = http.StatusInternalServerError
			resp.Description = err.Error()
			break
		}
		// get file extension
		extName := filepath.Ext(part.FileName())
		// create new filename with random name and extension
		filename := s + extName
		// create a new file ready for user to upload to
		dst, err := os.Create(UPDIRECTORY + filename)
		if err != nil {
			resp.ErrorCode = http.StatusInternalServerError
			resp.Description = err.Error()
			respond(w, output, resp)
			break
		}
		defer dst.Close()

		// Prepare sha1 hash
		h := sha1.New()

		// async copy uploaded data to be hashed
		t := io.TeeReader(part, h)

		// save uploaded data to created file
		_, err = io.Copy(dst, t)
		if err != nil {
			resp.ErrorCode = http.StatusInternalServerError
			resp.Description = err.Error()
			respond(w, output, resp)
			break
		}
		// hash data
		hash := h.Sum(nil)
		// convert data to human readable format
		sha1 := base64.URLEncoding.EncodeToString(hash)
		stat, _ := dst.Stat()
		// get filesize
		size := stat.Size()

		// check to see if filesize is larger than MAXSIZE
		if size > MAXSIZE {
			resp.ErrorCode = http.StatusRequestEntityTooLarge
			resp.Description = err.Error()
			break
		}

		// save original name
		originalname := part.FileName()
		// query database to see if file exists
		err = db.QueryRow("select originalname, filename, size from files where hash=?", sha1).Scan(&originalname, &filename, &size)
		// prepare Result struct
		res := Result{
			URL:  UPADDRESS + "/" + filename,
			Name: originalname,
			Hash: sha1,
			Size: size,
		}
		// if file does not exist insert data into the files table
		if err == sql.ErrNoRows {
			// prepare statement
			query, err := db.Prepare("INSERT into files(hash, originalname, filename, size, date) values(?, ?, ?, ?, ?)")
			if err != nil {
				resp.ErrorCode = http.StatusInternalServerError
				resp.Description = err.Error()
				break
			}
			// execute statement with all necessary variables
			_, err = query.Exec(res.Hash, res.Name, filename, res.Size, time.Now().Format("2016-01-02"))
			if err != nil {
				resp.ErrorCode = http.StatusInternalServerError
				resp.Description = err.Error()
				break
			}
		}
		// append file to response struct
		resp.Files = append(resp.Files, res)
	}
	// call repond function
	respond(w, output, resp)
}

func main() {
	http.HandleFunc("/upload.php", uploadHandler)
	http.HandleFunc("/grill.php", grillHandler)
	http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(POMFDIRECTORY))))
	err := http.ListenAndServe(PORT, nil)
	if err != nil {
		panic(err)
	}

}
<|endoftext|>


<commit_before>package puddle

import (
	"context"
	"errors"
	"sync"
	"time"
)

const (
	resourceStatusConstructing = 0
	resourceStatusIdle         = iota
	resourceStatusAcquired     = iota
	resourceStatusHijacked     = iota
)

// ErrClosedPool occurs on an attempt to acquire a connection from a closed pool
// or a pool that is closed while the acquire is waiting.
var ErrClosedPool = errors.New("closed pool")

// Constructor is a function called by the pool to construct a resource.
type Constructor func(ctx context.Context) (res interface{}, err error)

// Destructor is a function called by the pool to destroy a resource.
type Destructor func(res interface{})

// Resource is the resource handle returned by acquiring from the pool.
type Resource struct {
	value        interface{}
	pool         *Pool
	creationTime time.Time
	lastUsedNano int64
	status       byte
}

// Value returns the resource value.
func (res *Resource) Value() interface{} {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.value
}

// Release returns the resource to the pool. res must not be subsequently used.
func (res *Resource) Release() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, nanotime())
}

// ReleaseUnused returns the resource to the pool without updating when it was last used used. i.e. LastUsedNanotime
// will not change. res must not be subsequently used.
func (res *Resource) ReleaseUnused() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, res.lastUsedNano)
}

// Destroy returns the resource to the pool for destruction. res must not be
// subsequently used.
func (res *Resource) Destroy() {
	if res.status != resourceStatusAcquired {
		panic("tried to destroy resource that is not acquired")
	}
	res.pool.destroyAcquiredResource(res)
}

// Hijack assumes ownership of the resource from the pool. Caller is responsible
// for cleanup of resource value.
func (res *Resource) Hijack() {
	if res.status != resourceStatusAcquired {
		panic("tried to hijack resource that is not acquired")
	}
	res.pool.hijackAcquiredResource(res)
}

// CreationTime returns when the resource was created by the pool.
func (res *Resource) CreationTime() time.Time {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.creationTime
}

// LastUsedNanotime returns when Release was last called on the resource measured in nanoseconds from an arbitrary time
// (a monotonic time). Returns creation time if Release has never been called. This is only useful to compare with
// other calls to LastUsedNanotime. In almost all cases, IdleDuration should be used instead.
func (res *Resource) LastUsedNanotime() int64 {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return res.lastUsedNano
}

// IdleDuration returns the duration since Release was last called on the resource. This is equivalent to subtracting
// LastUsedNanotime to the current nanotime.
func (res *Resource) IdleDuration() time.Duration {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return time.Duration(nanotime() - res.lastUsedNano)
}

// Pool is a concurrency-safe resource pool.
type Pool struct {
	cond       *sync.Cond
	destructWG *sync.WaitGroup

	allResources  []*Resource
	idleResources []*Resource

	constructor Constructor
	destructor  Destructor
	maxSize     int32

	acquireCount         int64
	acquireDuration      time.Duration
	emptyAcquireCount    int64
	canceledAcquireCount int64

	closed bool
}

// NewPool creates a new pool. Panics if maxSize is less than 1.
func NewPool(constructor Constructor, destructor Destructor, maxSize int32) *Pool {
	if maxSize < 1 {
		panic("maxSize is less than 1")
	}

	return &Pool{
		cond:        sync.NewCond(new(sync.Mutex)),
		destructWG:  &sync.WaitGroup{},
		maxSize:     maxSize,
		constructor: constructor,
		destructor:  destructor,
	}
}

// Close destroys all resources in the pool and rejects future Acquire calls.
// Blocks until all resources are returned to pool and destroyed.
func (p *Pool) Close() {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return
	}
	p.closed = true

	for _, res := range p.idleResources {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}
	p.idleResources = nil
	p.cond.L.Unlock()

	// Wake up all go routines waiting for a resource to be returned so they can terminate.
	p.cond.Broadcast()

	p.destructWG.Wait()
}

// Stat is a snapshot of Pool statistics.
type Stat struct {
	constructingResources int32
	acquiredResources     int32
	idleResources         int32
	maxResources          int32
	acquireCount          int64
	acquireDuration       time.Duration
	emptyAcquireCount     int64
	canceledAcquireCount  int64
}

// TotalResource returns the total number of resources.
func (s *Stat) TotalResources() int32 {
	return s.constructingResources + s.acquiredResources + s.idleResources
}

// ConstructingResources returns the number of resources with construction in progress in
// the pool.
func (s *Stat) ConstructingResources() int32 {
	return s.constructingResources
}

// AcquiredResources returns the number of acquired resources in the pool.
func (s *Stat) AcquiredResources() int32 {
	return s.acquiredResources
}

// IdleResources returns the number of idle resources in the pool.
func (s *Stat) IdleResources() int32 {
	return s.idleResources
}

// MaxResources returns the maximum size of the pool.
func (s *Stat) MaxResources() int32 {
	return s.maxResources
}

// AcquireCount returns the number of successful acquires from the pool.
func (s *Stat) AcquireCount() int64 {
	return s.acquireCount
}

// AcquireDuration returns the total duration of all successful acquires from
// the pool.
func (s *Stat) AcquireDuration() time.Duration {
	return s.acquireDuration
}

// EmptyAcquireCount returns the number of successful acquires from the pool
// that waited for a resource to be released or constructed because the pool was
// empty.
func (s *Stat) EmptyAcquireCount() int64 {
	return s.emptyAcquireCount
}

// CanceledAcquireCount returns the number of acquires from the pool
// that were canceled by a context.
func (s *Stat) CanceledAcquireCount() int64 {
	return s.canceledAcquireCount
}

// Stat returns the current pool statistics.
func (p *Pool) Stat() *Stat {
	p.cond.L.Lock()
	s := &Stat{
		maxResources:         p.maxSize,
		acquireCount:         p.acquireCount,
		emptyAcquireCount:    p.emptyAcquireCount,
		canceledAcquireCount: p.canceledAcquireCount,
		acquireDuration:      p.acquireDuration,
	}

	for _, res := range p.allResources {
		switch res.status {
		case resourceStatusConstructing:
			s.constructingResources += 1
		case resourceStatusIdle:
			s.idleResources += 1
		case resourceStatusAcquired:
			s.acquiredResources += 1
		}
	}

	p.cond.L.Unlock()
	return s
}

// Acquire gets a resource from the pool. If no resources are available and the pool
// is not at maximum capacity it will create a new resource. If the pool is at
// maximum capacity it will block until a resource is available. ctx can be used
// to cancel the Acquire.
func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
	startNano := nanotime()
	p.cond.L.Lock()
	if doneChan := ctx.Done(); doneChan != nil {
		select {
		case <-ctx.Done():
			p.canceledAcquireCount += 1
			p.cond.L.Unlock()
			return nil, ctx.Err()
		default:
		}
	}

	emptyAcquire := false

	for {
		if p.closed {
			p.cond.L.Unlock()
			return nil, ErrClosedPool
		}

		// If a resource is available now
		if len(p.idleResources) > 0 {
			res := p.idleResources[len(p.idleResources)-1]
			p.idleResources = p.idleResources[:len(p.idleResources)-1]
			res.status = resourceStatusAcquired
			if emptyAcquire {
				p.emptyAcquireCount += 1
			}
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		emptyAcquire = true

		// If there is room to create a resource do so
		if len(p.allResources) < int(p.maxSize) {
			res := &Resource{pool: p, creationTime: time.Now(), lastUsedNano: nanotime(), status: resourceStatusConstructing}
			p.allResources = append(p.allResources, res)
			p.destructWG.Add(1)
			p.cond.L.Unlock()

			value, err := p.constructResourceValue(ctx)
			p.cond.L.Lock()
			if err != nil {
				p.allResources = removeResource(p.allResources, res)
				p.destructWG.Done()

				select {
				case <-ctx.Done():
					if err == ctx.Err() {
						p.canceledAcquireCount += 1
					}
				default:
				}

				p.cond.L.Unlock()
				return nil, err
			}

			res.value = value
			res.status = resourceStatusAcquired
			p.emptyAcquireCount += 1
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		if ctx.Done() == nil {
			p.cond.Wait()
		} else {
			// Convert p.cond.Wait into a channel
			waitChan := make(chan struct{}, 1)
			go func() {
				p.cond.Wait()
				waitChan <- struct{}{}
			}()

			select {
			case <-ctx.Done():
				// Allow goroutine waiting for signal to exit. Re-signal since we couldn't
				// do anything with it. Another goroutine might be waiting.
				go func() {
					<-waitChan
					p.cond.Signal()
					p.cond.L.Unlock()
				}()

				p.cond.L.Lock()
				p.canceledAcquireCount += 1
				p.cond.L.Unlock()
				return nil, ctx.Err()
			case <-waitChan:
			}
		}
	}
}

// AcquireAllIdle atomically acquires all currently idle resources. Its intended
// use is for health check and keep-alive functionality. It does not update pool
// statistics.
func (p *Pool) AcquireAllIdle() []*Resource {
	p.cond.L.Lock()

	for _, res := range p.idleResources {
		res.status = resourceStatusAcquired
	}
	resources := make([]*Resource, len(p.idleResources))
	copy(resources, p.idleResources)
	p.idleResources = p.idleResources[0:0]

	p.cond.L.Unlock()
	return resources
}

// CreateResource constructs a new resource without acquiring it.
// It goes straight in the IdlePool. It does not check against maxSize.
// It can be useful to maintain warm resources under little load.
func (p *Pool) CreateResource(ctx context.Context) error {

	value, err := p.constructResourceValue(ctx)
	if err != nil {
		return err
	}

	res := &Resource{
		pool:         p,
		creationTime: time.Now(),
		status:       resourceStatusIdle,
		value:        value,
		lastUsedNano: nanotime(),
	}

	p.cond.L.Lock()
	p.allResources = append(p.allResources, res)
	p.idleResources = append(p.idleResources, res)
	p.destructWG.Add(1)
	p.cond.L.Unlock()

	return nil
}

// releaseAcquiredResource returns res to the the pool.
func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedNano int64) {
	p.cond.L.Lock()

	if !p.closed {
		res.lastUsedNano = lastUsedNano
		res.status = resourceStatusIdle
		p.idleResources = append(p.idleResources, res)
	} else {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}

	p.cond.L.Unlock()
	p.cond.Signal()
}

// Remove removes res from the pool and closes it. If res is not part of the
// pool Remove will panic.
func (p *Pool) destroyAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	go p.destructResourceValue(res.value)

	p.cond.L.Unlock()
	p.cond.Signal()
}

func (p *Pool) hijackAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	res.status = resourceStatusHijacked
	p.destructWG.Done() // not responsible for destructing hijacked resources

	p.cond.L.Unlock()
	p.cond.Signal()
}

func removeResource(slice []*Resource, res *Resource) []*Resource {
	for i := range slice {
		if slice[i] == res {
			slice[i] = slice[len(slice)-1]
			return slice[:len(slice)-1]
		}
	}

	panic("BUG: removeResource could not find res in slice")
}

func (p *Pool) constructResourceValue(ctx context.Context) (interface{}, error) {
	return p.constructor(ctx)
}

func (p *Pool) destructResourceValue(value interface{}) {
	p.destructor(value)
	p.destructWG.Done()
}
<commit_msg>AcquireAllIdle immediately returns nil if pool is closed<commit_after>package puddle

import (
	"context"
	"errors"
	"sync"
	"time"
)

const (
	resourceStatusConstructing = 0
	resourceStatusIdle         = iota
	resourceStatusAcquired     = iota
	resourceStatusHijacked     = iota
)

// ErrClosedPool occurs on an attempt to acquire a connection from a closed pool
// or a pool that is closed while the acquire is waiting.
var ErrClosedPool = errors.New("closed pool")

// Constructor is a function called by the pool to construct a resource.
type Constructor func(ctx context.Context) (res interface{}, err error)

// Destructor is a function called by the pool to destroy a resource.
type Destructor func(res interface{})

// Resource is the resource handle returned by acquiring from the pool.
type Resource struct {
	value        interface{}
	pool         *Pool
	creationTime time.Time
	lastUsedNano int64
	status       byte
}

// Value returns the resource value.
func (res *Resource) Value() interface{} {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.value
}

// Release returns the resource to the pool. res must not be subsequently used.
func (res *Resource) Release() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, nanotime())
}

// ReleaseUnused returns the resource to the pool without updating when it was last used used. i.e. LastUsedNanotime
// will not change. res must not be subsequently used.
func (res *Resource) ReleaseUnused() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, res.lastUsedNano)
}

// Destroy returns the resource to the pool for destruction. res must not be
// subsequently used.
func (res *Resource) Destroy() {
	if res.status != resourceStatusAcquired {
		panic("tried to destroy resource that is not acquired")
	}
	res.pool.destroyAcquiredResource(res)
}

// Hijack assumes ownership of the resource from the pool. Caller is responsible
// for cleanup of resource value.
func (res *Resource) Hijack() {
	if res.status != resourceStatusAcquired {
		panic("tried to hijack resource that is not acquired")
	}
	res.pool.hijackAcquiredResource(res)
}

// CreationTime returns when the resource was created by the pool.
func (res *Resource) CreationTime() time.Time {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.creationTime
}

// LastUsedNanotime returns when Release was last called on the resource measured in nanoseconds from an arbitrary time
// (a monotonic time). Returns creation time if Release has never been called. This is only useful to compare with
// other calls to LastUsedNanotime. In almost all cases, IdleDuration should be used instead.
func (res *Resource) LastUsedNanotime() int64 {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return res.lastUsedNano
}

// IdleDuration returns the duration since Release was last called on the resource. This is equivalent to subtracting
// LastUsedNanotime to the current nanotime.
func (res *Resource) IdleDuration() time.Duration {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return time.Duration(nanotime() - res.lastUsedNano)
}

// Pool is a concurrency-safe resource pool.
type Pool struct {
	cond       *sync.Cond
	destructWG *sync.WaitGroup

	allResources  []*Resource
	idleResources []*Resource

	constructor Constructor
	destructor  Destructor
	maxSize     int32

	acquireCount         int64
	acquireDuration      time.Duration
	emptyAcquireCount    int64
	canceledAcquireCount int64

	closed bool
}

// NewPool creates a new pool. Panics if maxSize is less than 1.
func NewPool(constructor Constructor, destructor Destructor, maxSize int32) *Pool {
	if maxSize < 1 {
		panic("maxSize is less than 1")
	}

	return &Pool{
		cond:        sync.NewCond(new(sync.Mutex)),
		destructWG:  &sync.WaitGroup{},
		maxSize:     maxSize,
		constructor: constructor,
		destructor:  destructor,
	}
}

// Close destroys all resources in the pool and rejects future Acquire calls.
// Blocks until all resources are returned to pool and destroyed.
func (p *Pool) Close() {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return
	}
	p.closed = true

	for _, res := range p.idleResources {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}
	p.idleResources = nil
	p.cond.L.Unlock()

	// Wake up all go routines waiting for a resource to be returned so they can terminate.
	p.cond.Broadcast()

	p.destructWG.Wait()
}

// Stat is a snapshot of Pool statistics.
type Stat struct {
	constructingResources int32
	acquiredResources     int32
	idleResources         int32
	maxResources          int32
	acquireCount          int64
	acquireDuration       time.Duration
	emptyAcquireCount     int64
	canceledAcquireCount  int64
}

// TotalResource returns the total number of resources.
func (s *Stat) TotalResources() int32 {
	return s.constructingResources + s.acquiredResources + s.idleResources
}

// ConstructingResources returns the number of resources with construction in progress in
// the pool.
func (s *Stat) ConstructingResources() int32 {
	return s.constructingResources
}

// AcquiredResources returns the number of acquired resources in the pool.
func (s *Stat) AcquiredResources() int32 {
	return s.acquiredResources
}

// IdleResources returns the number of idle resources in the pool.
func (s *Stat) IdleResources() int32 {
	return s.idleResources
}

// MaxResources returns the maximum size of the pool.
func (s *Stat) MaxResources() int32 {
	return s.maxResources
}

// AcquireCount returns the number of successful acquires from the pool.
func (s *Stat) AcquireCount() int64 {
	return s.acquireCount
}

// AcquireDuration returns the total duration of all successful acquires from
// the pool.
func (s *Stat) AcquireDuration() time.Duration {
	return s.acquireDuration
}

// EmptyAcquireCount returns the number of successful acquires from the pool
// that waited for a resource to be released or constructed because the pool was
// empty.
func (s *Stat) EmptyAcquireCount() int64 {
	return s.emptyAcquireCount
}

// CanceledAcquireCount returns the number of acquires from the pool
// that were canceled by a context.
func (s *Stat) CanceledAcquireCount() int64 {
	return s.canceledAcquireCount
}

// Stat returns the current pool statistics.
func (p *Pool) Stat() *Stat {
	p.cond.L.Lock()
	s := &Stat{
		maxResources:         p.maxSize,
		acquireCount:         p.acquireCount,
		emptyAcquireCount:    p.emptyAcquireCount,
		canceledAcquireCount: p.canceledAcquireCount,
		acquireDuration:      p.acquireDuration,
	}

	for _, res := range p.allResources {
		switch res.status {
		case resourceStatusConstructing:
			s.constructingResources += 1
		case resourceStatusIdle:
			s.idleResources += 1
		case resourceStatusAcquired:
			s.acquiredResources += 1
		}
	}

	p.cond.L.Unlock()
	return s
}

// Acquire gets a resource from the pool. If no resources are available and the pool
// is not at maximum capacity it will create a new resource. If the pool is at
// maximum capacity it will block until a resource is available. ctx can be used
// to cancel the Acquire.
func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
	startNano := nanotime()
	p.cond.L.Lock()
	if doneChan := ctx.Done(); doneChan != nil {
		select {
		case <-ctx.Done():
			p.canceledAcquireCount += 1
			p.cond.L.Unlock()
			return nil, ctx.Err()
		default:
		}
	}

	emptyAcquire := false

	for {
		if p.closed {
			p.cond.L.Unlock()
			return nil, ErrClosedPool
		}

		// If a resource is available now
		if len(p.idleResources) > 0 {
			res := p.idleResources[len(p.idleResources)-1]
			p.idleResources = p.idleResources[:len(p.idleResources)-1]
			res.status = resourceStatusAcquired
			if emptyAcquire {
				p.emptyAcquireCount += 1
			}
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		emptyAcquire = true

		// If there is room to create a resource do so
		if len(p.allResources) < int(p.maxSize) {
			res := &Resource{pool: p, creationTime: time.Now(), lastUsedNano: nanotime(), status: resourceStatusConstructing}
			p.allResources = append(p.allResources, res)
			p.destructWG.Add(1)
			p.cond.L.Unlock()

			value, err := p.constructResourceValue(ctx)
			p.cond.L.Lock()
			if err != nil {
				p.allResources = removeResource(p.allResources, res)
				p.destructWG.Done()

				select {
				case <-ctx.Done():
					if err == ctx.Err() {
						p.canceledAcquireCount += 1
					}
				default:
				}

				p.cond.L.Unlock()
				return nil, err
			}

			res.value = value
			res.status = resourceStatusAcquired
			p.emptyAcquireCount += 1
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		if ctx.Done() == nil {
			p.cond.Wait()
		} else {
			// Convert p.cond.Wait into a channel
			waitChan := make(chan struct{}, 1)
			go func() {
				p.cond.Wait()
				waitChan <- struct{}{}
			}()

			select {
			case <-ctx.Done():
				// Allow goroutine waiting for signal to exit. Re-signal since we couldn't
				// do anything with it. Another goroutine might be waiting.
				go func() {
					<-waitChan
					p.cond.Signal()
					p.cond.L.Unlock()
				}()

				p.cond.L.Lock()
				p.canceledAcquireCount += 1
				p.cond.L.Unlock()
				return nil, ctx.Err()
			case <-waitChan:
			}
		}
	}
}

// AcquireAllIdle atomically acquires all currently idle resources. Its intended
// use is for health check and keep-alive functionality. It does not update pool
// statistics.
func (p *Pool) AcquireAllIdle() []*Resource {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return nil
	}

	for _, res := range p.idleResources {
		res.status = resourceStatusAcquired
	}
	resources := make([]*Resource, len(p.idleResources))
	copy(resources, p.idleResources)
	p.idleResources = p.idleResources[0:0]

	p.cond.L.Unlock()
	return resources
}

// CreateResource constructs a new resource without acquiring it.
// It goes straight in the IdlePool. It does not check against maxSize.
// It can be useful to maintain warm resources under little load.
func (p *Pool) CreateResource(ctx context.Context) error {

	value, err := p.constructResourceValue(ctx)
	if err != nil {
		return err
	}

	res := &Resource{
		pool:         p,
		creationTime: time.Now(),
		status:       resourceStatusIdle,
		value:        value,
		lastUsedNano: nanotime(),
	}

	p.cond.L.Lock()
	p.allResources = append(p.allResources, res)
	p.idleResources = append(p.idleResources, res)
	p.destructWG.Add(1)
	p.cond.L.Unlock()

	return nil
}

// releaseAcquiredResource returns res to the the pool.
func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedNano int64) {
	p.cond.L.Lock()

	if !p.closed {
		res.lastUsedNano = lastUsedNano
		res.status = resourceStatusIdle
		p.idleResources = append(p.idleResources, res)
	} else {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}

	p.cond.L.Unlock()
	p.cond.Signal()
}

// Remove removes res from the pool and closes it. If res is not part of the
// pool Remove will panic.
func (p *Pool) destroyAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	go p.destructResourceValue(res.value)

	p.cond.L.Unlock()
	p.cond.Signal()
}

func (p *Pool) hijackAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	res.status = resourceStatusHijacked
	p.destructWG.Done() // not responsible for destructing hijacked resources

	p.cond.L.Unlock()
	p.cond.Signal()
}

func removeResource(slice []*Resource, res *Resource) []*Resource {
	for i := range slice {
		if slice[i] == res {
			slice[i] = slice[len(slice)-1]
			return slice[:len(slice)-1]
		}
	}

	panic("BUG: removeResource could not find res in slice")
}

func (p *Pool) constructResourceValue(ctx context.Context) (interface{}, error) {
	return p.constructor(ctx)
}

func (p *Pool) destructResourceValue(value interface{}) {
	p.destructor(value)
	p.destructWG.Done()
}
<|endoftext|>


<commit_before>// A generic resource pool for databases etc
package pool

import (
	"errors"
	"io"
	"time"
)

var (
	TimeoutError    = errors.New("Timeout")
	PoolClosedError = errors.New("Pool is closed")
)

// ResourceOpener opens a resource
type ResourceOpener interface {
	Open() (Resource, error)
}

type Resource interface {
	io.Closer
	// Good returns true when the resource is in good state
	Good() bool
}

type PooledResource interface {
	// Release releases the resource back to the pool for reuse
	Release() error
	// Destroy destroys the resource. It is no longer usable.
	Destroy() error
	// Resource returns the underlying Resource
	Resource() Resource
}

type PoolMetrics interface {
	ReportResources(stats ResourcePoolStat)
	ReportWait(wt time.Duration)
}

type ResourcePoolStat struct {
	AvailableNow  uint32
	ResourcesOpen uint32
	Cap           uint32
	InUse         uint32
}

// ResourcePool manages a pool of resources for reuse.
type ResourcePool struct {
	metrics PoolMetrics //metrics interface to track how the pool performs

	// To get a resource, get a ticket first and then get the resource from
	// either the reserve or open a new one.
	// To release a resource, first put the resource back to the reserve
	// and then put the ticket back.
	// The order is important to make sure we never create >cap(tickets)
	// number of resources.
	reserve chan Resource // idle resources, ready for use
	tickets chan struct{} // ticket to own resources

	opener ResourceOpener
	closed chan struct{}
}

// NewPool creates a new pool of Clients.
func NewPool(maxReserve, maxOpen uint32, opener ResourceOpener, m PoolMetrics) *ResourcePool {
	if maxOpen < maxReserve {
		panic("maxOpen must be > maxReserve")
	}
	tickets := make(chan struct{}, maxOpen)
	for i := uint32(0); i < maxOpen; i++ {
		tickets <- struct{}{}
	}
	return &ResourcePool{
		metrics: m,
		reserve: make(chan Resource, maxReserve),
		tickets: tickets,
		opener:  opener,
		closed:  make(chan struct{}),
	}
}

func (p *ResourcePool) Get() (PooledResource, error) {
	return p.GetWithTimeout(365 * 24 * time.Hour) // 1 year is forever
}

type pooledResource struct {
	p   *ResourcePool
	res Resource
}

func (pr *pooledResource) Release() error {
	return pr.p.release(pr)
}

func (pr *pooledResource) Destroy() error {
	return pr.p.destroy(pr)
}

func (pr *pooledResource) Resource() Resource {
	return pr.res
}

func (p *ResourcePool) GetWithTimeout(timeout time.Duration) (PooledResource, error) {
	// order is important: first ticket then reserve
	start := time.Now()
	select {
	case <-p.tickets:
	case <-time.After(timeout):
		return nil, TimeoutError
	case <-p.closed:
		return nil, PoolClosedError
	}
	if p.isClosed() {
		// release ticket on close
		p.tickets <- struct{}{}
		return nil, PoolClosedError
	}
	p.reportMetrics(time.Now().Sub(start))

L:
	for {
		select {
		case r := <-p.reserve:
			if r.Good() {
				return &pooledResource{p: p, res: r}, nil
			}
			r.Close()

		default:
			// no reserve
			break L
		}
	}

	r, err := p.opener.Open()
	if err != nil {
		// release ticket on error
		p.tickets <- struct{}{}
		return nil, err
	}
	return &pooledResource{p: p, res: r}, nil
}

func (p *ResourcePool) release(pr PooledResource) error {
	var err error
	// order is important: first reserve then ticket
	res := pr.Resource()
	select {
	case p.reserve <- res:
	default:
		// reserve is full
		err = res.Close()
	}
	p.tickets <- struct{}{}

	if p.isClosed() {
		p.drainReserve()
	}
	return err
}

func (p *ResourcePool) destroy(pr PooledResource) error {
	p.tickets <- struct{}{}
	return pr.Resource().Close()
}

// Close closes the pool. Resources in use are not affected.
func (p *ResourcePool) Close() error {
	close(p.closed)
	p.drainReserve()
	return nil
}

func (p *ResourcePool) isClosed() bool {
	select {
	case <-p.closed:
		return true
	default:
		return false
	}
}

func (p *ResourcePool) drainReserve() {
	for {
		select {
		case r := <-p.reserve:
			r.Close()
		default:
			return
		}
	}
}

/**
Metrics
**/
func (p *ResourcePool) reportMetrics(wt time.Duration) {
	if p.metrics != nil {
		go p.metrics.ReportWait(wt)
		go p.metrics.ReportResources(p.Stats())
	}
}

func (p *ResourcePool) Stats() ResourcePoolStat {
	tot := uint32(cap(p.tickets))
	n := uint32(len(p.tickets))
	inuse := tot - n
	available := uint32(len(p.reserve))

	return ResourcePoolStat{
		AvailableNow:  available,
		ResourcesOpen: inuse + available,
		Cap:           tot,
		InUse:         inuse,
	}
}
<commit_msg>stop timer<commit_after>// A generic resource pool for databases etc
package pool

import (
	"errors"
	"io"
	"time"
)

var (
	TimeoutError    = errors.New("Timeout")
	PoolClosedError = errors.New("Pool is closed")
)

// ResourceOpener opens a resource
type ResourceOpener interface {
	Open() (Resource, error)
}

type Resource interface {
	io.Closer
	// Good returns true when the resource is in good state
	Good() bool
}

type PooledResource interface {
	// Release releases the resource back to the pool for reuse
	Release() error
	// Destroy destroys the resource. It is no longer usable.
	Destroy() error
	// Resource returns the underlying Resource
	Resource() Resource
}

type PoolMetrics interface {
	ReportResources(stats ResourcePoolStat)
	ReportWait(wt time.Duration)
}

type ResourcePoolStat struct {
	AvailableNow  uint32
	ResourcesOpen uint32
	Cap           uint32
	InUse         uint32
}

// ResourcePool manages a pool of resources for reuse.
type ResourcePool struct {
	metrics PoolMetrics //metrics interface to track how the pool performs

	// To get a resource, get a ticket first and then get the resource from
	// either the reserve or open a new one.
	// To release a resource, first put the resource back to the reserve
	// and then put the ticket back.
	// The order is important to make sure we never create >cap(tickets)
	// number of resources.
	reserve chan Resource // idle resources, ready for use
	tickets chan struct{} // ticket to own resources

	opener ResourceOpener
	closed chan struct{}
}

// NewPool creates a new pool of Clients.
func NewPool(maxReserve, maxOpen uint32, opener ResourceOpener, m PoolMetrics) *ResourcePool {
	if maxOpen < maxReserve {
		panic("maxOpen must be > maxReserve")
	}
	tickets := make(chan struct{}, maxOpen)
	for i := uint32(0); i < maxOpen; i++ {
		tickets <- struct{}{}
	}
	return &ResourcePool{
		metrics: m,
		reserve: make(chan Resource, maxReserve),
		tickets: tickets,
		opener:  opener,
		closed:  make(chan struct{}),
	}
}

func (p *ResourcePool) Get() (PooledResource, error) {
	return p.GetWithTimeout(365 * 24 * time.Hour) // 1 year is forever
}

type pooledResource struct {
	p   *ResourcePool
	res Resource
}

func (pr *pooledResource) Release() error {
	return pr.p.release(pr)
}

func (pr *pooledResource) Destroy() error {
	return pr.p.destroy(pr)
}

func (pr *pooledResource) Resource() Resource {
	return pr.res
}

func (p *ResourcePool) GetWithTimeout(timeout time.Duration) (PooledResource, error) {

	// order is important: first ticket then reserve
	start := time.Now()
	timer := time.NewTimer(timeout)

	select {
	case <-p.tickets:
	case <-timer.C:
		return nil, TimeoutError
	case <-p.closed:
		timer.Stop()
		return nil, PoolClosedError
	}

	timer.Stop()
	if p.isClosed() {
		// release ticket on close
		p.tickets <- struct{}{}
		return nil, PoolClosedError
	}
	p.reportMetrics(time.Now().Sub(start))

L:
	for {
		select {
		case r := <-p.reserve:
			if r.Good() {
				return &pooledResource{p: p, res: r}, nil
			}
			r.Close()

		default:
			// no reserve
			break L
		}
	}

	r, err := p.opener.Open()
	if err != nil {
		// release ticket on error
		p.tickets <- struct{}{}
		return nil, err
	}
	return &pooledResource{p: p, res: r}, nil
}

func (p *ResourcePool) release(pr PooledResource) error {
	var err error
	// order is important: first reserve then ticket
	res := pr.Resource()
	select {
	case p.reserve <- res:
	default:
		// reserve is full
		err = res.Close()
	}
	p.tickets <- struct{}{}

	if p.isClosed() {
		p.drainReserve()
	}
	return err
}

func (p *ResourcePool) destroy(pr PooledResource) error {
	p.tickets <- struct{}{}
	return pr.Resource().Close()
}

// Close closes the pool. Resources in use are not affected.
func (p *ResourcePool) Close() error {
	close(p.closed)
	p.drainReserve()
	return nil
}

func (p *ResourcePool) isClosed() bool {
	select {
	case <-p.closed:
		return true
	default:
		return false
	}
}

func (p *ResourcePool) drainReserve() {
	for {
		select {
		case r := <-p.reserve:
			r.Close()
		default:
			return
		}
	}
}

/**
Metrics
**/
func (p *ResourcePool) reportMetrics(wt time.Duration) {
	if p.metrics != nil {
		go p.metrics.ReportWait(wt)
		go p.metrics.ReportResources(p.Stats())
	}
}

func (p *ResourcePool) Stats() ResourcePoolStat {
	tot := uint32(cap(p.tickets))
	n := uint32(len(p.tickets))
	inuse := tot - n
	available := uint32(len(p.reserve))

	return ResourcePoolStat{
		AvailableNow:  available,
		ResourcesOpen: inuse + available,
		Cap:           tot,
		InUse:         inuse,
	}
}
<|endoftext|>


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

package main

import (
	"code.google.com/p/gordon-go/go/types"
	. "code.google.com/p/gordon-go/gui"
	"go/ast"
	"go/parser"
	"math"
)

type port struct {
	*ViewBase
	out          bool
	node         node
	obj          *types.Var
	valView      *typeView
	conns        []*connection
	focused, bad bool
	connsChanged func()

	conntxt *Text
}

const portSize = 11.0

func newInput(n node, v *types.Var) *port  { return newPort(false, n, v) }
func newOutput(n node, v *types.Var) *port { return newPort(true, n, v) }
func newPort(out bool, n node, v *types.Var) *port {
	if v == nil {
		v = &types.Var{}
	}

	p := &port{out: out, node: n, obj: v}
	p.ViewBase = NewView(p)
	p.valView = newValueView(v, nil) // TODO: pass currentPkg here so that named types are not package qualified in the current package and so that unexported fields in unnamed literals (rare) are hidden as appropriate
	Hide(p.valView)
	p.connsChanged = func() {}
	p.Add(p.valView)
	p.SetRect(ZR.Inset(-portSize / 2))
	p.setType(*p.valView.typ)

	p.conntxt = NewText("")
	p.conntxt.SetTextColor(lineColor)
	p.conntxt.SetBackgroundColor(noColor)
	p.conntxt.Validate = validateID
	p.Add(p.conntxt)
	return p
}

func validateID(text *string) bool {
	x, err := parser.ParseExpr(*text)
	_, ok := x.(*ast.Ident)
	return *text == "" || err == nil && ok
}

func (p *port) setType(t types.Type) {
	p.valView.setType(t)
	if p.out {
		p.valView.Move(Pt(-Width(p.valView)/2, -Height(p.valView)-12))
	} else {
		p.valView.Move(Pt(-Width(p.valView)/2, 12))
	}

	for i := 0; i < len(p.conns); {
		c := p.conns[i]
		if c.src != nil && c.dst != nil && !c.connectable(c.src, c.dst) {
			c.blk.removeConn(c)
		} else {
			if p.out && c.dst != nil {
				c.dst.connsChanged()
			}
			i++
		}
	}
}

func (p *port) connect(c *connection) {
	p.conns = append(p.conns, c)
}

func (p *port) disconnect(c *connection) {
	for i, c2 := range p.conns {
		if c2 == c {
			p.conns = append(p.conns[:i], p.conns[i+1:]...)
			return
		}
	}
}

func (p *port) focusMiddle() {
	var conn *connection
	dist := 0.0
	for _, c := range p.conns {
		dir := c.srcPt.Sub(c.dstPt)
		dir.Y, dir.X = dir.XY()
		d := math.Abs(dir.Angle())
		if conn == nil || d < dist {
			conn, dist = c, d
		}
	}
	if conn != nil {
		conn.focus(p.out)
	} else {
		SetKeyFocus(p)
	}
}

// TODO: work with all of a node's in/out conns (including seq conns), not just a single port's conns.
func (p *port) focusNextConn(curƟ float64, dir int) {
	less := func(x, y float64) bool { return x < y }
	if p.out != (dir == KeyRight) {
		less = func(x, y float64) bool { return x > y }
	}
	var conn *connection
	nearestƟ := 0.0
	for _, c := range p.conns {
		Ɵ := c.dstPt.Sub(c.srcPt).Angle()
		if Ɵ != curƟ && less(curƟ, Ɵ) && (conn == nil || less(Ɵ, nearestƟ)) {
			conn, nearestƟ = c, Ɵ
		}
	}
	if conn != nil {
		conn.focus(p.out)
		return
	}

	if p := p.next(dir); p != nil {
		Ɵ := math.Pi
		if p.out == (dir == KeyRight) {
			Ɵ = -Ɵ
		}
		p.focusNextConn(Ɵ, dir)
	}
}

func (p *port) next(dir int) *port {
	if p.obj.Type == seqType {
		// TODO: return nearest
		return nil
	}

	ports := ins(p.node)
	if p.out {
		ports = outs(p.node)
	}
	i := 0
	for j, p2 := range ports {
		if p2 == p {
			i = j
			break
		}
	}
	if dir == KeyLeft {
		i--
	} else {
		i++
	}
	if i >= 0 && i < len(ports) {
		return ports[i]
	}
	return nil
}

func (p *port) Move(pt Point) {
	p.ViewBase.Move(pt)
	for _, c := range p.conns {
		c.reform()
	}
}

func (p *port) TookKeyFocus() {
	p.focused = true
	Repaint(p)
	Show(p.valView)
	panTo(p, ZP)
}

func (p *port) LostKeyFocus() {
	p.focused = false
	Repaint(p)
	Hide(p.valView)
}

func (p *port) KeyPress(event KeyEvent) {
	if event.Alt && !event.Shift {
		switch event.Key {
		case KeyLeft, KeyRight, KeyDown, KeyUp:
			p.node.block().focusNearestView(p, event.Key)
			return
		}
	}

	switch k := event.Key; k {
	case KeyUp, KeyDown:
		if p.out == (k == KeyDown) {
			p.focusMiddle()
		} else {
			if f, ok := p.node.(focuserFrom); ok {
				f.focusFrom(p)
			} else {
				SetKeyFocus(p.node)
			}
		}
	case KeyLeft, KeyRight:
		if p := p.next(k); p != nil {
			SetKeyFocus(p)
		}
	case KeyPeriod:
		if event.Ctrl {
			p.ViewBase.KeyPress(event)
		}
		if !p.out {
			break
		}
		t := p.obj.Type
		if pt, ok := indirect(t); ok { // automatically dereference **t
			if _, ok := indirect(pt); ok {
				t = pt
			}
		}
		b := newSelectionBrowser(t, p)
		if len(b.objs) == 0 {
			break
		}
		p.Add(b)
		b.accepted = func(obj types.Object) {
			b.Close()
			n := p.node.block().newNode(obj, b.funcAsVal, "")
			c := newConnection()
			c.setSrc(p)
			c.setDst(ins(n)[0])
		}
		b.canceled = func() {
			b.Close()
			SetKeyFocus(p)
		}
		SetKeyFocus(b)
	case KeyEnter:
		c := newConnection()
		if p.out {
			c.setSrc(p)
			c.focus(false)
		} else {
			c.setDst(p)
			c.focus(true)
		}
		c.startEditing()
	case KeyBackspace, KeyDelete:
		if n, ok := p.node.(interface {
			removePort(*port)
		}); ok {
			n.removePort(p)
		} else {
			SetKeyFocus(p.node)
		}
	case KeyEscape:
		if f, ok := p.node.(focuserFrom); ok {
			f.focusFrom(p)
		} else {
			SetKeyFocus(p.node)
		}
	default:
		if pn, ok := p.node.(*portsNode); ok && p.out && pn.outs[0] == p && event.Text == "*" {
			if t, ok := p.obj.Type.(*types.Pointer); ok {
				p.setType(t.Elem)
			} else {
				p.setType(&types.Pointer{Elem: p.obj.Type})
			}
		} else {
			p.ViewBase.KeyPress(event)
		}
	}
}

func (p *port) Mouse(m MouseEvent) {
	if m.Press {
		SetKeyFocus(p)
		c := newConnection()
		if p.out {
			c.setSrc(p)
			c.focus(false)
		} else {
			c.setDst(p)
			c.focus(true)
		}
		SetMouser(c, m.Button)
		c.startEditing()
	}
}

func (p *port) Paint() {
	if p.focused {
		SetColor(focusColor)
		SetPointSize(portSize)
		DrawPoint(ZP)
	}
	if p.bad {
		SetColor(Color{1, 0, 0, 1})
		SetLineWidth(3)
		r := Rect(p)
		DrawLine(r.Min, r.Max)
		DrawLine(Pt(r.Min.X, r.Max.Y), Pt(r.Max.X, r.Min.Y))
	}
}
<commit_msg>Flux:  Avoid infinite recursion when feedback connections are present.<commit_after>// Copyright 2014 Gordon Klaus. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"code.google.com/p/gordon-go/go/types"
	. "code.google.com/p/gordon-go/gui"
	"go/ast"
	"go/parser"
	"math"
)

type port struct {
	*ViewBase
	out          bool
	node         node
	obj          *types.Var
	valView      *typeView
	conns        []*connection
	focused, bad bool
	connsChanged func()

	conntxt *Text
}

const portSize = 11.0

func newInput(n node, v *types.Var) *port  { return newPort(false, n, v) }
func newOutput(n node, v *types.Var) *port { return newPort(true, n, v) }
func newPort(out bool, n node, v *types.Var) *port {
	if v == nil {
		v = &types.Var{}
	}

	p := &port{out: out, node: n, obj: v}
	p.ViewBase = NewView(p)
	p.valView = newValueView(v, nil) // TODO: pass currentPkg here so that named types are not package qualified in the current package and so that unexported fields in unnamed literals (rare) are hidden as appropriate
	Hide(p.valView)
	p.connsChanged = func() {}
	p.Add(p.valView)
	p.SetRect(ZR.Inset(-portSize / 2))
	p.setType(*p.valView.typ)

	p.conntxt = NewText("")
	p.conntxt.SetTextColor(lineColor)
	p.conntxt.SetBackgroundColor(noColor)
	p.conntxt.Validate = validateID
	p.Add(p.conntxt)
	return p
}

func validateID(text *string) bool {
	x, err := parser.ParseExpr(*text)
	_, ok := x.(*ast.Ident)
	return *text == "" || err == nil && ok
}

func (p *port) setType(t types.Type) {
	p.valView.setType(t)
	if p.out {
		p.valView.Move(Pt(-Width(p.valView)/2, -Height(p.valView)-12))
	} else {
		p.valView.Move(Pt(-Width(p.valView)/2, 12))
	}

	if *p.valView.typ == t {
		// avoid infinite recursion when feedback connections are present
		return
	}

	for i := 0; i < len(p.conns); {
		c := p.conns[i]
		if c.src != nil && c.dst != nil && !c.connectable(c.src, c.dst) {
			c.blk.removeConn(c)
		} else {
			if p.out && c.dst != nil {
				c.dst.connsChanged()
			}
			i++
		}
	}
}

func (p *port) connect(c *connection) {
	p.conns = append(p.conns, c)
}

func (p *port) disconnect(c *connection) {
	for i, c2 := range p.conns {
		if c2 == c {
			p.conns = append(p.conns[:i], p.conns[i+1:]...)
			return
		}
	}
}

func (p *port) focusMiddle() {
	var conn *connection
	dist := 0.0
	for _, c := range p.conns {
		dir := c.srcPt.Sub(c.dstPt)
		dir.Y, dir.X = dir.XY()
		d := math.Abs(dir.Angle())
		if conn == nil || d < dist {
			conn, dist = c, d
		}
	}
	if conn != nil {
		conn.focus(p.out)
	} else {
		SetKeyFocus(p)
	}
}

// TODO: work with all of a node's in/out conns (including seq conns), not just a single port's conns.
func (p *port) focusNextConn(curƟ float64, dir int) {
	less := func(x, y float64) bool { return x < y }
	if p.out != (dir == KeyRight) {
		less = func(x, y float64) bool { return x > y }
	}
	var conn *connection
	nearestƟ := 0.0
	for _, c := range p.conns {
		Ɵ := c.dstPt.Sub(c.srcPt).Angle()
		if Ɵ != curƟ && less(curƟ, Ɵ) && (conn == nil || less(Ɵ, nearestƟ)) {
			conn, nearestƟ = c, Ɵ
		}
	}
	if conn != nil {
		conn.focus(p.out)
		return
	}

	if p := p.next(dir); p != nil {
		Ɵ := math.Pi
		if p.out == (dir == KeyRight) {
			Ɵ = -Ɵ
		}
		p.focusNextConn(Ɵ, dir)
	}
}

func (p *port) next(dir int) *port {
	if p.obj.Type == seqType {
		// TODO: return nearest
		return nil
	}

	ports := ins(p.node)
	if p.out {
		ports = outs(p.node)
	}
	i := 0
	for j, p2 := range ports {
		if p2 == p {
			i = j
			break
		}
	}
	if dir == KeyLeft {
		i--
	} else {
		i++
	}
	if i >= 0 && i < len(ports) {
		return ports[i]
	}
	return nil
}

func (p *port) Move(pt Point) {
	p.ViewBase.Move(pt)
	for _, c := range p.conns {
		c.reform()
	}
}

func (p *port) TookKeyFocus() {
	p.focused = true
	Repaint(p)
	Show(p.valView)
	panTo(p, ZP)
}

func (p *port) LostKeyFocus() {
	p.focused = false
	Repaint(p)
	Hide(p.valView)
}

func (p *port) KeyPress(event KeyEvent) {
	if event.Alt && !event.Shift {
		switch event.Key {
		case KeyLeft, KeyRight, KeyDown, KeyUp:
			p.node.block().focusNearestView(p, event.Key)
			return
		}
	}

	switch k := event.Key; k {
	case KeyUp, KeyDown:
		if p.out == (k == KeyDown) {
			p.focusMiddle()
		} else {
			if f, ok := p.node.(focuserFrom); ok {
				f.focusFrom(p)
			} else {
				SetKeyFocus(p.node)
			}
		}
	case KeyLeft, KeyRight:
		if p := p.next(k); p != nil {
			SetKeyFocus(p)
		}
	case KeyPeriod:
		if event.Ctrl {
			p.ViewBase.KeyPress(event)
		}
		if !p.out {
			break
		}
		t := p.obj.Type
		if pt, ok := indirect(t); ok { // automatically dereference **t
			if _, ok := indirect(pt); ok {
				t = pt
			}
		}
		b := newSelectionBrowser(t, p)
		if len(b.objs) == 0 {
			break
		}
		p.Add(b)
		b.accepted = func(obj types.Object) {
			b.Close()
			n := p.node.block().newNode(obj, b.funcAsVal, "")
			c := newConnection()
			c.setSrc(p)
			c.setDst(ins(n)[0])
		}
		b.canceled = func() {
			b.Close()
			SetKeyFocus(p)
		}
		SetKeyFocus(b)
	case KeyEnter:
		c := newConnection()
		if p.out {
			c.setSrc(p)
			c.focus(false)
		} else {
			c.setDst(p)
			c.focus(true)
		}
		c.startEditing()
	case KeyBackspace, KeyDelete:
		if n, ok := p.node.(interface {
			removePort(*port)
		}); ok {
			n.removePort(p)
		} else {
			SetKeyFocus(p.node)
		}
	case KeyEscape:
		if f, ok := p.node.(focuserFrom); ok {
			f.focusFrom(p)
		} else {
			SetKeyFocus(p.node)
		}
	default:
		if pn, ok := p.node.(*portsNode); ok && p.out && pn.outs[0] == p && event.Text == "*" {
			if t, ok := p.obj.Type.(*types.Pointer); ok {
				p.setType(t.Elem)
			} else {
				p.setType(&types.Pointer{Elem: p.obj.Type})
			}
		} else {
			p.ViewBase.KeyPress(event)
		}
	}
}

func (p *port) Mouse(m MouseEvent) {
	if m.Press {
		SetKeyFocus(p)
		c := newConnection()
		if p.out {
			c.setSrc(p)
			c.focus(false)
		} else {
			c.setDst(p)
			c.focus(true)
		}
		SetMouser(c, m.Button)
		c.startEditing()
	}
}

func (p *port) Paint() {
	if p.focused {
		SetColor(focusColor)
		SetPointSize(portSize)
		DrawPoint(ZP)
	}
	if p.bad {
		SetColor(Color{1, 0, 0, 1})
		SetLineWidth(3)
		r := Rect(p)
		DrawLine(r.Min, r.Max)
		DrawLine(Pt(r.Min.X, r.Max.Y), Pt(r.Max.X, r.Min.Y))
	}
}
<|endoftext|>


<commit_before>package tent

import (
	"crypto/sha256"
	"encoding/json"
	"errors"
	"io"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/tent/hawk-go"
	"github.com/tent/http-link-go"
)

type PostRef struct {
	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`
	Post           string `json:"post,omitempty"`
	Version        string `json:"version,omitempty"`
	Type           string `json:"type,omitempty"`
}

type PostMention struct {
	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`
	Post           string `json:"post,omitempty"`
	Version        string `json:"version,omitempty"`
	Type           string `json:"type,omitempty"`
	PublicFlag     *bool  `json:"public,omitempty"` // nil or true is public == true; false is public == false
}

func (mention *PostMention) Public() bool {
	return mention.PublicFlag == nil || *mention.PublicFlag
}

type PostAttachment struct {
	Name        string `json:"name"`
	Category    string `json:"category"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size,omitempty"`
	Digest      string `json:"digest,omitempty"`

	// Include Data to upload a new attachment with the post
	Data ReadLenSeeker `json:"-"`

	entity string `json:"-"`
	body   io.ReadCloser
	client *Client
}

// Read downloads and reads from the attachment body.
// The attachment must have be initialized by downloading the containing post
// from the server in order to use Read.
func (att *PostAttachment) Read(p []byte) (int, error) {
	if att.client == nil || att.Digest == "" || att.entity == "" {
		return 0, errors.New("tent: improperly initialized attachment")
	}
	if att.body == nil {
		var err error
		att.body, err = att.client.GetAttachment(att.entity, att.Digest)
		if err != nil {
			return 0, err
		}
	}
	return att.body.Read(p)
}

func (att *PostAttachment) Close() error {
	if att.body == nil {
		return nil
	}
	return att.body.Close()
}

type PostPermissions struct {
	PublicFlag *bool    `json:"public,omitempty"` // nil or true is public == true; false is public == false
	Groups     []string `json:"groups,omitempty"`
	Entities   []string `json:"entities,omitempty"`
}

func (perm *PostPermissions) Public() bool {
	return perm == nil || perm.PublicFlag == nil || *perm.PublicFlag
}

type PostApp struct {
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
	ID   string `json:"id,omitempty"`
}

type PostVersionParent struct {
	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`
	Post           string `json:"post,omitempty"`
	Version        string `json:"version"`
}

type PostVersion struct {
	ID          string              `json:"id,omitempty"`
	Parents     []PostVersionParent `json:"parents,omitempty"`
	Message     string              `json:"message,omitempty"`
	PublishedAt *UnixTime           `json:"published_at,omitempty"`
	ReceivedAt  *UnixTime           `json:"received_at,omitempty"`

	// Used in post version and children lists
	Type   string `json:"type,omitempty"`
	Entity string `json:"entity,omitempty"`
	Post   string `json:"post,omitempty"`
}

type Post struct {
	ID string `json:"id,omitempty"`

	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`

	Type    string          `json:"type"`
	Content json.RawMessage `json:"content,omitempty"`

	Version *PostVersion `json:"version,omitempty"`

	Refs        []PostRef         `json:"refs,omitempty"`
	Mentions    []PostMention     `json:"mentions,omitempty"`
	Licenses    []string          `json:"licenses,omitempty"`
	Attachments []*PostAttachment `json:"attachments,omitempty"`
	Permissions *PostPermissions  `json:"permissions,omitempty"`

	App *PostApp `json:"app,omitempty"`

	ReceivedAt  *UnixTime `json:"received_at,omitempty"`
	PublishedAt *UnixTime `json:"published_at,omitempty"`

	Links []link.Link `json:"-"`
}

type PostEnvelope struct {
	Post *Post  `json:"post"`
	Refs []Post `json:"refs"`
}

const RelCredentials = "https://tent.io/rels/credentials"

var ErrMissingCredentialsLink = errors.New("tent: missing credentials link")

type PostRequest struct {
	MaxRefs int
}

func (client *Client) GetPost(entity, id, version string, r *PostRequest) (*PostEnvelope, error) {
	post := &PostEnvelope{}
	header := make(http.Header)
	header.Set("Accept", MediaTypePost)
	urlFunc := func(server *MetaPostServer) (string, error) {
		u, err := server.URLs.PostURL(entity, id, version)
		if err != nil {
			return "", err
		}
		if r != nil && r.MaxRefs > 0 {
			if strings.Contains(u, "?") {
				uri, err := url.Parse(u)
				if err != nil {
					return "", err
				}
				q := uri.Query()
				q.Add("max_refs", strconv.Itoa(r.MaxRefs))
				uri.RawQuery = q.Encode()
				u = uri.String()
			} else {
				u += "?max_refs=" + strconv.Itoa(r.MaxRefs)
			}
		}
		return u, nil
	}
	_, err := client.requestJSON("GET", urlFunc, header, nil, post)
	if err != nil || post.Post == nil {
		if err == nil {
			err = newBadResponseError(ErrBadData, nil)
		}
		return nil, err
	}
	post.Post.initAttachments(client)
	for _, p := range post.Refs {
		p.initAttachments(client)
	}
	return post, err
}

func GetPost(url string) (*PostEnvelope, error) {
	req, err := newRequest("GET", url, nil, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", MediaTypePost)
	res, err := HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return nil, newBadResponseError(ErrBadStatusCode, res)
	}

	post := &PostEnvelope{}
	if ok := timeoutRead(res.Body, func() {
		err = json.NewDecoder(res.Body).Decode(post)
	}); !ok {
		return nil, newBadResponseError(ErrReadTimeout, res)
	}
	if post.Post == nil {
		return nil, newBadResponseError(ErrBadData, res)
	}
	c := &Client{}
	post.Post.initAttachments(c)
	for _, p := range post.Refs {
		p.initAttachments(c)
	}
	return post, err
}

func (post *Post) LinkedCredentials() (*hawk.Credentials, *Post, error) {
	var credsPostURL string
	for _, l := range post.Links {
		if l.Rel == RelCredentials {
			credsPostURL = l.URI
			break
		}
	}
	if credsPostURL == "" {
		return nil, nil, ErrMissingCredentialsLink
	}
	p, err := GetPost(credsPostURL)
	if err != nil {
		return nil, nil, err
	}
	creds, err := ParseCredentials(p.Post)
	return creds, p.Post, err
}

func (post *Post) hasNewAttachments() bool {
	for _, att := range post.Attachments {
		if att.Data != nil {
			return true
		}
	}
	return false
}

func (post *Post) contentType() string {
	return mime.FormatMediaType(MediaTypePost, map[string]string{"type": post.Type})
}

func (post *Post) initAttachments(client *Client) {
	if post == nil {
		return
	}
	for _, att := range post.Attachments {
		att.entity = post.Entity
		att.client = client
	}
}

func ParseCredentials(post *Post) (*hawk.Credentials, error) {
	creds := &hawk.Credentials{ID: post.ID, Hash: sha256.New}
	temp := &Credentials{}
	err := json.Unmarshal(post.Content, temp)
	creds.Key = temp.HawkKey
	for _, mention := range post.Mentions {
		if mention.Type == PostTypeApp {
			creds.App = mention.Post
		}
	}
	return creds, err
}
<commit_msg>Add notification rel hook to Post<commit_after>package tent

import (
	"crypto/sha256"
	"encoding/json"
	"errors"
	"io"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/tent/hawk-go"
	"github.com/tent/http-link-go"
)

type PostRef struct {
	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`
	Post           string `json:"post,omitempty"`
	Version        string `json:"version,omitempty"`
	Type           string `json:"type,omitempty"`
}

type PostMention struct {
	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`
	Post           string `json:"post,omitempty"`
	Version        string `json:"version,omitempty"`
	Type           string `json:"type,omitempty"`
	PublicFlag     *bool  `json:"public,omitempty"` // nil or true is public == true; false is public == false
}

func (mention *PostMention) Public() bool {
	return mention.PublicFlag == nil || *mention.PublicFlag
}

type PostAttachment struct {
	Name        string `json:"name"`
	Category    string `json:"category"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size,omitempty"`
	Digest      string `json:"digest,omitempty"`

	// Include Data to upload a new attachment with the post
	Data ReadLenSeeker `json:"-"`

	entity string `json:"-"`
	body   io.ReadCloser
	client *Client
}

// Read downloads and reads from the attachment body.
// The attachment must have be initialized by downloading the containing post
// from the server in order to use Read.
func (att *PostAttachment) Read(p []byte) (int, error) {
	if att.client == nil || att.Digest == "" || att.entity == "" {
		return 0, errors.New("tent: improperly initialized attachment")
	}
	if att.body == nil {
		var err error
		att.body, err = att.client.GetAttachment(att.entity, att.Digest)
		if err != nil {
			return 0, err
		}
	}
	return att.body.Read(p)
}

func (att *PostAttachment) Close() error {
	if att.body == nil {
		return nil
	}
	return att.body.Close()
}

type PostPermissions struct {
	PublicFlag *bool    `json:"public,omitempty"` // nil or true is public == true; false is public == false
	Groups     []string `json:"groups,omitempty"`
	Entities   []string `json:"entities,omitempty"`
}

func (perm *PostPermissions) Public() bool {
	return perm == nil || perm.PublicFlag == nil || *perm.PublicFlag
}

type PostApp struct {
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
	ID   string `json:"id,omitempty"`
}

type PostVersionParent struct {
	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`
	Post           string `json:"post,omitempty"`
	Version        string `json:"version"`
}

type PostVersion struct {
	ID          string              `json:"id,omitempty"`
	Parents     []PostVersionParent `json:"parents,omitempty"`
	Message     string              `json:"message,omitempty"`
	PublishedAt *UnixTime           `json:"published_at,omitempty"`
	ReceivedAt  *UnixTime           `json:"received_at,omitempty"`

	// Used in post version and children lists
	Type   string `json:"type,omitempty"`
	Entity string `json:"entity,omitempty"`
	Post   string `json:"post,omitempty"`
}

type Post struct {
	ID string `json:"id,omitempty"`

	Entity         string `json:"entity,omitempty"`
	OriginalEntity string `json:"original_entity,omitempty"`

	Type    string          `json:"type"`
	Content json.RawMessage `json:"content,omitempty"`

	Version *PostVersion `json:"version,omitempty"`

	Refs        []PostRef         `json:"refs,omitempty"`
	Mentions    []PostMention     `json:"mentions,omitempty"`
	Licenses    []string          `json:"licenses,omitempty"`
	Attachments []*PostAttachment `json:"attachments,omitempty"`
	Permissions *PostPermissions  `json:"permissions,omitempty"`

	App *PostApp `json:"app,omitempty"`

	ReceivedAt  *UnixTime `json:"received_at,omitempty"`
	PublishedAt *UnixTime `json:"published_at,omitempty"`

	Links []link.Link `json:"-"`

	Notification bool `json:"-"`
}

type PostEnvelope struct {
	Post *Post  `json:"post"`
	Refs []Post `json:"refs"`
}

const RelCredentials = "https://tent.io/rels/credentials"

var ErrMissingCredentialsLink = errors.New("tent: missing credentials link")

type PostRequest struct {
	MaxRefs int
}

func (client *Client) GetPost(entity, id, version string, r *PostRequest) (*PostEnvelope, error) {
	post := &PostEnvelope{}
	header := make(http.Header)
	header.Set("Accept", MediaTypePost)
	urlFunc := func(server *MetaPostServer) (string, error) {
		u, err := server.URLs.PostURL(entity, id, version)
		if err != nil {
			return "", err
		}
		if r != nil && r.MaxRefs > 0 {
			if strings.Contains(u, "?") {
				uri, err := url.Parse(u)
				if err != nil {
					return "", err
				}
				q := uri.Query()
				q.Add("max_refs", strconv.Itoa(r.MaxRefs))
				uri.RawQuery = q.Encode()
				u = uri.String()
			} else {
				u += "?max_refs=" + strconv.Itoa(r.MaxRefs)
			}
		}
		return u, nil
	}
	_, err := client.requestJSON("GET", urlFunc, header, nil, post)
	if err != nil || post.Post == nil {
		if err == nil {
			err = newBadResponseError(ErrBadData, nil)
		}
		return nil, err
	}
	post.Post.initAttachments(client)
	for _, p := range post.Refs {
		p.initAttachments(client)
	}
	return post, err
}

func GetPost(url string) (*PostEnvelope, error) {
	req, err := newRequest("GET", url, nil, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", MediaTypePost)
	res, err := HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return nil, newBadResponseError(ErrBadStatusCode, res)
	}

	post := &PostEnvelope{}
	if ok := timeoutRead(res.Body, func() {
		err = json.NewDecoder(res.Body).Decode(post)
	}); !ok {
		return nil, newBadResponseError(ErrReadTimeout, res)
	}
	if post.Post == nil {
		return nil, newBadResponseError(ErrBadData, res)
	}
	c := &Client{}
	post.Post.initAttachments(c)
	for _, p := range post.Refs {
		p.initAttachments(c)
	}
	return post, err
}

func (post *Post) LinkedCredentials() (*hawk.Credentials, *Post, error) {
	var credsPostURL string
	for _, l := range post.Links {
		if l.Rel == RelCredentials {
			credsPostURL = l.URI
			break
		}
	}
	if credsPostURL == "" {
		return nil, nil, ErrMissingCredentialsLink
	}
	p, err := GetPost(credsPostURL)
	if err != nil {
		return nil, nil, err
	}
	creds, err := ParseCredentials(p.Post)
	return creds, p.Post, err
}

func (post *Post) hasNewAttachments() bool {
	for _, att := range post.Attachments {
		if att.Data != nil {
			return true
		}
	}
	return false
}

func (post *Post) contentType() string {
	params := map[string]string{"type": post.Type}
	if post.Notification {
		params["rel"] = "https://tent.io/rels/notification"
	}
	return mime.FormatMediaType(MediaTypePost, params)
}

func (post *Post) initAttachments(client *Client) {
	if post == nil {
		return
	}
	for _, att := range post.Attachments {
		att.entity = post.Entity
		att.client = client
	}
}

func ParseCredentials(post *Post) (*hawk.Credentials, error) {
	creds := &hawk.Credentials{ID: post.ID, Hash: sha256.New}
	temp := &Credentials{}
	err := json.Unmarshal(post.Content, temp)
	creds.Key = temp.HawkKey
	for _, mention := range post.Mentions {
		if mention.Type == PostTypeApp {
			creds.App = mention.Post
		}
	}
	return creds, err
}
<|endoftext|>