<commit_before>// Package godocsis is package providing misceleaneous functions and few binaries
// that are usefull for users/admins DOCSIS based networks
// Currently all functions are cable modem related.
// TODO is to add support for concurency and some CMTS support
package godocsis

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/soniah/gosnmp"
)

// Protocol type used insted uint
type Protocol uint

// IPAddrType SNMP ipaddr type
type IPAddrType int

func (r IPAddrType) String() string {
	if r == 1 {
		return "ipv4(1)"
	}
	return "unknown()"
}

func (p Protocol) String() string {
	return strconv.Itoa(int(p))
}

// Value return protocol type casted to int for external use
func (p Protocol) Value() int {
	return int(p)
}

const (
	// ResetOid - generic DOCSIS cable modem reset oid
	ResetOid string = "1.3.6.1.2.1.69.1.1.3.0"
	// DsOid contans table of active downstreams
	DsOid string = "1.3.6.1.2.1.10.127.1.1.1.1.6"
	// UsOid contains table of used upstream channels
	UsOid string = "1.3.6.1.2.1.10.127.1.2.2.1.3"
	// IPAdEntIfIndex will provide tree with list of IP addressess
	IPAdEntIfIndex string = "1.3.6.1.2.1.4.20.1.2"
	// oid_cgConnectedDevices is Technicolor TC7200 specific mib
	// with list of connected devices
	oid_cgConnectedDevices string = "1.3.6.1.4.1.2863.205.10.1.13"
)

const (
	DocsDevSwServerOid       string = ".1.3.6.1.2.1.69.1.3.1.0"
	oid_docsDevSwFilename    string = ".1.3.6.1.2.1.69.1.3.2.0"
	oid_docsDevSwAdminStatus string = ".1.3.6.1.2.1.69.1.3.3.0"
	oid_docsDevSwCurrentVers string = ".1.3.6.1.2.1.69.1.3.5.0"
)

// list of oids for forwarding table in TC7200
const (
	oid_tc7200_cgUiAdvancedForwardingPortStartValue         string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.2"
	oid_tc7200_cgUiAdvancedForwardingPortEndValue           string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.3"
	oid_tc7200_cgUiAdvancedForwardingProtocolType           string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.4"
	oid_tc7200_cgUiAdvancedForwardingIpAddrType             string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.5"
	oid_tc7200_cgUiAdvancedForwardingIpAddr                 string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.6"
	oid_tc7200_cgUiAdvancedForwardingEnabled                string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.7"
	oid_tc7200_cgUiAdvancedForwardingRowStatus              string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.8"
	oid_tc7200_cgUiAdvancedForwardingPortInternalStartValue string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.9"
	oid_tc7200_cgUiAdvancedForwardingPortInternalEndValue   string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.10"
	oid_tc7200_cgUiAdvancedForwardingRemoteIpAddr           string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.11"
	oid_tc7200_cgUiAdvancedForwardingDescription            string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.12"
	oid_tc7200_cgUiAdvancedForwardingRemove                 string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.13"
)

var TC7200ForwardingTree = &CgForwardingOid{
	oid_tc7200_cgUiAdvancedForwardingPortStartValue,
	oid_tc7200_cgUiAdvancedForwardingPortEndValue,
	oid_tc7200_cgUiAdvancedForwardingProtocolType,
	oid_tc7200_cgUiAdvancedForwardingIpAddrType,
	oid_tc7200_cgUiAdvancedForwardingIpAddr,
	oid_tc7200_cgUiAdvancedForwardingEnabled,
	oid_tc7200_cgUiAdvancedForwardingRowStatus,
	oid_tc7200_cgUiAdvancedForwardingPortInternalStartValue,
	oid_tc7200_cgUiAdvancedForwardingPortInternalEndValue,
	oid_tc7200_cgUiAdvancedForwardingRemoteIpAddr,
	oid_tc7200_cgUiAdvancedForwardingDescription,
}

const (
	// Both SNMP code
	Both Protocol = 1
	// Tcp SNMP code
	Tcp Protocol = 2
	// Udp SNMP code
	Udp Protocol = 3
)

const IPv4 IPAddrType = 1

var Session = gosnmp.GoSNMP{
	Port:      161,
	Community: "public",
	Version:   gosnmp.Version2c,
	Timeout:   time.Duration(2) * time.Second,
	Retries:   1,
}

// cable modem structure
type CM struct {
	IPaddr   string
	RouterIP string
	RF       RFParams
	Devices  []cgConnectedDevices
}

// type for holding data related to customer devices connected to cable modem
type cgConnectedDevices struct {
	MacAddr       net.HardwareAddr
	Name          string
	IPAddr        net.IP
	InterfaceType int
}

type CgForwardingOid struct {
	ExtPortStart        string
	ExtPortEnd          string
	ProtocolType        string
	IpAddrType          string
	LocalIP             string
	ForwardingEnabled   string
	ForwardingRowStatus string
	LocalPortStart      string
	LocalPortEnd        string
	ExtIP               string
	RuleName            string
}

type CgForwardRule struct {
	LocalIP        net.IP
	LocalPortStart int
	LocalPortEnd   int
	ExtPortStart   int
	ExtPortEnd     int
	RuleName       string
	ProtocolType   Protocol
	IPAddrType     IPAddrType
}

func (p *CgForwardRule) Validate() (err error) {
	if p.LocalPortStart == 0 {
		err = fmt.Errorf("LocalPortStart can't be 0")
	}
	if p.ExtPortStart == 0 {
		err = fmt.Errorf("ExtPortStart can't be 0")
	}
	if p.LocalPortEnd == 0 {
		p.LocalPortEnd = p.LocalPortStart
		//fmt.Fprintf(os.Stderr, "WARN: LocalEndPort not set. Assuming the same value like LocalStartPort\n")
	}
	if p.ExtPortEnd == 0 {
		p.ExtPortEnd = p.ExtPortStart
	}
	if p.LocalPortStart > p.LocalPortEnd {
		err = fmt.Errorf("LocalPortStart can't be higher than LocalPortEnd")
	}
	if p.ExtPortStart > p.ExtPortEnd {
		err = fmt.Errorf("ExtPortStart can't be higher than ExtPortEnd")
	}
	if len(p.RuleName) == 0 {
		err = fmt.Errorf("RuleName can't be empty")
	}
	if p.ProtocolType == 0 {
		p.ProtocolType = Both
	}
	if p.IPAddrType == 0 {
		p.IPAddrType = 1
		fmt.Fprintf(os.Stderr, "WARN: Default IP addr type used (ipv4(1))\n")
	}
	return
}

// Basic structure used to hold misc CM RF parameters
type RFParams struct {
	DSLevel []int
	USLevel []int
}

func (rf *RFParams) DsBondingSize() int {
	return len(rf.DSLevel)

}

func (rf *RFParams) UsBondingSize() int {
	return len(rf.USLevel)
}

//type WalkFunc func(dataUnit gosnmp.SnmpPDU) []string, error)
//generic SNMPWalk function to walk over SNMP tree
func snmpwalk(session gosnmp.GoSNMP, oid string) ([]string, error) {

	err := session.Connect()
	defer session.Conn.Close()
	if err != nil {
		//log.Fatalf("Connect() err: %v", err)
		return nil, fmt.Errorf("snmpwalk() Connection error: %s", err)
	}
	response, err := session.WalkAll(oid) // Get() accepts up to g.MAX_OIDS
	if err != nil {
		//log.Fatalf("Get() err: %v", err)
		return nil, fmt.Errorf("snmpwalk(): %s", err)
	}
	var result = make([]string, len(response))
	for i, pdu := range response {
		result[i] = strconv.Itoa(pdu.Value.(int))
	}
	return result, nil
}

func snmpset(session gosnmp.GoSNMP, pdus []gosnmp.SnmpPDU) (err error) {
	err = session.Connect()
	defer session.Conn.Close()
	if err != nil {
		return
	}
	_, err = session.Set(pdus)
	return
}

// legacy helper function to convert []byte array to human readable form of IP
// currently this is handled by net/ip package functions
func HexIPtoString(octet_a []byte) (string, error) {
	if len(octet_a) == 4 {
		return fmt.Sprintf("%d.%d.%d.%d", octet_a[0], octet_a[1], octet_a[2], octet_a[3]), nil
	} else {
		return "", fmt.Errorf("Unable to make conversion. 4 bytes required")
	}
}

// PanicIf wrapper for commonly used code for handling errors
func PanicIf(err error) {
	if err != nil {
		panic(err)
	}
}

// AddOidSuffix will add index suffix and return full OID
func AddOidSuffix(oid string, suffix int) (finalOid string) {
	data := []string{oid, strconv.Itoa(suffix)}
	finalOid = strings.Join(data, ".")
	return
}
<commit_msg>Oid2MAC() added<commit_after>// Package godocsis is package providing misceleaneous functions and few binaries
// that are usefull for users/admins DOCSIS based networks
// Currently all functions are cable modem related.
// TODO is to add support for concurency and some CMTS support
package godocsis

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/soniah/gosnmp"
)

// Protocol type used insted uint
type Protocol uint

// IPAddrType SNMP ipaddr type
type IPAddrType int

func (r IPAddrType) String() string {
	if r == 1 {
		return "ipv4(1)"
	}
	return "unknown()"
}

func (p Protocol) String() string {
	return strconv.Itoa(int(p))
}

// Value return protocol type casted to int for external use
func (p Protocol) Value() int {
	return int(p)
}

const (
	// ResetOid - generic DOCSIS cable modem reset oid
	ResetOid string = "1.3.6.1.2.1.69.1.1.3.0"
	// DsOid contans table of active downstreams
	DsOid string = "1.3.6.1.2.1.10.127.1.1.1.1.6"
	// UsOid contains table of used upstream channels
	UsOid string = "1.3.6.1.2.1.10.127.1.2.2.1.3"
	// IPAdEntIfIndex will provide tree with list of IP addressess
	IPAdEntIfIndex string = "1.3.6.1.2.1.4.20.1.2"
	// oid_cgConnectedDevices is Technicolor TC7200 specific mib
	// with list of connected devices
	oid_cgConnectedDevices string = "1.3.6.1.4.1.2863.205.10.1.13"
)

const (
	DocsDevSwServerOid       string = ".1.3.6.1.2.1.69.1.3.1.0"
	oid_docsDevSwFilename    string = ".1.3.6.1.2.1.69.1.3.2.0"
	oid_docsDevSwAdminStatus string = ".1.3.6.1.2.1.69.1.3.3.0"
	oid_docsDevSwCurrentVers string = ".1.3.6.1.2.1.69.1.3.5.0"
)

// list of oids for forwarding table in TC7200
const (
	oid_tc7200_cgUiAdvancedForwardingPortStartValue         string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.2"
	oid_tc7200_cgUiAdvancedForwardingPortEndValue           string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.3"
	oid_tc7200_cgUiAdvancedForwardingProtocolType           string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.4"
	oid_tc7200_cgUiAdvancedForwardingIpAddrType             string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.5"
	oid_tc7200_cgUiAdvancedForwardingIpAddr                 string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.6"
	oid_tc7200_cgUiAdvancedForwardingEnabled                string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.7"
	oid_tc7200_cgUiAdvancedForwardingRowStatus              string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.8"
	oid_tc7200_cgUiAdvancedForwardingPortInternalStartValue string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.9"
	oid_tc7200_cgUiAdvancedForwardingPortInternalEndValue   string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.10"
	oid_tc7200_cgUiAdvancedForwardingRemoteIpAddr           string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.11"
	oid_tc7200_cgUiAdvancedForwardingDescription            string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.12"
	oid_tc7200_cgUiAdvancedForwardingRemove                 string = ".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.13"
)

var TC7200ForwardingTree = &CgForwardingOid{
	oid_tc7200_cgUiAdvancedForwardingPortStartValue,
	oid_tc7200_cgUiAdvancedForwardingPortEndValue,
	oid_tc7200_cgUiAdvancedForwardingProtocolType,
	oid_tc7200_cgUiAdvancedForwardingIpAddrType,
	oid_tc7200_cgUiAdvancedForwardingIpAddr,
	oid_tc7200_cgUiAdvancedForwardingEnabled,
	oid_tc7200_cgUiAdvancedForwardingRowStatus,
	oid_tc7200_cgUiAdvancedForwardingPortInternalStartValue,
	oid_tc7200_cgUiAdvancedForwardingPortInternalEndValue,
	oid_tc7200_cgUiAdvancedForwardingRemoteIpAddr,
	oid_tc7200_cgUiAdvancedForwardingDescription,
}

const (
	// Both SNMP code
	Both Protocol = 1
	// Tcp SNMP code
	Tcp Protocol = 2
	// Udp SNMP code
	Udp Protocol = 3
)

const IPv4 IPAddrType = 1

var Session = gosnmp.GoSNMP{
	Port:      161,
	Community: "public",
	Version:   gosnmp.Version2c,
	Timeout:   time.Duration(2) * time.Second,
	Retries:   1,
}

// cable modem structure
type CM struct {
	IPaddr   string
	RouterIP string
	RF       RFParams
	Devices  []cgConnectedDevices
}

// type for holding data related to customer devices connected to cable modem
type cgConnectedDevices struct {
	MacAddr       net.HardwareAddr
	Name          string
	IPAddr        net.IP
	InterfaceType int
}

type CgForwardingOid struct {
	ExtPortStart        string
	ExtPortEnd          string
	ProtocolType        string
	IpAddrType          string
	LocalIP             string
	ForwardingEnabled   string
	ForwardingRowStatus string
	LocalPortStart      string
	LocalPortEnd        string
	ExtIP               string
	RuleName            string
}

type CgForwardRule struct {
	LocalIP        net.IP
	LocalPortStart int
	LocalPortEnd   int
	ExtPortStart   int
	ExtPortEnd     int
	RuleName       string
	ProtocolType   Protocol
	IPAddrType     IPAddrType
}

func (p *CgForwardRule) Validate() (err error) {
	if p.LocalPortStart == 0 {
		err = fmt.Errorf("LocalPortStart can't be 0")
	}
	if p.ExtPortStart == 0 {
		err = fmt.Errorf("ExtPortStart can't be 0")
	}
	if p.LocalPortEnd == 0 {
		p.LocalPortEnd = p.LocalPortStart
		//fmt.Fprintf(os.Stderr, "WARN: LocalEndPort not set. Assuming the same value like LocalStartPort\n")
	}
	if p.ExtPortEnd == 0 {
		p.ExtPortEnd = p.ExtPortStart
	}
	if p.LocalPortStart > p.LocalPortEnd {
		err = fmt.Errorf("LocalPortStart can't be higher than LocalPortEnd")
	}
	if p.ExtPortStart > p.ExtPortEnd {
		err = fmt.Errorf("ExtPortStart can't be higher than ExtPortEnd")
	}
	if len(p.RuleName) == 0 {
		err = fmt.Errorf("RuleName can't be empty")
	}
	if p.ProtocolType == 0 {
		p.ProtocolType = Both
	}
	if p.IPAddrType == 0 {
		p.IPAddrType = 1
		fmt.Fprintf(os.Stderr, "WARN: Default IP addr type used (ipv4(1))\n")
	}
	return
}

// Basic structure used to hold misc CM RF parameters
type RFParams struct {
	DSLevel []int
	USLevel []int
}

func (rf *RFParams) DsBondingSize() int {
	return len(rf.DSLevel)

}

func (rf *RFParams) UsBondingSize() int {
	return len(rf.USLevel)
}

//type WalkFunc func(dataUnit gosnmp.SnmpPDU) []string, error)
//generic SNMPWalk function to walk over SNMP tree
func snmpwalk(session gosnmp.GoSNMP, oid string) ([]string, error) {

	err := session.Connect()
	defer session.Conn.Close()
	if err != nil {
		//log.Fatalf("Connect() err: %v", err)
		return nil, fmt.Errorf("snmpwalk() Connection error: %s", err)
	}
	response, err := session.WalkAll(oid) // Get() accepts up to g.MAX_OIDS
	if err != nil {
		//log.Fatalf("Get() err: %v", err)
		return nil, fmt.Errorf("snmpwalk(): %s", err)
	}
	var result = make([]string, len(response))
	for i, pdu := range response {
		result[i] = strconv.Itoa(pdu.Value.(int))
	}
	return result, nil
}

func snmpset(session gosnmp.GoSNMP, pdus []gosnmp.SnmpPDU) (err error) {
	err = session.Connect()
	defer session.Conn.Close()
	if err != nil {
		return
	}
	_, err = session.Set(pdus)
	return
}

// legacy helper function to convert []byte array to human readable form of IP
// currently this is handled by net/ip package functions
func HexIPtoString(octet_a []byte) (string, error) {
	if len(octet_a) == 4 {
		return fmt.Sprintf("%d.%d.%d.%d", octet_a[0], octet_a[1], octet_a[2], octet_a[3]), nil
	} else {
		return "", fmt.Errorf("Unable to make conversion. 4 bytes required")
	}
}

// PanicIf wrapper for commonly used code for handling errors
func PanicIf(err error) {
	if err != nil {
		panic(err)
	}
}

// AddOidSuffix will add index suffix and return full OID
func AddOidSuffix(oid string, suffix int) (finalOid string) {
	data := []string{oid, strconv.Itoa(suffix)}
	finalOid = strings.Join(data, ".")
	return
}

//Oid2MAC Replace SNMP integer encoded MAC ("4.222.110.11.22.224")
// MAC address
func Oid2MAC(oid string) (string, error) {
	var rs []string
	for _, v := range strings.Split(oid, ".") {
		i, err := strconv.Atoi(v)
		if err != nil {
			return "", err
		}
		rs = append(rs, fmt.Sprintf("%02X", i))

	}
	return strings.Join(rs, ":"), nil
}
<|endoftext|>


<commit_before>package main

import "fmt"

func main() {
	fmt.Printf("hi!")
}
<commit_msg>I can read a username! Hurray!<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Username: ")
	input, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Unexpected error reading from standard input.")
		fmt.Println(err)
	}
	username := strings.TrimSpace(input)
	fmt.Println(username)
}
<|endoftext|>


<commit_before>package web

import (
	"net/http"
)

/*
Mux is an HTTP multiplexer, much like net/http's ServeMux.

Routes may be added using any of the various HTTP-method-specific functions.
When processing a request, when iterating in insertion order the first route
that matches both the request's path and method is used.

There are two other differences worth mentioning between web.Mux and
http.ServeMux. First, string patterns (i.e., Sinatra-like patterns) must match
exactly: the "rooted subtree" behavior of ServeMux is not implemented. Secondly,
unlike ServeMux, Mux does not support Host-specific patterns.

If you require any of these features, remember that you are free to mix and
match muxes at any part of the stack.

In order to provide a sane API, many functions on Mux take interface{}'s. This
is obviously not a very satisfying solution, but it's probably the best we can
do for now. Instead of duplicating documentation on each method, the types
accepted by those functions are documented here.

A middleware (the untyped parameter in Use() and Insert()) must be one of the
following types:
	- func(http.Handler) http.Handler
	- func(c *web.C, http.Handler) http.Handler
All of the route-adding functions on Mux take two untyped parameters: pattern
and handler. Pattern must be one of the following types:
	- string. It will be interpreted as a Sinatra-like pattern. In
	  particular, the following syntax is recognized:
		- a path segment starting with with a colon will match any
		  string placed at that position. e.g., "/:name" will match
		  "/carl", binding "name" to "carl".
		- a pattern ending with an asterisk will match any prefix of
		  that route. For instance, "/admin/*" will match "/admin/" and
		  "/admin/secret/lair". This is similar to Sinatra's wildcard,
		  but may only appear at the very end of the string and is
		  therefore significantly less powerful.
	- regexp.Regexp. The library assumes that it is a Perl-style regexp that
	  is anchored on the left (i.e., the beginning of the string). If your
	  regexp is not anchored on the left, a hopefully-identical
	  left-anchored regexp will be created and used instead.
	- web.Pattern
Handler must be one of the following types:
	- http.Handler
	- web.Handler
	- func(w http.ResponseWriter, r *http.Request)
	- func(c web.C, w http.ResponseWriter, r *http.Request)
*/
type Mux struct {
	mStack
	router
}

// New creates a new Mux without any routes or middleware.
func New() *Mux {
	mux := Mux{
		mStack: mStack{
			stack: make([]mLayer, 0),
			pool:  make(chan *cStack, mPoolSize),
		},
		router: router{
			routes:   make([]route, 0),
			notFound: parseHandler(http.NotFound),
		},
	}
	mux.mStack.router = HandlerFunc(mux.router.route)
	return &mux
}

// Serve a request with the given Mux. Satisfies the http.Handler interface.
func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	stack := m.mStack.alloc()
	defer m.mStack.release(stack)

	stack.ServeHTTP(w, r)
}

// ServeHTTPC creates a context dependent request with the given Mux. Satisfies
// the web.Handler interface.
func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) {
	stack := m.mStack.alloc()
	defer m.mStack.release(stack)

	stack.ServeHTTPC(c, w, r)
}
<commit_msg>No need to re-document methods.<commit_after>package web

import (
	"net/http"
)

/*
Mux is an HTTP multiplexer, much like net/http's ServeMux.

Routes may be added using any of the various HTTP-method-specific functions.
When processing a request, when iterating in insertion order the first route
that matches both the request's path and method is used.

There are two other differences worth mentioning between web.Mux and
http.ServeMux. First, string patterns (i.e., Sinatra-like patterns) must match
exactly: the "rooted subtree" behavior of ServeMux is not implemented. Secondly,
unlike ServeMux, Mux does not support Host-specific patterns.

If you require any of these features, remember that you are free to mix and
match muxes at any part of the stack.

In order to provide a sane API, many functions on Mux take interface{}'s. This
is obviously not a very satisfying solution, but it's probably the best we can
do for now. Instead of duplicating documentation on each method, the types
accepted by those functions are documented here.

A middleware (the untyped parameter in Use() and Insert()) must be one of the
following types:
	- func(http.Handler) http.Handler
	- func(c *web.C, http.Handler) http.Handler
All of the route-adding functions on Mux take two untyped parameters: pattern
and handler. Pattern must be one of the following types:
	- string. It will be interpreted as a Sinatra-like pattern. In
	  particular, the following syntax is recognized:
		- a path segment starting with with a colon will match any
		  string placed at that position. e.g., "/:name" will match
		  "/carl", binding "name" to "carl".
		- a pattern ending with an asterisk will match any prefix of
		  that route. For instance, "/admin/*" will match "/admin/" and
		  "/admin/secret/lair". This is similar to Sinatra's wildcard,
		  but may only appear at the very end of the string and is
		  therefore significantly less powerful.
	- regexp.Regexp. The library assumes that it is a Perl-style regexp that
	  is anchored on the left (i.e., the beginning of the string). If your
	  regexp is not anchored on the left, a hopefully-identical
	  left-anchored regexp will be created and used instead.
	- web.Pattern
Handler must be one of the following types:
	- http.Handler
	- web.Handler
	- func(w http.ResponseWriter, r *http.Request)
	- func(c web.C, w http.ResponseWriter, r *http.Request)
*/
type Mux struct {
	mStack
	router
}

// New creates a new Mux without any routes or middleware.
func New() *Mux {
	mux := Mux{
		mStack: mStack{
			stack: make([]mLayer, 0),
			pool:  make(chan *cStack, mPoolSize),
		},
		router: router{
			routes:   make([]route, 0),
			notFound: parseHandler(http.NotFound),
		},
	}
	mux.mStack.router = HandlerFunc(mux.router.route)
	return &mux
}

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	stack := m.mStack.alloc()
	defer m.mStack.release(stack)

	stack.ServeHTTP(w, r)
}

// ServeHTTPC creates a context dependent request with the given Mux. Satisfies
// the web.Handler interface.
func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) {
	stack := m.mStack.alloc()
	defer m.mStack.release(stack)

	stack.ServeHTTPC(c, w, r)
}
<|endoftext|>


<commit_before>// Copyright 2011 The draw2d Authors. All rights reserved.
// created: 29/09/2011 by Laurent Le Goff

// The package draw2d provide a Graphic Context that can draw vectorial figure on surface.
package draw2d
<commit_msg>remove Copyright for godoc<commit_after>// The package draw2d provide a Graphic Context that can draw vectorial figure on surface.
package draw2d

<|endoftext|>


<commit_before>package web

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/github"

	gorillactx "github.com/gorilla/context"
	"github.com/gorilla/sessions"
	"github.com/psimika/secure-web-app/petfind"
)

// Error can be returned by the handlers of application's HTTP server.
type Error struct {
	Err     error
	Message string
	Code    int
}

// E constructs an *Error and can be used as a shorthand when a handler returns
// an *Error.
func E(err error, message string, code int) *Error {
	return &Error{Err: err, Message: message, Code: code}
}

// handler is a custom HTTP handler that can return an *Error. It is used
// instead of the standard http.Handler in order to simplify repetitive error
// handling as proposed by Gerrand (2011a):
// https://blog.golang.org/error-handling-and-go
type handler func(http.ResponseWriter, *http.Request) *Error

func (fn handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *web.Error, not error.
		log.Println(e)
		http.Error(w, e.Message, e.Code)
	}
}

// server is the application's HTTP server.
type server struct {
	handlers      http.Handler
	mux           *http.ServeMux
	store         petfind.Store
	tmpl          *tmpl
	github        *oauth2.Config
	sessions      sessions.Store
	sessionTTL    int
	sessionMaxTTL int
}

// tmpl contains the server's templates required to render its pages.
type tmpl struct {
	home        *template.Template
	addPet      *template.Template
	searchReply *template.Template
	showPets    *template.Template
	login       *template.Template
}

// NewServer initializes and returns a new HTTP server.
func NewServer(
	store petfind.Store,
	sessionStore sessions.Store,
	sessionTTL int,
	sessionMaxTTL int,
	hashKey []byte,
	blockKey []byte,
	templatePath string,
	githubID string,
	githubSecret string,
) (http.Handler, error) {
	t, err := parseTemplates(filepath.Join(templatePath, "templates"))
	if err != nil {
		return nil, fmt.Errorf("error parsing templates: %v", err)
	}

	s := &server{
		mux:           http.NewServeMux(),
		store:         store,
		tmpl:          t,
		github:        newGitHubOAuthConfig(githubID, githubSecret),
		sessions:      sessionStore,
		sessionTTL:    sessionTTL,
		sessionMaxTTL: sessionMaxTTL,
	}
	s.handlers = gorillactx.ClearHandler(s.mux)
	s.mux.Handle("/", handler(s.homeHandler))
	s.mux.Handle("/form", handler(s.searchReplyHandler))
	s.mux.Handle("/pets", handler(s.servePets))
	s.mux.Handle("/pets/add", s.auth(s.serveAddPet))
	s.mux.Handle("/pets/add/submit", s.auth(s.handleAddPet))
	s.mux.Handle("/login", handler(s.serveLogin))
	s.mux.Handle("/login/github", handler(s.handleLoginGitHub))
	s.mux.Handle("/login/github/cb", handler(s.handleLoginGitHubCallback))
	s.mux.Handle("/logout", s.auth(s.handleLogout))
	return s, nil
}

func newGitHubOAuthConfig(clientID, clientSecret string) *oauth2.Config {
	return &oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		// Only requiring the user's public info and their email.
		//
		// Full list of scopes:
		// https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/about-scopes-for-oauth-apps/
		Scopes:   []string{"user:email"},
		Endpoint: github.Endpoint,
	}
}

func parseTemplates(dir string) (*tmpl, error) {
	homeTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "search.tmpl"))
	if err != nil {
		return nil, err
	}
	addPetTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "addpet.tmpl"))
	if err != nil {
		return nil, err
	}
	searchReplyTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "searchreply.tmpl"))
	if err != nil {
		return nil, err
	}
	showPetsTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "showpets.tmpl"))
	if err != nil {
		return nil, err
	}
	loginTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "login.tmpl"))
	t := &tmpl{
		home:        homeTmpl,
		addPet:      addPetTmpl,
		searchReply: searchReplyTmpl,
		showPets:    showPetsTmpl,
		login:       loginTmpl,
	}
	return t, err
}

// ServeHTTP satisfies the http.Handler interface for a server.
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
		// HSTS header suggested by OWASP (2017a) to address certain threats:
		// https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet
		w.Header().Set("Strict-Transport-Security", "max-age=86400; includeSubDomains")
	}
	s.handlers.ServeHTTP(w, r)
}

func (s *server) homeHandler(w http.ResponseWriter, r *http.Request) *Error {
	if err := s.tmpl.home.Execute(w, nil); err != nil {
		return E(err, "could not serve home", http.StatusInternalServerError)
	}
	return nil
}

func (s *server) serveAddPet(w http.ResponseWriter, r *http.Request) *Error {
	user, ok := fromContextGetUser(r.Context())
	if !ok || user == nil {
		return E(nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
	}

	err := s.tmpl.addPet.Execute(w, user)
	if err != nil {
		return E(err, "could not serve addPet", http.StatusInternalServerError)
	}
	return nil
}

func (s *server) handleAddPet(w http.ResponseWriter, r *http.Request) *Error {
	user, ok := fromContextGetUser(r.Context())
	if !ok || user == nil {
		return E(nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
	}

	name := r.FormValue("name")
	p := &petfind.Pet{Name: name}
	if err := s.store.AddPet(p); err != nil {
		return E(err, "Error adding pet", http.StatusInternalServerError)
	}

	w.Write([]byte("pet added!"))
	return nil
}

func (s *server) searchReplyHandler(w http.ResponseWriter, r *http.Request) *Error {
	name := r.FormValue("name")

	pets, err := s.store.GetAllPets()
	if err != nil {
		return E(err, "internal server error", http.StatusInternalServerError)
	}
	var p *petfind.Pet
	for i := range pets {
		if pets[i].Name == name {
			p = &pets[i]
		}
	}

	if err := s.tmpl.searchReply.Execute(w, p); err != nil {
		return E(err, "internal server error", http.StatusInternalServerError)
	}
	return nil
}

func (s *server) servePets(w http.ResponseWriter, r *http.Request) *Error {
	pets, err := s.store.GetAllPets()
	if err != nil {
		return E(err, "Error getting all pets", http.StatusInternalServerError)
	}
	err = s.tmpl.showPets.Execute(w, pets)
	if err != nil {
		return E(err, "internal server error", http.StatusInternalServerError)
	}
	return nil
}
<commit_msg>Document sessionTTL and sessionMaxTTL on NewServer<commit_after>package web

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/github"

	gorillactx "github.com/gorilla/context"
	"github.com/gorilla/sessions"
	"github.com/psimika/secure-web-app/petfind"
)

// Error can be returned by the handlers of application's HTTP server.
type Error struct {
	Err     error
	Message string
	Code    int
}

// E constructs an *Error and can be used as a shorthand when a handler returns
// an *Error.
func E(err error, message string, code int) *Error {
	return &Error{Err: err, Message: message, Code: code}
}

// handler is a custom HTTP handler that can return an *Error. It is used
// instead of the standard http.Handler in order to simplify repetitive error
// handling as proposed by Gerrand (2011a):
// https://blog.golang.org/error-handling-and-go
type handler func(http.ResponseWriter, *http.Request) *Error

func (fn handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *web.Error, not error.
		log.Println(e)
		http.Error(w, e.Message, e.Code)
	}
}

// server is the application's HTTP server.
type server struct {
	handlers      http.Handler
	mux           *http.ServeMux
	store         petfind.Store
	tmpl          *tmpl
	github        *oauth2.Config
	sessions      sessions.Store
	sessionTTL    int
	sessionMaxTTL int
}

// tmpl contains the server's templates required to render its pages.
type tmpl struct {
	home        *template.Template
	addPet      *template.Template
	searchReply *template.Template
	showPets    *template.Template
	login       *template.Template
}

// NewServer initializes and returns a new HTTP server.
//
// sessionTTL is used to extend the session's idle timeout.
//
// sessionMaxTTL is used to check if a session has expired by surpassing its
// absolute timeout.
func NewServer(
	store petfind.Store,
	sessionStore sessions.Store,
	sessionTTL int,
	sessionMaxTTL int,
	hashKey []byte,
	blockKey []byte,
	templatePath string,
	githubID string,
	githubSecret string,
) (http.Handler, error) {
	t, err := parseTemplates(filepath.Join(templatePath, "templates"))
	if err != nil {
		return nil, fmt.Errorf("error parsing templates: %v", err)
	}

	s := &server{
		mux:           http.NewServeMux(),
		store:         store,
		tmpl:          t,
		github:        newGitHubOAuthConfig(githubID, githubSecret),
		sessions:      sessionStore,
		sessionTTL:    sessionTTL,
		sessionMaxTTL: sessionMaxTTL,
	}
	s.handlers = gorillactx.ClearHandler(s.mux)
	s.mux.Handle("/", handler(s.homeHandler))
	s.mux.Handle("/form", handler(s.searchReplyHandler))
	s.mux.Handle("/pets", handler(s.servePets))
	s.mux.Handle("/pets/add", s.auth(s.serveAddPet))
	s.mux.Handle("/pets/add/submit", s.auth(s.handleAddPet))
	s.mux.Handle("/login", handler(s.serveLogin))
	s.mux.Handle("/login/github", handler(s.handleLoginGitHub))
	s.mux.Handle("/login/github/cb", handler(s.handleLoginGitHubCallback))
	s.mux.Handle("/logout", s.auth(s.handleLogout))
	return s, nil
}

func newGitHubOAuthConfig(clientID, clientSecret string) *oauth2.Config {
	return &oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		// Only requiring the user's public info and their email.
		//
		// Full list of scopes:
		// https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/about-scopes-for-oauth-apps/
		Scopes:   []string{"user:email"},
		Endpoint: github.Endpoint,
	}
}

func parseTemplates(dir string) (*tmpl, error) {
	homeTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "search.tmpl"))
	if err != nil {
		return nil, err
	}
	addPetTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "addpet.tmpl"))
	if err != nil {
		return nil, err
	}
	searchReplyTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "searchreply.tmpl"))
	if err != nil {
		return nil, err
	}
	showPetsTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "showpets.tmpl"))
	if err != nil {
		return nil, err
	}
	loginTmpl, err := template.ParseFiles(filepath.Join(dir, "base.tmpl"), filepath.Join(dir, "login.tmpl"))
	t := &tmpl{
		home:        homeTmpl,
		addPet:      addPetTmpl,
		searchReply: searchReplyTmpl,
		showPets:    showPetsTmpl,
		login:       loginTmpl,
	}
	return t, err
}

// ServeHTTP satisfies the http.Handler interface for a server.
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
		// HSTS header suggested by OWASP (2017a) to address certain threats:
		// https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet
		w.Header().Set("Strict-Transport-Security", "max-age=86400; includeSubDomains")
	}
	s.handlers.ServeHTTP(w, r)
}

func (s *server) homeHandler(w http.ResponseWriter, r *http.Request) *Error {
	if err := s.tmpl.home.Execute(w, nil); err != nil {
		return E(err, "could not serve home", http.StatusInternalServerError)
	}
	return nil
}

func (s *server) serveAddPet(w http.ResponseWriter, r *http.Request) *Error {
	user, ok := fromContextGetUser(r.Context())
	if !ok || user == nil {
		return E(nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
	}

	err := s.tmpl.addPet.Execute(w, user)
	if err != nil {
		return E(err, "could not serve addPet", http.StatusInternalServerError)
	}
	return nil
}

func (s *server) handleAddPet(w http.ResponseWriter, r *http.Request) *Error {
	user, ok := fromContextGetUser(r.Context())
	if !ok || user == nil {
		return E(nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
	}

	name := r.FormValue("name")
	p := &petfind.Pet{Name: name}
	if err := s.store.AddPet(p); err != nil {
		return E(err, "Error adding pet", http.StatusInternalServerError)
	}

	w.Write([]byte("pet added!"))
	return nil
}

func (s *server) searchReplyHandler(w http.ResponseWriter, r *http.Request) *Error {
	name := r.FormValue("name")

	pets, err := s.store.GetAllPets()
	if err != nil {
		return E(err, "internal server error", http.StatusInternalServerError)
	}
	var p *petfind.Pet
	for i := range pets {
		if pets[i].Name == name {
			p = &pets[i]
		}
	}

	if err := s.tmpl.searchReply.Execute(w, p); err != nil {
		return E(err, "internal server error", http.StatusInternalServerError)
	}
	return nil
}

func (s *server) servePets(w http.ResponseWriter, r *http.Request) *Error {
	pets, err := s.store.GetAllPets()
	if err != nil {
		return E(err, "Error getting all pets", http.StatusInternalServerError)
	}
	err = s.tmpl.showPets.Execute(w, pets)
	if err != nil {
		return E(err, "internal server error", http.StatusInternalServerError)
	}
	return nil
}
<|endoftext|>


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

// +build !js

package oto

// #cgo LDFLAGS: -framework AudioToolbox
//
// #import <AudioToolbox/AudioToolbox.h>
//
// void oto_render(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
//
// void oto_setNotificationHandler(AudioQueueRef audioQueue);
// bool oto_isBackground(void);
import "C"

import (
	"fmt"
	"runtime"
	"sync"
	"time"
	"unsafe"
)

const baseQueueBufferSize = 1024

type audioInfo struct {
	channelNum      int
	bitDepthInBytes int
}

type driver struct {
	audioQueue    C.AudioQueueRef
	buf           []byte
	bufSize       int
	sampleRate    int
	audioInfo     *audioInfo
	buffers       []C.AudioQueueBufferRef
	paused        bool
	lastPauseTime time.Time

	err error

	chWrite   chan []byte
	chWritten chan int

	m sync.Mutex
}

var (
	theDriver *driver
	driverM   sync.Mutex
)

func setDriver(d *driver) {
	driverM.Lock()
	defer driverM.Unlock()

	if theDriver != nil && d != nil {
		panic("oto: at most one driver object can exist")
	}
	theDriver = d

	if d != nil {
		setNotificationHandler(d)
	}
}

func getDriver() *driver {
	driverM.Lock()
	defer driverM.Unlock()

	return theDriver
}

// TOOD: Convert the error code correctly.
// See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful

func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) {
	flags := C.kAudioFormatFlagIsPacked
	if bitDepthInBytes != 1 {
		flags |= C.kAudioFormatFlagIsSignedInteger
	}
	desc := C.AudioStreamBasicDescription{
		mSampleRate:       C.double(sampleRate),
		mFormatID:         C.kAudioFormatLinearPCM,
		mFormatFlags:      C.UInt32(flags),
		mBytesPerPacket:   C.UInt32(channelNum * bitDepthInBytes),
		mFramesPerPacket:  1,
		mBytesPerFrame:    C.UInt32(channelNum * bitDepthInBytes),
		mChannelsPerFrame: C.UInt32(channelNum),
		mBitsPerChannel:   C.UInt32(8 * bitDepthInBytes),
	}

	audioInfo := &audioInfo{
		channelNum:      channelNum,
		bitDepthInBytes: bitDepthInBytes,
	}

	var audioQueue C.AudioQueueRef
	if osstatus := C.AudioQueueNewOutput(
		&desc,
		(C.AudioQueueOutputCallback)(C.oto_render),
		unsafe.Pointer(audioInfo),
		(C.CFRunLoopRef)(0),
		(C.CFStringRef)(0),
		0,
		&audioQueue); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus)
	}

	queueBufferSize := baseQueueBufferSize * channelNum * bitDepthInBytes
	nbuf := bufferSizeInBytes / queueBufferSize
	if nbuf <= 1 {
		nbuf = 2
	}

	d := &driver{
		audioQueue: audioQueue,
		sampleRate: sampleRate,
		audioInfo:  audioInfo,
		bufSize:    nbuf * queueBufferSize,
		buffers:    make([]C.AudioQueueBufferRef, nbuf),
		chWrite:    make(chan []byte),
		chWritten:  make(chan int),
	}
	runtime.SetFinalizer(d, (*driver).Close)
	// Set the driver before setting the rendering callback.
	setDriver(d)

	for i := 0; i < len(d.buffers); i++ {
		if osstatus := C.AudioQueueAllocateBuffer(audioQueue, C.UInt32(queueBufferSize), &d.buffers[i]); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus)
		}
		d.buffers[i].mAudioDataByteSize = C.UInt32(queueBufferSize)
		for j := 0; j < queueBufferSize; j++ {
			*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(d.buffers[i].mAudioData)) + uintptr(j))) = 0
		}
		if osstatus := C.AudioQueueEnqueueBuffer(audioQueue, d.buffers[i], 0, nil); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		}
	}

	if osstatus := C.AudioQueueStart(audioQueue, nil); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus)
	}

	return d, nil
}

//export oto_render
func oto_render(inUserData unsafe.Pointer, inAQ C.AudioQueueRef, inBuffer C.AudioQueueBufferRef) {
	audioInfo := (*audioInfo)(inUserData)
	queueBufferSize := baseQueueBufferSize * audioInfo.channelNum * audioInfo.bitDepthInBytes

	d := getDriver()

	var buf []byte

	// Set the timer. When the input does not come, the audio must be paused.
	s := time.Second * time.Duration(queueBufferSize) / time.Duration(d.sampleRate*d.audioInfo.channelNum*d.audioInfo.bitDepthInBytes)
	t := time.NewTicker(s)
	defer t.Stop()
	ch := t.C

	for len(buf) < queueBufferSize {
		select {
		case dbuf := <-d.chWrite:
			for !d.resume(false) {
				d.m.Lock()
				err := d.err
				d.m.Unlock()
				if err != nil {
					return
				}

				time.Sleep(time.Second)
			}
			n := queueBufferSize - len(buf)
			if n > len(dbuf) {
				n = len(dbuf)
			}
			buf = append(buf, dbuf[:n]...)
			d.chWritten <- n
		case <-ch:
			d.pause()
			ch = nil
		}
	}

	for i := 0; i < queueBufferSize; i++ {
		*(*byte)(unsafe.Pointer(uintptr(inBuffer.mAudioData) + uintptr(i))) = buf[i]
	}
	// Do not update mAudioDataByteSize, or the buffer is not used correctly any more.

	d.enqueueBuffer(inBuffer)
}

func (d *driver) TryWrite(data []byte) (int, error) {
	d.m.Lock()
	err := d.err
	d.m.Unlock()
	if err != nil {
		return 0, err
	}

	n := d.bufSize - len(d.buf)
	if n > len(data) {
		n = len(data)
	}
	d.buf = append(d.buf, data[:n]...)
	// Use the buffer only when the buffer length is enough to avoid choppy sound.
	queueBufferSize := baseQueueBufferSize * d.audioInfo.channelNum * d.audioInfo.bitDepthInBytes
	for len(d.buf) >= queueBufferSize {
		d.chWrite <- d.buf
		n := <-d.chWritten
		d.buf = d.buf[n:]
	}
	return n, nil
}

func (d *driver) Close() error {
	d.m.Lock()
	defer d.m.Unlock()

	runtime.SetFinalizer(d, nil)

	if osstatus := C.AudioQueueStop(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueStop failed: %d", osstatus)
	}
	if osstatus := C.AudioQueueDispose(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueDispose failed: %d", osstatus)
	}
	d.audioQueue = nil
	setDriver(nil)
	return nil
}

func (d *driver) enqueueBuffer(buffer C.AudioQueueBufferRef) {
	d.m.Lock()
	defer d.m.Unlock()

	if osstatus := C.AudioQueueEnqueueBuffer(d.audioQueue, buffer, 0, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		return
	}
}

func (d *driver) resume(afterSleep bool) bool {
	d.m.Lock()
	defer d.m.Unlock()

	// Audio doesn't work soon after recovering from sleeping. Wait for a while
	// (hajimehoshi/ebiten#1259).
	if afterSleep {
		// After short-time sleeping, 500ms more sleeping is enough. However, after long-time sleeping, it
		// looks like 1 second more sleeping are required (hajimehoshi/ebiten#1280).
		// This is tested on MacBook Pro 2020 macOS 10.15.6.
		if time.Now().Sub(d.lastPauseTime) < 30*time.Second {
			time.Sleep(500 * time.Millisecond)
		} else {
			time.Sleep(time.Second)
		}
	}

	if C.oto_isBackground() {
		return false
	}

	if osstatus := C.AudioQueueStart(d.audioQueue, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueStart for resuming failed: %d", osstatus)
		return false
	}
	d.paused = false
	return true
}

func (d *driver) pause() {
	d.m.Lock()
	defer d.m.Unlock()

	if d.paused {
		return
	}
	if osstatus := C.AudioQueuePause(d.audioQueue); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus)
		return
	}
	d.paused = true
	d.lastPauseTime = time.Now()
}

func (d *driver) setError(err error) {
	d.m.Lock()
	defer d.m.Unlock()

	if theDriver.err != nil {
		return
	}
	theDriver.err = err
}

func setNotificationHandler(driver *driver) {
	C.oto_setNotificationHandler(driver.audioQueue)
}

//export oto_setGlobalPause
func oto_setGlobalPause() {
	theDriver.pause()
}

//export oto_setGlobalResume
func oto_setGlobalResume() {
	theDriver.resume(true)
}

//export oto_setErrorByNotification
func oto_setErrorByNotification(s C.OSStatus, from *C.char) {
	gofrom := C.GoString(from)
	theDriver.setError(fmt.Errorf("oto: %s at notification failed: %d", gofrom, s))
}
<commit_msg>ios: Wait for foreground state when the context is created<commit_after>// Copyright 2019 The Oto Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build !js

package oto

// #cgo LDFLAGS: -framework AudioToolbox
//
// #import <AudioToolbox/AudioToolbox.h>
//
// void oto_render(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
//
// void oto_setNotificationHandler(AudioQueueRef audioQueue);
// bool oto_isBackground(void);
import "C"

import (
	"fmt"
	"runtime"
	"sync"
	"time"
	"unsafe"
)

const baseQueueBufferSize = 1024

type audioInfo struct {
	channelNum      int
	bitDepthInBytes int
}

type driver struct {
	audioQueue    C.AudioQueueRef
	buf           []byte
	bufSize       int
	sampleRate    int
	audioInfo     *audioInfo
	buffers       []C.AudioQueueBufferRef
	paused        bool
	lastPauseTime time.Time

	err error

	chWrite   chan []byte
	chWritten chan int

	m sync.Mutex
}

var (
	theDriver *driver
	driverM   sync.Mutex
)

func setDriver(d *driver) {
	driverM.Lock()
	defer driverM.Unlock()

	if theDriver != nil && d != nil {
		panic("oto: at most one driver object can exist")
	}
	theDriver = d

	if d != nil {
		setNotificationHandler(d)
	}
}

func getDriver() *driver {
	driverM.Lock()
	defer driverM.Unlock()

	return theDriver
}

// TOOD: Convert the error code correctly.
// See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful

func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) {
	flags := C.kAudioFormatFlagIsPacked
	if bitDepthInBytes != 1 {
		flags |= C.kAudioFormatFlagIsSignedInteger
	}
	desc := C.AudioStreamBasicDescription{
		mSampleRate:       C.double(sampleRate),
		mFormatID:         C.kAudioFormatLinearPCM,
		mFormatFlags:      C.UInt32(flags),
		mBytesPerPacket:   C.UInt32(channelNum * bitDepthInBytes),
		mFramesPerPacket:  1,
		mBytesPerFrame:    C.UInt32(channelNum * bitDepthInBytes),
		mChannelsPerFrame: C.UInt32(channelNum),
		mBitsPerChannel:   C.UInt32(8 * bitDepthInBytes),
	}

	audioInfo := &audioInfo{
		channelNum:      channelNum,
		bitDepthInBytes: bitDepthInBytes,
	}

	var audioQueue C.AudioQueueRef
	if osstatus := C.AudioQueueNewOutput(
		&desc,
		(C.AudioQueueOutputCallback)(C.oto_render),
		unsafe.Pointer(audioInfo),
		(C.CFRunLoopRef)(0),
		(C.CFStringRef)(0),
		0,
		&audioQueue); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus)
	}

	queueBufferSize := baseQueueBufferSize * channelNum * bitDepthInBytes
	nbuf := bufferSizeInBytes / queueBufferSize
	if nbuf <= 1 {
		nbuf = 2
	}

	d := &driver{
		audioQueue: audioQueue,
		sampleRate: sampleRate,
		audioInfo:  audioInfo,
		bufSize:    nbuf * queueBufferSize,
		buffers:    make([]C.AudioQueueBufferRef, nbuf),
		chWrite:    make(chan []byte),
		chWritten:  make(chan int),
	}
	runtime.SetFinalizer(d, (*driver).Close)
	// Set the driver before setting the rendering callback.
	setDriver(d)

	for i := 0; i < len(d.buffers); i++ {
		if osstatus := C.AudioQueueAllocateBuffer(audioQueue, C.UInt32(queueBufferSize), &d.buffers[i]); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus)
		}
		d.buffers[i].mAudioDataByteSize = C.UInt32(queueBufferSize)
		for j := 0; j < queueBufferSize; j++ {
			*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(d.buffers[i].mAudioData)) + uintptr(j))) = 0
		}
		if osstatus := C.AudioQueueEnqueueBuffer(audioQueue, d.buffers[i], 0, nil); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		}
	}

	for C.oto_isBackground() {
		time.Sleep(time.Second)
	}

	if osstatus := C.AudioQueueStart(audioQueue, nil); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus)
	}

	return d, nil
}

//export oto_render
func oto_render(inUserData unsafe.Pointer, inAQ C.AudioQueueRef, inBuffer C.AudioQueueBufferRef) {
	audioInfo := (*audioInfo)(inUserData)
	queueBufferSize := baseQueueBufferSize * audioInfo.channelNum * audioInfo.bitDepthInBytes

	d := getDriver()

	var buf []byte

	// Set the timer. When the input does not come, the audio must be paused.
	s := time.Second * time.Duration(queueBufferSize) / time.Duration(d.sampleRate*d.audioInfo.channelNum*d.audioInfo.bitDepthInBytes)
	t := time.NewTicker(s)
	defer t.Stop()
	ch := t.C

	for len(buf) < queueBufferSize {
		select {
		case dbuf := <-d.chWrite:
			for !d.resume(false) {
				d.m.Lock()
				err := d.err
				d.m.Unlock()
				if err != nil {
					return
				}

				time.Sleep(time.Second)
			}
			n := queueBufferSize - len(buf)
			if n > len(dbuf) {
				n = len(dbuf)
			}
			buf = append(buf, dbuf[:n]...)
			d.chWritten <- n
		case <-ch:
			d.pause()
			ch = nil
		}
	}

	for i := 0; i < queueBufferSize; i++ {
		*(*byte)(unsafe.Pointer(uintptr(inBuffer.mAudioData) + uintptr(i))) = buf[i]
	}
	// Do not update mAudioDataByteSize, or the buffer is not used correctly any more.

	d.enqueueBuffer(inBuffer)
}

func (d *driver) TryWrite(data []byte) (int, error) {
	d.m.Lock()
	err := d.err
	d.m.Unlock()
	if err != nil {
		return 0, err
	}

	n := d.bufSize - len(d.buf)
	if n > len(data) {
		n = len(data)
	}
	d.buf = append(d.buf, data[:n]...)
	// Use the buffer only when the buffer length is enough to avoid choppy sound.
	queueBufferSize := baseQueueBufferSize * d.audioInfo.channelNum * d.audioInfo.bitDepthInBytes
	for len(d.buf) >= queueBufferSize {
		d.chWrite <- d.buf
		n := <-d.chWritten
		d.buf = d.buf[n:]
	}
	return n, nil
}

func (d *driver) Close() error {
	d.m.Lock()
	defer d.m.Unlock()

	runtime.SetFinalizer(d, nil)

	if osstatus := C.AudioQueueStop(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueStop failed: %d", osstatus)
	}
	if osstatus := C.AudioQueueDispose(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueDispose failed: %d", osstatus)
	}
	d.audioQueue = nil
	setDriver(nil)
	return nil
}

func (d *driver) enqueueBuffer(buffer C.AudioQueueBufferRef) {
	d.m.Lock()
	defer d.m.Unlock()

	if osstatus := C.AudioQueueEnqueueBuffer(d.audioQueue, buffer, 0, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		return
	}
}

func (d *driver) resume(afterSleep bool) bool {
	d.m.Lock()
	defer d.m.Unlock()

	// Audio doesn't work soon after recovering from sleeping. Wait for a while
	// (hajimehoshi/ebiten#1259).
	if afterSleep {
		// After short-time sleeping, 500ms more sleeping is enough. However, after long-time sleeping, it
		// looks like 1 second more sleeping are required (hajimehoshi/ebiten#1280).
		// This is tested on MacBook Pro 2020 macOS 10.15.6.
		if time.Now().Sub(d.lastPauseTime) < 30*time.Second {
			time.Sleep(500 * time.Millisecond)
		} else {
			time.Sleep(time.Second)
		}
	}

	if C.oto_isBackground() {
		return false
	}

	if osstatus := C.AudioQueueStart(d.audioQueue, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueStart for resuming failed: %d", osstatus)
		return false
	}
	d.paused = false
	return true
}

func (d *driver) pause() {
	d.m.Lock()
	defer d.m.Unlock()

	if d.paused {
		return
	}
	if osstatus := C.AudioQueuePause(d.audioQueue); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus)
		return
	}
	d.paused = true
	d.lastPauseTime = time.Now()
}

func (d *driver) setError(err error) {
	d.m.Lock()
	defer d.m.Unlock()

	if theDriver.err != nil {
		return
	}
	theDriver.err = err
}

func setNotificationHandler(driver *driver) {
	C.oto_setNotificationHandler(driver.audioQueue)
}

//export oto_setGlobalPause
func oto_setGlobalPause() {
	theDriver.pause()
}

//export oto_setGlobalResume
func oto_setGlobalResume() {
	theDriver.resume(true)
}

//export oto_setErrorByNotification
func oto_setErrorByNotification(s C.OSStatus, from *C.char) {
	gofrom := C.GoString(from)
	theDriver.setError(fmt.Errorf("oto: %s at notification failed: %d", gofrom, s))
}
<|endoftext|>


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

// +build !js

package oto

// #cgo LDFLAGS: -framework AudioToolbox
//
// #import <AudioToolbox/AudioToolbox.h>
//
// void oto_render(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
//
// void oto_setNotificationHandler(AudioQueueRef audioQueue);
import "C"

import (
	"fmt"
	"runtime"
	"sync"
	"time"
	"unsafe"
)

const baseQueueBufferSize = 1024

type audioInfo struct {
	channelNum      int
	bitDepthInBytes int
}

type driver struct {
	audioQueue    C.AudioQueueRef
	buf           []byte
	bufSize       int
	sampleRate    int
	audioInfo     *audioInfo
	buffers       []C.AudioQueueBufferRef
	paused        bool
	lastPauseTime time.Time

	err error

	chWrite   chan []byte
	chWritten chan int

	m sync.Mutex
}

var (
	theDriver *driver
	driverM   sync.Mutex
)

func setDriver(d *driver) {
	driverM.Lock()
	defer driverM.Unlock()

	if theDriver != nil && d != nil {
		panic("oto: at most one driver object can exist")
	}
	theDriver = d

	if d != nil {
		setNotificationHandler(d)
	}
}

func getDriver() *driver {
	driverM.Lock()
	defer driverM.Unlock()

	return theDriver
}

// TOOD: Convert the error code correctly.
// See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful

func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) {
	flags := C.kAudioFormatFlagIsPacked
	if bitDepthInBytes != 1 {
		flags |= C.kAudioFormatFlagIsSignedInteger
	}
	desc := C.AudioStreamBasicDescription{
		mSampleRate:       C.double(sampleRate),
		mFormatID:         C.kAudioFormatLinearPCM,
		mFormatFlags:      C.UInt32(flags),
		mBytesPerPacket:   C.UInt32(channelNum * bitDepthInBytes),
		mFramesPerPacket:  1,
		mBytesPerFrame:    C.UInt32(channelNum * bitDepthInBytes),
		mChannelsPerFrame: C.UInt32(channelNum),
		mBitsPerChannel:   C.UInt32(8 * bitDepthInBytes),
	}

	audioInfo := &audioInfo{
		channelNum:      channelNum,
		bitDepthInBytes: bitDepthInBytes,
	}

	var audioQueue C.AudioQueueRef
	if osstatus := C.AudioQueueNewOutput(
		&desc,
		(C.AudioQueueOutputCallback)(C.oto_render),
		unsafe.Pointer(audioInfo),
		(C.CFRunLoopRef)(0),
		(C.CFStringRef)(0),
		0,
		&audioQueue); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus)
	}

	queueBufferSize := baseQueueBufferSize * channelNum * bitDepthInBytes
	nbuf := bufferSizeInBytes / queueBufferSize
	if nbuf <= 1 {
		nbuf = 2
	}

	d := &driver{
		audioQueue: audioQueue,
		sampleRate: sampleRate,
		audioInfo:  audioInfo,
		bufSize:    nbuf * queueBufferSize,
		buffers:    make([]C.AudioQueueBufferRef, nbuf),
		chWrite:    make(chan []byte),
		chWritten:  make(chan int),
	}
	runtime.SetFinalizer(d, (*driver).Close)
	// Set the driver before setting the rendering callback.
	setDriver(d)

	for i := 0; i < len(d.buffers); i++ {
		if osstatus := C.AudioQueueAllocateBuffer(audioQueue, C.UInt32(queueBufferSize), &d.buffers[i]); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus)
		}
		d.buffers[i].mAudioDataByteSize = C.UInt32(queueBufferSize)
		for j := 0; j < queueBufferSize; j++ {
			*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(d.buffers[i].mAudioData)) + uintptr(j))) = 0
		}
		if osstatus := C.AudioQueueEnqueueBuffer(audioQueue, d.buffers[i], 0, nil); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		}
	}

	if osstatus := C.AudioQueueStart(audioQueue, nil); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus)
	}

	return d, nil
}

//export oto_render
func oto_render(inUserData unsafe.Pointer, inAQ C.AudioQueueRef, inBuffer C.AudioQueueBufferRef) {
	audioInfo := (*audioInfo)(inUserData)
	queueBufferSize := baseQueueBufferSize * audioInfo.channelNum * audioInfo.bitDepthInBytes

	d := getDriver()

	var buf []byte

	// Set the timer. When the input does not come, the audio must be paused.
	s := time.Second * time.Duration(queueBufferSize) / time.Duration(d.sampleRate*d.audioInfo.channelNum*d.audioInfo.bitDepthInBytes)
	t := time.NewTicker(s)
	defer t.Stop()
	ch := t.C

	for len(buf) < queueBufferSize {
		select {
		case dbuf := <-d.chWrite:
			d.resume(false)
			n := queueBufferSize - len(buf)
			if n > len(dbuf) {
				n = len(dbuf)
			}
			buf = append(buf, dbuf[:n]...)
			d.chWritten <- n
		case <-ch:
			d.pause()
			ch = nil
		}
	}

	for i := 0; i < queueBufferSize; i++ {
		*(*byte)(unsafe.Pointer(uintptr(inBuffer.mAudioData) + uintptr(i))) = buf[i]
	}
	// Do not update mAudioDataByteSize, or the buffer is not used correctly any more.

	d.enqueueBuffer(inBuffer)
}

func (d *driver) TryWrite(data []byte) (int, error) {
	d.m.Lock()
	err := d.err
	d.m.Unlock()
	if err != nil {
		return 0, err
	}

	n := d.bufSize - len(d.buf)
	if n > len(data) {
		n = len(data)
	}
	d.buf = append(d.buf, data[:n]...)
	// Use the buffer only when the buffer length is enough to avoid choppy sound.
	queueBufferSize := baseQueueBufferSize * d.audioInfo.channelNum * d.audioInfo.bitDepthInBytes
	for len(d.buf) >= queueBufferSize {
		d.chWrite <- d.buf
		n := <-d.chWritten
		d.buf = d.buf[n:]
	}
	return n, nil
}

func (d *driver) Close() error {
	runtime.SetFinalizer(d, nil)

	if osstatus := C.AudioQueueStop(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueStop failed: %d", osstatus)
	}
	if osstatus := C.AudioQueueDispose(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueDispose failed: %d", osstatus)
	}
	d.audioQueue = nil
	setDriver(nil)
	return nil
}

func (d *driver) enqueueBuffer(buffer C.AudioQueueBufferRef) {
	d.m.Lock()
	defer d.m.Unlock()

	if osstatus := C.AudioQueueEnqueueBuffer(d.audioQueue, buffer, 0, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		return
	}
}

func (d *driver) resume(afterSleep bool) {
	d.m.Lock()
	defer d.m.Unlock()

	// Audio doesn't work soon after recovering from sleeping. Wait for a while
	// (hajimehoshi/ebiten#1259).
	if afterSleep {
		// After short-time sleeping, 500ms more sleeping is enough. However, after long-time sleeping, it
		// looks like 1 second more sleeping are required (hajimehoshi/ebiten#1280).
		// This is tested on MacBook Pro 2020 macOS 10.15.6.
		if time.Now().Sub(d.lastPauseTime) < 30*time.Second {
			time.Sleep(500 * time.Millisecond)
		} else {
			time.Sleep(time.Second)
		}
	}

	if !d.paused {
		return
	}
	if osstatus := C.AudioQueueStart(d.audioQueue, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus)
		return
	}
	d.paused = false
}

func (d *driver) pause() {
	d.m.Lock()
	defer d.m.Unlock()

	if d.paused {
		return
	}
	if osstatus := C.AudioQueuePause(d.audioQueue); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus)
		return
	}
	d.paused = true
	d.lastPauseTime = time.Now()
}

func setNotificationHandler(driver *driver) {
	C.oto_setNotificationHandler(driver.audioQueue)
}

//export oto_setGlobalPause
func oto_setGlobalPause(paused C.int) {
	if paused != 0 {
		theDriver.pause()
	} else {
		theDriver.resume(true)
	}
}

//export oto_setErrorByNotification
func oto_setErrorByNotification(s C.OSStatus, from *C.char) {
	if theDriver.err != nil {
		return
	}

	gofrom := C.GoString(from)
	theDriver.err = fmt.Errorf("oto: %s at notification failed: %d", gofrom, s)
}
<commit_msg>darwin: Use mutex at setError<commit_after>// Copyright 2019 The Oto Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build !js

package oto

// #cgo LDFLAGS: -framework AudioToolbox
//
// #import <AudioToolbox/AudioToolbox.h>
//
// void oto_render(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
//
// void oto_setNotificationHandler(AudioQueueRef audioQueue);
import "C"

import (
	"fmt"
	"runtime"
	"sync"
	"time"
	"unsafe"
)

const baseQueueBufferSize = 1024

type audioInfo struct {
	channelNum      int
	bitDepthInBytes int
}

type driver struct {
	audioQueue    C.AudioQueueRef
	buf           []byte
	bufSize       int
	sampleRate    int
	audioInfo     *audioInfo
	buffers       []C.AudioQueueBufferRef
	paused        bool
	lastPauseTime time.Time

	err error

	chWrite   chan []byte
	chWritten chan int

	m sync.Mutex
}

var (
	theDriver *driver
	driverM   sync.Mutex
)

func setDriver(d *driver) {
	driverM.Lock()
	defer driverM.Unlock()

	if theDriver != nil && d != nil {
		panic("oto: at most one driver object can exist")
	}
	theDriver = d

	if d != nil {
		setNotificationHandler(d)
	}
}

func getDriver() *driver {
	driverM.Lock()
	defer driverM.Unlock()

	return theDriver
}

// TOOD: Convert the error code correctly.
// See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful

func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) {
	flags := C.kAudioFormatFlagIsPacked
	if bitDepthInBytes != 1 {
		flags |= C.kAudioFormatFlagIsSignedInteger
	}
	desc := C.AudioStreamBasicDescription{
		mSampleRate:       C.double(sampleRate),
		mFormatID:         C.kAudioFormatLinearPCM,
		mFormatFlags:      C.UInt32(flags),
		mBytesPerPacket:   C.UInt32(channelNum * bitDepthInBytes),
		mFramesPerPacket:  1,
		mBytesPerFrame:    C.UInt32(channelNum * bitDepthInBytes),
		mChannelsPerFrame: C.UInt32(channelNum),
		mBitsPerChannel:   C.UInt32(8 * bitDepthInBytes),
	}

	audioInfo := &audioInfo{
		channelNum:      channelNum,
		bitDepthInBytes: bitDepthInBytes,
	}

	var audioQueue C.AudioQueueRef
	if osstatus := C.AudioQueueNewOutput(
		&desc,
		(C.AudioQueueOutputCallback)(C.oto_render),
		unsafe.Pointer(audioInfo),
		(C.CFRunLoopRef)(0),
		(C.CFStringRef)(0),
		0,
		&audioQueue); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus)
	}

	queueBufferSize := baseQueueBufferSize * channelNum * bitDepthInBytes
	nbuf := bufferSizeInBytes / queueBufferSize
	if nbuf <= 1 {
		nbuf = 2
	}

	d := &driver{
		audioQueue: audioQueue,
		sampleRate: sampleRate,
		audioInfo:  audioInfo,
		bufSize:    nbuf * queueBufferSize,
		buffers:    make([]C.AudioQueueBufferRef, nbuf),
		chWrite:    make(chan []byte),
		chWritten:  make(chan int),
	}
	runtime.SetFinalizer(d, (*driver).Close)
	// Set the driver before setting the rendering callback.
	setDriver(d)

	for i := 0; i < len(d.buffers); i++ {
		if osstatus := C.AudioQueueAllocateBuffer(audioQueue, C.UInt32(queueBufferSize), &d.buffers[i]); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus)
		}
		d.buffers[i].mAudioDataByteSize = C.UInt32(queueBufferSize)
		for j := 0; j < queueBufferSize; j++ {
			*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(d.buffers[i].mAudioData)) + uintptr(j))) = 0
		}
		if osstatus := C.AudioQueueEnqueueBuffer(audioQueue, d.buffers[i], 0, nil); osstatus != C.noErr {
			return nil, fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		}
	}

	if osstatus := C.AudioQueueStart(audioQueue, nil); osstatus != C.noErr {
		return nil, fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus)
	}

	return d, nil
}

//export oto_render
func oto_render(inUserData unsafe.Pointer, inAQ C.AudioQueueRef, inBuffer C.AudioQueueBufferRef) {
	audioInfo := (*audioInfo)(inUserData)
	queueBufferSize := baseQueueBufferSize * audioInfo.channelNum * audioInfo.bitDepthInBytes

	d := getDriver()

	var buf []byte

	// Set the timer. When the input does not come, the audio must be paused.
	s := time.Second * time.Duration(queueBufferSize) / time.Duration(d.sampleRate*d.audioInfo.channelNum*d.audioInfo.bitDepthInBytes)
	t := time.NewTicker(s)
	defer t.Stop()
	ch := t.C

	for len(buf) < queueBufferSize {
		select {
		case dbuf := <-d.chWrite:
			d.resume(false)
			n := queueBufferSize - len(buf)
			if n > len(dbuf) {
				n = len(dbuf)
			}
			buf = append(buf, dbuf[:n]...)
			d.chWritten <- n
		case <-ch:
			d.pause()
			ch = nil
		}
	}

	for i := 0; i < queueBufferSize; i++ {
		*(*byte)(unsafe.Pointer(uintptr(inBuffer.mAudioData) + uintptr(i))) = buf[i]
	}
	// Do not update mAudioDataByteSize, or the buffer is not used correctly any more.

	d.enqueueBuffer(inBuffer)
}

func (d *driver) TryWrite(data []byte) (int, error) {
	d.m.Lock()
	err := d.err
	d.m.Unlock()
	if err != nil {
		return 0, err
	}

	n := d.bufSize - len(d.buf)
	if n > len(data) {
		n = len(data)
	}
	d.buf = append(d.buf, data[:n]...)
	// Use the buffer only when the buffer length is enough to avoid choppy sound.
	queueBufferSize := baseQueueBufferSize * d.audioInfo.channelNum * d.audioInfo.bitDepthInBytes
	for len(d.buf) >= queueBufferSize {
		d.chWrite <- d.buf
		n := <-d.chWritten
		d.buf = d.buf[n:]
	}
	return n, nil
}

func (d *driver) Close() error {
	runtime.SetFinalizer(d, nil)

	if osstatus := C.AudioQueueStop(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueStop failed: %d", osstatus)
	}
	if osstatus := C.AudioQueueDispose(d.audioQueue, C.false); osstatus != C.noErr {
		return fmt.Errorf("oto: AudioQueueDispose failed: %d", osstatus)
	}
	d.audioQueue = nil
	setDriver(nil)
	return nil
}

func (d *driver) enqueueBuffer(buffer C.AudioQueueBufferRef) {
	d.m.Lock()
	defer d.m.Unlock()

	if osstatus := C.AudioQueueEnqueueBuffer(d.audioQueue, buffer, 0, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)
		return
	}
}

func (d *driver) resume(afterSleep bool) {
	d.m.Lock()
	defer d.m.Unlock()

	// Audio doesn't work soon after recovering from sleeping. Wait for a while
	// (hajimehoshi/ebiten#1259).
	if afterSleep {
		// After short-time sleeping, 500ms more sleeping is enough. However, after long-time sleeping, it
		// looks like 1 second more sleeping are required (hajimehoshi/ebiten#1280).
		// This is tested on MacBook Pro 2020 macOS 10.15.6.
		if time.Now().Sub(d.lastPauseTime) < 30*time.Second {
			time.Sleep(500 * time.Millisecond)
		} else {
			time.Sleep(time.Second)
		}
	}

	if !d.paused {
		return
	}
	if osstatus := C.AudioQueueStart(d.audioQueue, nil); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus)
		return
	}
	d.paused = false
}

func (d *driver) pause() {
	d.m.Lock()
	defer d.m.Unlock()

	if d.paused {
		return
	}
	if osstatus := C.AudioQueuePause(d.audioQueue); osstatus != C.noErr && d.err == nil {
		d.err = fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus)
		return
	}
	d.paused = true
	d.lastPauseTime = time.Now()
}

func (d *driver) setError(err error) {
	d.m.Lock()
	defer d.m.Unlock()

	if theDriver.err != nil {
		return
	}
	theDriver.err = err
}

func setNotificationHandler(driver *driver) {
	C.oto_setNotificationHandler(driver.audioQueue)
}

//export oto_setGlobalPause
func oto_setGlobalPause(paused C.int) {
	if paused != 0 {
		theDriver.pause()
	} else {
		theDriver.resume(true)
	}
}

//export oto_setErrorByNotification
func oto_setErrorByNotification(s C.OSStatus, from *C.char) {
	gofrom := C.GoString(from)
	theDriver.setError(fmt.Errorf("oto: %s at notification failed: %d", gofrom, s))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/dimfeld/goconfig"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"text/template"
)

type Hook struct {
	// URL at which this hook should be available.
	Url string
	// Dir is the working directory from which the command should be run.
	// If blank, the current working directory is used.
	Dir string
	// Env is a list of environment variables to set. If empty, the current
	// environment is used. Each item takes the form "key=value"
	Env []string

	// If PerCommit is true, call the hook once for each commit in the message.
	// Otherwise it is just called once per message.
	PerCommit bool

	// If empty, all events are accepted.
	AllowEvent []string

	// Commands to run.
	Commands [][]string

	// Override the default timeout.
	Timeout int

	template [][]*template.Template
}

type Hooks struct {
	Hook []*Hook
}

type Config struct {
	ListenAddress string

	DebugMode bool

	// The maximum amount of time to wait for a command to finish.
	// Default is 5 seconds.
	CommandTimeout int

	// Accept connections from only the given IP addresses.
	AcceptIp []string

	LogFile   string
	LogPrefix string

	// Paths to search for hook files
	HookPath []string

	Hook []*Hook
}

var (
	logger    *log.Logger
	debugMode bool
)

func debugf(format string, args ...interface{}) {
	if debugMode {
		logger.Printf(format, args...)
	}
}

func debug(args ...interface{}) {
	if debugMode {
		logger.Println(args...)
	}
}

func (c *Config) MergeHooks(other Hooks) {
	c.Hook = append(c.Hook, other.Hook...)
}

func (c *Config) AddHookFile(file string) {
	h := Hooks{}

	f := os.Stdin

	if file == "-" {
		// Change file here so that any error messages will look better.
		file = "stdin"
	} else {
		f, err := os.Open(file)
		if err != nil {
			logger.Printf("Error loading %s: %s", file, err)
			return
		}
		defer f.Close()
	}

	_, err := toml.DecodeReader(f, h)
	if err != nil {
		logger.Printf("Error loading %s: %s", file, err)
		return
	}

	c.MergeHooks(h)
}

func (c *Config) AddHookPath(p string) {
	info, err := os.Stat(p)
	if err != nil {
		logger.Printf("Error loading %s: %s", p, err)
		return
	}

	if info.IsDir() {
		filepath.Walk(p,
			func(path string, info os.FileInfo, err error) error {
				if err != nil {
					logger.Printf("Error loading %s, %s", path, err)
					return err
				}
				if info.IsDir() {
					return nil
				}

				c.AddHookFile(path)
				return nil
			})
	} else {
		c.AddHookFile(p)
	}
}

func catchSIGINT(f func(), quit bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			logger.Println("SIGINT received...")
			f()
			if quit {
				os.Exit(1)
			}
		}
	}()
}

func main() {
	config := &Config{
		ListenAddress:  ":80",
		CommandTimeout: 5,
	}

	mainConfigPath := os.Getenv("WEBHOOK_CONFFILE")
	hooksStartIndex := 1
	if mainConfigPath == "" {
		if len(os.Args) > 1 {
			mainConfigPath = os.Args[1]
		} else {
			mainConfigPath = os.Args[0] + ".conf"
		}
		hooksStartIndex = 2
	}

	if mainConfigPath == "-" {
		goconfig.Load(config, os.Stdin, "WEBHOOK")
	} else {
		f, err := os.Open(mainConfigPath)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to open config file %s: %s",
				mainConfigPath, err)
			os.Exit(1)
		}
		err = goconfig.Load(config, f, "WEBHOOK")
		f.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading config file %s: %s",
				mainConfigPath, err)
			os.Exit(1)
		}
	}

	logFile, err := os.OpenFile(config.LogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0640)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Could not open log file %s\n", config.LogFile)
		os.Exit(1)
	}

	logger = log.New(logFile, config.LogPrefix, log.LstdFlags)

	debugMode = config.DebugMode

	for _, h := range config.HookPath {
		config.AddHookPath(h)
	}

	if len(os.Args) > hooksStartIndex {
		for _, arg := range os.Args[hooksStartIndex:] {
			config.AddHookPath(arg)
		}
	}

	closer := func() {
		logFile.Close()
	}
	catchSIGINT(closer, true)
	defer closer()

	failed := false
	for _, h := range config.Hook {
		if h.Timeout == 0 {
			h.Timeout = config.CommandTimeout
		}

		err := h.CreateTemplates()
		if err != nil {
			logger.Printf("Failed parsing template %s: %s", h.Url, err)
			failed = true
		}
	}

	if failed {
		os.Exit(1)
	}

	RunServer(config)
}
<commit_msg>HookPath -> HookPaths<commit_after>package main

import (
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/dimfeld/goconfig"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"text/template"
)

type Hook struct {
	// URL at which this hook should be available.
	Url string
	// Dir is the working directory from which the command should be run.
	// If blank, the current working directory is used.
	Dir string
	// Env is a list of environment variables to set. If empty, the current
	// environment is used. Each item takes the form "key=value"
	Env []string

	// If PerCommit is true, call the hook once for each commit in the message.
	// Otherwise it is just called once per message.
	PerCommit bool

	// If empty, all events are accepted.
	AllowEvent []string

	// Commands to run.
	Commands [][]string

	// Override the default timeout.
	Timeout int

	template [][]*template.Template
}

type Hooks struct {
	Hook []*Hook
}

type Config struct {
	ListenAddress string

	DebugMode bool

	// The maximum amount of time to wait for a command to finish.
	// Default is 5 seconds.
	CommandTimeout int

	// Accept connections from only the given IP addresses.
	AcceptIp []string

	LogFile   string
	LogPrefix string

	// Paths to search for hook files
	HookPaths []string

	Hook []*Hook
}

var (
	logger    *log.Logger
	debugMode bool
)

func debugf(format string, args ...interface{}) {
	if debugMode {
		logger.Printf(format, args...)
	}
}

func debug(args ...interface{}) {
	if debugMode {
		logger.Println(args...)
	}
}

func (c *Config) MergeHooks(other Hooks) {
	c.Hook = append(c.Hook, other.Hook...)
}

func (c *Config) AddHookFile(file string) {
	h := Hooks{}

	f := os.Stdin

	if file == "-" {
		// Change file here so that any error messages will look better.
		file = "stdin"
	} else {
		f, err := os.Open(file)
		if err != nil {
			logger.Printf("Error loading %s: %s", file, err)
			return
		}
		defer f.Close()
	}

	_, err := toml.DecodeReader(f, h)
	if err != nil {
		logger.Printf("Error loading %s: %s", file, err)
		return
	}

	c.MergeHooks(h)
}

func (c *Config) AddHookPath(p string) {
	info, err := os.Stat(p)
	if err != nil {
		logger.Printf("Error loading %s: %s", p, err)
		return
	}

	if info.IsDir() {
		filepath.Walk(p,
			func(path string, info os.FileInfo, err error) error {
				if err != nil {
					logger.Printf("Error loading %s, %s", path, err)
					return err
				}
				if info.IsDir() {
					return nil
				}

				c.AddHookFile(path)
				return nil
			})
	} else {
		c.AddHookFile(p)
	}
}

func catchSIGINT(f func(), quit bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			logger.Println("SIGINT received...")
			f()
			if quit {
				os.Exit(1)
			}
		}
	}()
}

func main() {
	config := &Config{
		ListenAddress:  ":80",
		CommandTimeout: 5,
	}

	mainConfigPath := os.Getenv("WEBHOOK_CONFFILE")
	hooksStartIndex := 1
	if mainConfigPath == "" {
		if len(os.Args) > 1 {
			mainConfigPath = os.Args[1]
		} else {
			mainConfigPath = os.Args[0] + ".conf"
		}
		hooksStartIndex = 2
	}

	if mainConfigPath == "-" {
		goconfig.Load(config, os.Stdin, "WEBHOOK")
	} else {
		f, err := os.Open(mainConfigPath)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to open config file %s: %s",
				mainConfigPath, err)
			os.Exit(1)
		}
		err = goconfig.Load(config, f, "WEBHOOK")
		f.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading config file %s: %s",
				mainConfigPath, err)
			os.Exit(1)
		}
	}

	logFile, err := os.OpenFile(config.LogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0640)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Could not open log file %s\n", config.LogFile)
		os.Exit(1)
	}

	logger = log.New(logFile, config.LogPrefix, log.LstdFlags)

	debugMode = config.DebugMode

	for _, h := range config.HookPaths {
		config.AddHookPath(h)
	}

	if len(os.Args) > hooksStartIndex {
		for _, arg := range os.Args[hooksStartIndex:] {
			config.AddHookPath(arg)
		}
	}

	closer := func() {
		logFile.Close()
	}
	catchSIGINT(closer, true)
	defer closer()

	failed := false
	for _, h := range config.Hook {
		if h.Timeout == 0 {
			h.Timeout = config.CommandTimeout
		}

		err := h.CreateTemplates()
		if err != nil {
			logger.Printf("Failed parsing template %s: %s", h.Url, err)
			failed = true
		}
	}

	if failed {
		os.Exit(1)
	}

	RunServer(config)
}
<|endoftext|>


<commit_before>package artifactory

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"time"
)

// NewClient returns a new Artifactory client with the given Config
func NewClient(config Config) Client {
	return DefaultClient{
		config: config,
	}
}

// CreateSnapshotRepository creates a snapshot repository with the given ID.  If the repository creation failed for reasons of transport failure,
// an error is returned.  If the repository creation failed for other business reasons, *HTTPStatus will have the details.
func (c DefaultClient) CreateSnapshotRepository(repositoryID string) (*HTTPStatus, error) {
	repoConfig := LocalRepositoryConfiguration{
		Key:                     repositoryID,
		RClass:                  "local",
		Notes:                   "Created via automation with https://github.com/ae6rt/artifactory Go client [" + time.Now().String() + "]",
		PackageType:             "maven",
		RepoLayoutRef:           "maven-2-default",
		HandleSnapshots:         true,
		HandleReleases:          false,
		MaxUniqueSnapshots:      0,
		SnapshotVersionBehavior: "unique",
	}

	serial, err := json.Marshal(&repoConfig)
	if err != nil {
		return &HTTPStatus{}, err
	}

	req, err := http.NewRequest("PUT", c.config.BaseURL+"/api/repositories/"+repositoryID, bytes.NewBuffer(serial))
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	req.Header.Set("Accept", "*/*")
	req.Header.Set("Content-type", "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json")

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{StatusCode: response.StatusCode, Entity: data}, nil
	}

	return nil, nil
}

// GetVirtualRepositoryConfiguration returns the configuration of the given virtual repository.
func (c DefaultClient) GetVirtualRepositoryConfiguration(repositoryID string) (VirtualRepositoryConfiguration, error) {

	req, err := http.NewRequest("GET", c.config.BaseURL+"/api/repositories/"+repositoryID, nil)
	if err != nil {
		return VirtualRepositoryConfiguration{}, err
	}
	c.setAuthHeaders(req)

	req.Header.Set("Accept", "application/vnd.org.jfrog.artifactory.repositories.VirtualRepositoryConfiguration+json")

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return VirtualRepositoryConfiguration{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return VirtualRepositoryConfiguration{}, err
	}

	if response.StatusCode/100 == 5 {
		return VirtualRepositoryConfiguration{}, http500{data}
	}

	if response.StatusCode != 200 {
		return VirtualRepositoryConfiguration{HTTPStatus: &HTTPStatus{StatusCode: response.StatusCode, Entity: data}}, nil
	}

	var virtualRepository VirtualRepositoryConfiguration
	err = json.Unmarshal(data, &virtualRepository)
	return virtualRepository, err
}

// LocalRepositoryExists returns whether the given local repository exists.
func (c DefaultClient) LocalRepositoryExists(repositoryID string) (bool, error) {

	// https://www.jfrog.com/jira/browse/RTFACT-9998?filter=-2
	req, err := http.NewRequest("HEAD", c.config.BaseURL+"/api/repositories/"+repositoryID, nil)
	if err != nil {
		return false, err
	}

	req.Header.Set("Accept", "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json")
	c.setAuthHeaders(req)

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return false, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return false, err
	}

	if response.StatusCode/100 == 5 {
		return false, http500{data}
	}

	return response.StatusCode == 200, nil
}

// RemoveRepository removes the given repository.  Check error for transport or marshaling errors.  Check HTTPStatus for other business errors.
func (c DefaultClient) RemoveRepository(repositoryID string) (*HTTPStatus, error) {
	req, err := http.NewRequest("DELETE", c.config.BaseURL+"/api/repositories/"+repositoryID, nil)
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode/100 == 5 {
		return &HTTPStatus{StatusCode: response.StatusCode, Entity: data}, http500{data}
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{response.StatusCode, data}, nil
	}

	return nil, nil
}

// RemoveItemFromRepository removes the given item from a repository.  Check error for transport or marshaling errors.
// Check HTTPStatus for other business errors.
func (c DefaultClient) RemoveItemFromRepository(repositoryID, item string) (*HTTPStatus, error) {
	if item == "" {
		panic("Refusing to remove an item of zero length.")
	}

	req, err := http.NewRequest("DELETE", c.config.BaseURL+"/api/repositories/"+repositoryID+"/"+item, nil)
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode/100 == 5 {
		return &HTTPStatus{StatusCode: response.StatusCode, Entity: data}, http500{data}
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{response.StatusCode, data}, nil
	}

	return nil, nil
}

// AddLocalRepositoryToGroup adds the given local repository to a virtual repository.  Check error for transport or marshaling errors.
// Check HTTPStatus for other business errors.
func (c DefaultClient) AddLocalRepositoryToGroup(virtualRepositoryID, localRepositoryID string) (*HTTPStatus, error) {
	r, err := c.GetVirtualRepositoryConfiguration(virtualRepositoryID)
	if err != nil {
		return nil, err
	}
	if r.HTTPStatus != nil {
		return r.HTTPStatus, nil
	}

	if contains(r.Repositories, localRepositoryID) {
		return nil, nil
	}

	r.Repositories = append(r.Repositories, localRepositoryID)

	return c.updateVirtualRepository(r)
}

// RemoveLocalRepositoryFromGroup removes the given local repository to a virtual repository.  Check error for transport or marshaling errors.
// Check HTTPStatus for other business errors.
func (c DefaultClient) RemoveLocalRepositoryFromGroup(virtualRepositoryID, localRepositoryID string) (*HTTPStatus, error) {
	r, err := c.GetVirtualRepositoryConfiguration(virtualRepositoryID)
	if err != nil {
		return nil, err
	}
	if r.HTTPStatus != nil {
		return r.HTTPStatus, nil
	}

	if !contains(r.Repositories, localRepositoryID) {
		return nil, nil
	}

	r.Repositories = remove(r.Repositories, localRepositoryID)

	return c.updateVirtualRepository(r)
}

func (h http500) Error() string {
	return string(h.httpEntity)
}

func contains(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

func remove(arr []string, removeIt string) []string {
	var t []string
	for _, v := range arr {
		if v == removeIt {
			continue
		}
		t = append(t, v)
	}
	return t
}

func (c DefaultClient) updateVirtualRepository(r VirtualRepositoryConfiguration) (*HTTPStatus, error) {
	serial, err := json.Marshal(&r)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", c.config.BaseURL+"/api/repositories/"+r.Key, bytes.NewBuffer(serial))
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	req.Header.Set("Accept", "*/*")
	// The Content-type prescribed by the API docs doesn't work:  https://www.jfrog.com/jira/browse/RTFACT-10035?filter=-2
	req.Header.Set("Content-type", "application/json")

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{response.StatusCode, data}, nil
	}
	return nil, nil
}

func (c DefaultClient) setAuthHeaders(req *http.Request) {
	if c.config.APIKey != "" {
		req.Header.Set("X-JFrog-Art-Api", c.config.APIKey)
	} else {
		req.SetBasicAuth(c.config.Username, c.config.Password)
	}
}
<commit_msg>comments<commit_after>package artifactory

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"time"
)

// NewClient returns a new Artifactory client with the given Config
func NewClient(config Config) Client {
	return DefaultClient{
		config: config,
	}
}

// CreateSnapshotRepository creates a snapshot repository with the given ID.  If the repository creation failed for reasons of transport failure,
// an error is returned.  If the repository creation failed for other business reasons, *HTTPStatus will have the details.
func (c DefaultClient) CreateSnapshotRepository(repositoryID string) (*HTTPStatus, error) {
	repoConfig := LocalRepositoryConfiguration{
		Key:                     repositoryID,
		RClass:                  "local",
		Notes:                   "Created via automation with https://github.com/ae6rt/artifactory Go client [" + time.Now().String() + "]",
		PackageType:             "maven",
		RepoLayoutRef:           "maven-2-default",
		HandleSnapshots:         true,
		HandleReleases:          false,
		MaxUniqueSnapshots:      0,
		SnapshotVersionBehavior: "unique",
	}

	serial, err := json.Marshal(&repoConfig)
	if err != nil {
		return &HTTPStatus{}, err
	}

	req, err := http.NewRequest("PUT", c.config.BaseURL+"/api/repositories/"+repositoryID, bytes.NewBuffer(serial))
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	req.Header.Set("Accept", "*/*")
	req.Header.Set("Content-type", "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json")

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{StatusCode: response.StatusCode, Entity: data}, nil
	}

	return nil, nil
}

// GetVirtualRepositoryConfiguration returns the configuration of the given virtual repository.
func (c DefaultClient) GetVirtualRepositoryConfiguration(repositoryID string) (VirtualRepositoryConfiguration, error) {

	req, err := http.NewRequest("GET", c.config.BaseURL+"/api/repositories/"+repositoryID, nil)
	if err != nil {
		return VirtualRepositoryConfiguration{}, err
	}
	c.setAuthHeaders(req)

	req.Header.Set("Accept", "application/vnd.org.jfrog.artifactory.repositories.VirtualRepositoryConfiguration+json")

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return VirtualRepositoryConfiguration{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return VirtualRepositoryConfiguration{}, err
	}

	if response.StatusCode/100 == 5 {
		return VirtualRepositoryConfiguration{}, http500{data}
	}

	if response.StatusCode != 200 {
		return VirtualRepositoryConfiguration{HTTPStatus: &HTTPStatus{StatusCode: response.StatusCode, Entity: data}}, nil
	}

	var virtualRepository VirtualRepositoryConfiguration
	err = json.Unmarshal(data, &virtualRepository)
	return virtualRepository, err
}

// LocalRepositoryExists returns whether the given local repository exists.
func (c DefaultClient) LocalRepositoryExists(repositoryID string) (bool, error) {

	// https://www.jfrog.com/jira/browse/RTFACT-9998
	req, err := http.NewRequest("HEAD", c.config.BaseURL+"/api/repositories/"+repositoryID, nil)
	if err != nil {
		return false, err
	}

	req.Header.Set("Accept", "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json")
	c.setAuthHeaders(req)

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return false, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return false, err
	}

	if response.StatusCode/100 == 5 {
		return false, http500{data}
	}

	return response.StatusCode == 200, nil
}

// RemoveRepository removes the given repository.  Check error for transport or marshaling errors.  Check HTTPStatus for other business errors.
func (c DefaultClient) RemoveRepository(repositoryID string) (*HTTPStatus, error) {
	req, err := http.NewRequest("DELETE", c.config.BaseURL+"/api/repositories/"+repositoryID, nil)
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode/100 == 5 {
		return &HTTPStatus{StatusCode: response.StatusCode, Entity: data}, http500{data}
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{response.StatusCode, data}, nil
	}

	return nil, nil
}

// RemoveItemFromRepository removes the given item from a repository.  Check error for transport or marshaling errors.
// Check HTTPStatus for other business errors.
func (c DefaultClient) RemoveItemFromRepository(repositoryID, item string) (*HTTPStatus, error) {
	if item == "" {
		panic("Refusing to remove an item of zero length.")
	}

	req, err := http.NewRequest("DELETE", c.config.BaseURL+"/api/repositories/"+repositoryID+"/"+item, nil)
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode/100 == 5 {
		return &HTTPStatus{StatusCode: response.StatusCode, Entity: data}, http500{data}
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{response.StatusCode, data}, nil
	}

	return nil, nil
}

// AddLocalRepositoryToGroup adds the given local repository to a virtual repository.  Check error for transport or marshaling errors.
// Check HTTPStatus for other business errors.
func (c DefaultClient) AddLocalRepositoryToGroup(virtualRepositoryID, localRepositoryID string) (*HTTPStatus, error) {
	r, err := c.GetVirtualRepositoryConfiguration(virtualRepositoryID)
	if err != nil {
		return nil, err
	}
	if r.HTTPStatus != nil {
		return r.HTTPStatus, nil
	}

	if contains(r.Repositories, localRepositoryID) {
		return nil, nil
	}

	r.Repositories = append(r.Repositories, localRepositoryID)

	return c.updateVirtualRepository(r)
}

// RemoveLocalRepositoryFromGroup removes the given local repository to a virtual repository.  Check error for transport or marshaling errors.
// Check HTTPStatus for other business errors.
func (c DefaultClient) RemoveLocalRepositoryFromGroup(virtualRepositoryID, localRepositoryID string) (*HTTPStatus, error) {
	r, err := c.GetVirtualRepositoryConfiguration(virtualRepositoryID)
	if err != nil {
		return nil, err
	}
	if r.HTTPStatus != nil {
		return r.HTTPStatus, nil
	}

	if !contains(r.Repositories, localRepositoryID) {
		return nil, nil
	}

	r.Repositories = remove(r.Repositories, localRepositoryID)

	return c.updateVirtualRepository(r)
}

func (h http500) Error() string {
	return string(h.httpEntity)
}

func contains(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

func remove(arr []string, removeIt string) []string {
	var t []string
	for _, v := range arr {
		if v == removeIt {
			continue
		}
		t = append(t, v)
	}
	return t
}

func (c DefaultClient) updateVirtualRepository(r VirtualRepositoryConfiguration) (*HTTPStatus, error) {
	serial, err := json.Marshal(&r)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", c.config.BaseURL+"/api/repositories/"+r.Key, bytes.NewBuffer(serial))
	if err != nil {
		return &HTTPStatus{}, err
	}
	c.setAuthHeaders(req)

	req.Header.Set("Accept", "*/*")
	// The Content-type prescribed by the API docs doesn't work:  https://www.jfrog.com/jira/browse/RTFACT-10035
	req.Header.Set("Content-type", "application/json")

	response, err := c.config.Doer.Do(req)
	if err != nil {
		return &HTTPStatus{}, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return &HTTPStatus{response.StatusCode, data}, nil
	}
	return nil, nil
}

func (c DefaultClient) setAuthHeaders(req *http.Request) {
	if c.config.APIKey != "" {
		req.Header.Set("X-JFrog-Art-Api", c.config.APIKey)
	} else {
		req.SetBasicAuth(c.config.Username, c.config.Password)
	}
}
<|endoftext|>