<commit_before>package registry

import (
	"bytes"
	"io/ioutil"
	"net/http"

	"github.com/docker/distribution/digest"
	manifestV1 "github.com/docker/distribution/manifest/schema1"
	manifestV2 "github.com/docker/distribution/manifest/schema2"
)

func (registry *Registry) Manifest(repository, reference string) (*manifestV1.SignedManifest, error) {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.get url=%s repository=%s reference=%s", url, repository, reference)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Accept", manifestV1.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	signedManifest := &manifestV1.SignedManifest{}
	err = signedManifest.UnmarshalJSON(body)
	if err != nil {
		return nil, err
	}

	return signedManifest, nil
}

func (registry *Registry) ManifestV2(repository, reference string) (*manifestV2.DeserializedManifest, error) {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.get url=%s repository=%s reference=%s", url, repository, reference)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Accept", manifestV2.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	deserialized := &manifestV2.DeserializedManifest{}
	err = deserialized.UnmarshalJSON(body)
	if err != nil {
		return nil, err
	}
	return deserialized, nil
}

func (registry *Registry) ManifestDigest(repository, reference string) (digest.Digest, error) {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.head url=%s repository=%s reference=%s", url, repository, reference)

	resp, err := registry.Client.Head(url)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return "", err
	}
	return digest.ParseDigest(resp.Header.Get("Docker-Content-Digest"))
}

func (registry *Registry) DeleteManifest(repository string, digest digest.Digest) error {
	url := registry.url("/v2/%s/manifests/%s", repository, digest)
	registry.Logf("registry.manifest.delete url=%s repository=%s reference=%s", url, repository, digest)

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}

func (registry *Registry) PutManifest(repository, reference string, signedManifest *manifestV1.SignedManifest) error {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, reference)

	body, err := signedManifest.MarshalJSON()
	if err != nil {
		return err
	}

	buffer := bytes.NewBuffer(body)
	req, err := http.NewRequest("PUT", url, buffer)
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", manifestV1.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	return err
}
<commit_msg>use schema version 2 as the default way to Manifest and remove Manifest2 func,fix issue#24<commit_after>package registry

import (
	"bytes"
	"io/ioutil"
	"net/http"

	"github.com/docker/distribution/digest"
	manifestV1 "github.com/docker/distribution/manifest/schema1"
	manifest "github.com/docker/distribution/manifest/schema2"
)

func (registry *Registry) Manifest(repository, reference string) (*manifest.DeserializedManifest, error) {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.get url=%s repository=%s reference=%s", url, repository, reference)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Accept", manifest.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	deserialized := &manifest.DeserializedManifest{}
	err = deserialized.UnmarshalJSON(body)
	return deserialized, err
}

func (registry *Registry) ManifestDigest(repository, reference string) (digest.Digest, error) {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.head url=%s repository=%s reference=%s", url, repository, reference)

	req, err := http.NewRequest("HEAD", url, nil)
	if err != nil {
		return "", err
	}

	req.Header.Set("Accept", manifest.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return "", err
	}
	return digest.ParseDigest(resp.Header.Get("Docker-Content-Digest"))
}

func (registry *Registry) DeleteManifest(repository string, digest digest.Digest) error {
	url := registry.url("/v2/%s/manifests/%s", repository, digest)
	registry.Logf("registry.manifest.delete url=%s repository=%s reference=%s", url, repository, digest)

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return err
	}

	req.Header.Set("Accept", manifest.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	return err
}

func (registry *Registry) PutManifest(repository, reference string, signedManifest *manifestV1.SignedManifest) error {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, reference)

	body, err := signedManifest.MarshalJSON()
	if err != nil {
		return err
	}

	buffer := bytes.NewBuffer(body)
	req, err := http.NewRequest("PUT", url, buffer)
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", manifestV1.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	return err
}
<|endoftext|>


<commit_before>// Package registry is the primary object of coreos-registry
package registry

import (
	"path"
	"time"
	"net"
	"strings"

	"github.com/coreos/coreinit/machine"
	"github.com/coreos/go-etcd/etcd"
	"github.com/coreos/go-systemd/dbus"
)

const (
	DefaultServiceTTL = "10s"
	DefaultMachineTTL = "20m"
	keyPrefix = "/github.com/coreos/coreinit/"
	machinePrefix = "/machines/"
	systemPrefix = "/system/"
	refreshInterval = 2 // Refresh TTLs at 1/2 the TTL length
)

type Registry struct {
	Etcd *etcd.Client
	Systemd *dbus.Conn
	Machine *machine.Machine
	ServiceTTL string
}

// DoHeartbeat ensures that all of the units are registered at an interval of
// half of the TTL.
func (r *Registry) DoHeartbeat() {
	go r.doServiceHeartbeat()
	r.doMachineHeartbeat()
	return
}

func parseDuration(d string) time.Duration {
	duration, err := time.ParseDuration(d)
	if err != nil {
		panic(err)
	}

	return duration
}

func intervalFromTTL(ttl string) time.Duration {
	duration := parseDuration(ttl)
	return duration / refreshInterval
}

func (r *Registry) doServiceHeartbeat() {
	interval := intervalFromTTL(r.ServiceTTL)

	c := time.Tick(interval)
	for now := range c {
		println(now.String())
		r.SetAllUnits()
	}
}

func (r *Registry) doMachineHeartbeat() {
	interval := intervalFromTTL(DefaultMachineTTL)

	c := time.Tick(interval)
	for now := range c {
		println(now.String())
		r.SetAllUnits()
	}
}

func (r *Registry) SetMachine() {
	list := []string{}
	ifs, err := net.InterfaceAddrs()

	if err != nil {
		panic(err)
	}

	for _, k := range(ifs) {
		list = append(list, k.String())
	}

	key := path.Join(keyPrefix, machinePrefix, r.Machine.BootId, "network")
	d := parseDuration(DefaultMachineTTL)
	r.Etcd.Set(key, strings.Join(list, ","), uint64(d.Seconds()))
}

func (r *Registry) SetAllUnits() {
	units, err := r.Systemd.ListUnits()
	if err != nil {
		panic(err)
	}

	d := parseDuration(r.ServiceTTL)
	for _, u := range(units) {
		if u.ActiveState == "active" {
			println(u.Name, u.ActiveState)
			key := path.Join(keyPrefix, systemPrefix, u.Name, r.Machine.BootId)
			r.Etcd.Set(key, "active", uint64(d.Seconds()))
		}
	}
}

func NewRegistry(ttl string) (registry *Registry) {
	etcdC := etcd.NewClient(nil)
	mach := machine.NewMachine("")
	systemd := dbus.New()

	if ttl == "" {
		ttl = DefaultServiceTTL
	}

	registry = &Registry{etcdC, systemd, mach, ttl}

	return registry
}

/*
func startUnit() {
	script := []string{"/bin/sh", "-c",
		"while true; do echo goodbye world; sleep 1; done"}

	jobid, err := i.Systemd.StartTransientUnit("hello.service",
		"replace",
		dbus.PropExecStart(script, false))
	fmt.Println(jobid, err)
}
*/
<commit_msg>fix(registry): prefix with coreos.com key<commit_after>// Package registry is the primary object of coreos-registry
package registry

import (
	"path"
	"time"
	"net"
	"strings"

	"github.com/coreos/coreinit/machine"
	"github.com/coreos/go-etcd/etcd"
	"github.com/coreos/go-systemd/dbus"
)

const (
	DefaultServiceTTL = "10s"
	DefaultMachineTTL = "20m"
	keyPrefix = "/coreos.com/coreinit/"
	machinePrefix = "/machines/"
	systemPrefix = "/system/"
	refreshInterval = 2 // Refresh TTLs at 1/2 the TTL length
)

type Registry struct {
	Etcd *etcd.Client
	Systemd *dbus.Conn
	Machine *machine.Machine
	ServiceTTL string
}

// DoHeartbeat ensures that all of the units are registered at an interval of
// half of the TTL.
func (r *Registry) DoHeartbeat() {
	go r.doServiceHeartbeat()
	r.doMachineHeartbeat()
	return
}

func parseDuration(d string) time.Duration {
	duration, err := time.ParseDuration(d)
	if err != nil {
		panic(err)
	}

	return duration
}

func intervalFromTTL(ttl string) time.Duration {
	duration := parseDuration(ttl)
	return duration / refreshInterval
}

func (r *Registry) doServiceHeartbeat() {
	interval := intervalFromTTL(r.ServiceTTL)

	c := time.Tick(interval)
	for now := range c {
		println(now.String())
		r.SetAllUnits()
	}
}

func (r *Registry) doMachineHeartbeat() {
	interval := intervalFromTTL(DefaultMachineTTL)

	c := time.Tick(interval)
	for now := range c {
		println(now.String())
		r.SetAllUnits()
	}
}

func (r *Registry) SetMachine() {
	list := []string{}
	ifs, err := net.InterfaceAddrs()

	if err != nil {
		panic(err)
	}

	for _, k := range(ifs) {
		list = append(list, k.String())
	}

	key := path.Join(keyPrefix, machinePrefix, r.Machine.BootId, "network")
	d := parseDuration(DefaultMachineTTL)
	r.Etcd.Set(key, strings.Join(list, ","), uint64(d.Seconds()))
}

func (r *Registry) SetAllUnits() {
	units, err := r.Systemd.ListUnits()
	if err != nil {
		panic(err)
	}

	d := parseDuration(r.ServiceTTL)
	for _, u := range(units) {
		if u.ActiveState == "active" {
			println(u.Name, u.ActiveState)
			key := path.Join(keyPrefix, systemPrefix, u.Name, r.Machine.BootId)
			r.Etcd.Set(key, "active", uint64(d.Seconds()))
		}
	}
}

func NewRegistry(ttl string) (registry *Registry) {
	etcdC := etcd.NewClient(nil)
	mach := machine.NewMachine("")
	systemd := dbus.New()

	if ttl == "" {
		ttl = DefaultServiceTTL
	}

	registry = &Registry{etcdC, systemd, mach, ttl}

	return registry
}

/*
func startUnit() {
	script := []string{"/bin/sh", "-c",
		"while true; do echo goodbye world; sleep 1; done"}

	jobid, err := i.Systemd.StartTransientUnit("hello.service",
		"replace",
		dbus.PropExecStart(script, false))
	fmt.Println(jobid, err)
}
*/
<|endoftext|>


<commit_before>// Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package registry

import (
	"errors"
	"fmt"
	"github.com/miekg/dns"
	"github.com/skynetservices/skydns/msg"
	"log"
	"sort"
	"strings"
	"sync"
	"time"
)

var (
	ErrExists    = errors.New("Service already exists in registry")
	ErrNotExists = errors.New("Service does not exist in registry")
)

type Registry interface {
	Add(s msg.Service) error
	Get(domain string) ([]msg.Service, error)
	GetUUID(uuid string) (msg.Service, error)
	GetExpired() []string
	Remove(s msg.Service) error
	RemoveUUID(uuid string) error
	UpdateTTL(uuid string, ttl uint32, expires time.Time) error
	AddCallback(s msg.Service, c msg.Callback) error
	Len() int
	GetNSEC(key string) (string, string)
	// TODO(miek): add DNSSEC on/off thingy
}

// New returns a new DefaultRegistry.
func New() Registry {
	return &DefaultRegistry{
		tree:  newNode(),
		nodes: make(map[string]*node),
		nsec:  make([]denialReference, 0, 10), // TODO(miek): 10?
	}
}

// DefaultRegistry is a datastore for registered services.
type DefaultRegistry struct {
	tree  *node
	nodes map[string]*node
	mutex sync.Mutex

	// holds a list of sorted domain names
	nsec []denialReference // D N S S E C
}

type denialReference struct {
	name      string // domain name
	reference int    // reference count
}

// Add adds a service to registry.
func (r *DefaultRegistry) Add(s msg.Service) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	// TODO: Validate service has correct values, and getRegistryKey returns a valid value
	if _, ok := r.nodes[s.UUID]; ok {
		return ErrExists
	}

	k := getRegistryKey(s)
	n, err := r.tree.add(strings.Split(k, "."), s)
	if err == nil {
		r.nodes[n.value.UUID] = n
		r.addNSEC(s.Region + "." + s.Version + "." + s.Name + "." + s.Environment)
		r.addNSEC(s.Version + "." + s.Name + "." + s.Environment)
		r.addNSEC(s.Name + "." + s.Environment)
		r.addNSEC(s.Environment)
	}
	return err
}

// the registry look is already being held.
func (r *DefaultRegistry) addNSEC(key string) {
	i := sort.Search(len(r.nsec), func(i int) bool { return r.nsec[i].name >= key })
	if i < len(r.nsec) && r.nsec[i].name == key {
		r.nsec[i].reference++
		return
	}
	r.nsec = append(r.nsec, denialReference{"", 0})
	copy(r.nsec[i+1:], r.nsec[i:])

	r.nsec[i].name = key
	r.nsec[i].reference = 1
}

// RemoveUUID removes a Service specified by an UUID.
func (r *DefaultRegistry) RemoveUUID(uuid string) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[uuid]; ok {
		return r.removeService(n.value)
	}
	return ErrNotExists
}

// UpdateTTL updates the TTL of a service, as well as pushes the expiration time out TTL seconds from now.
// This serves as a ping, for the service to keep SkyDNS aware of it's existence so that it is not expired, and purged.
func (r *DefaultRegistry) UpdateTTL(uuid string, ttl uint32, expires time.Time) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[uuid]; ok {
		n.value.TTL = ttl
		n.value.Expires = expires
		return nil
	}
	return ErrNotExists
}

// removeService remove service from registry while r.mutex is held.
func (r *DefaultRegistry) removeService(s msg.Service) error {
	// we can always delete, even if r.tree reports it doesn't exist,
	// because this means, we just removed a bad service entry.
	// Map deletion is also a no-op, if entry not found in map
	delete(r.nodes, s.UUID)
	// No matter what, call the callbacks
	log.Println("Calling", len(s.Callback), "callback(s) for service", s.UUID)
	for _, c := range s.Callback {
		c.Call(s)
	}

	// TODO: Validate service has correct values, and getRegistryKey returns a valid value
	k := getRegistryKey(s)
	r.removeNSEC(s.Region + "." + s.Version + "." + s.Name + "." + s.Environment)
	r.removeNSEC(s.Version + "." + s.Name + "." + s.Environment)
	r.removeNSEC(s.Name + "." + s.Environment)
	r.removeNSEC(s.Environment)

	return r.tree.remove(strings.Split(k, "."))
}

// registry lock is already being held.
func (r *DefaultRegistry) removeNSEC(key string) {
	i := sort.Search(len(r.nsec), func(i int) bool { return r.nsec[i].name >= key })
	if i < len(r.nsec) && r.nsec[i].name == key {
		r.nsec[i].reference--
		if r.nsec[i].reference == 0 {
			copy(r.nsec[i:], r.nsec[i+1:])
			r.nsec[len(r.nsec)-1] = denialReference{"", 0}
			r.nsec = r.nsec[:len(r.nsec)-1]
		}
	}
}

// Remove removes a service from registry.
func (r *DefaultRegistry) Remove(s msg.Service) (err error) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[s.UUID]; ok {
		return r.removeService(n.value)
	}
	return ErrNotExists
}

// GetUUID retrieves a service based on its UUID.
func (r *DefaultRegistry) GetUUID(uuid string) (s msg.Service, err error) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if s, ok := r.nodes[uuid]; ok {
		s.value.TTL = s.value.RemainingTTL()

		if s.value.TTL >= 1 {
			return s.value, nil
		}
	}
	return s, ErrNotExists
}

func (r *DefaultRegistry) GetNSEC(key string) (string, string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	if len(r.nsec) == 0 {
		return "", "" // @ -> @
	}
	i := sort.Search(len(r.nsec), func(i int) bool { return r.nsec[i].name >= key })
	if i < len(r.nsec) && r.nsec[i].name == key {
		if i+1 == len(r.nsec) {
			return r.nsec[i].name + ".", ""
		}
		return r.nsec[i].name, r.nsec[i+1].name + "."
	}
	if i == 1 {
		return "", r.nsec[i].name + "."
	}
	return r.nsec[i-1].name + ".", r.nsec[i].name + "."
}

// Get retrieves a list of services from the registry that matches the given domain pattern:
//
// uuid.host.region.version.service.environment
// any of these positions may supply the wildcard "*", to have all values match in this position.
// additionally, you only need to specify as much of the domain as needed the domain version.service.environment is perfectly acceptable,
// and will assume "*" for all the ommited subdomain positions
func (r *DefaultRegistry) Get(domain string) ([]msg.Service, error) {
	// TODO: account for version wildcards
	r.mutex.Lock()
	defer r.mutex.Unlock()

	// DNS queries have a trailing .
	if strings.HasSuffix(domain, ".") {
		domain = domain[:len(domain)-1]
	}

	tree := dns.SplitDomainName(domain)

	// Domains can be partial, and we should assume wildcards for the unsupplied portions
	if len(tree) < 6 {
		pad := 6 - len(tree)
		t := make([]string, pad)

		for i := 0; i < pad; i++ {
			t[i] = "*"
		}

		tree = append(t, tree...)
	}
	return r.tree.get(tree)
}

// GetExpired returns a slice of expired UUIDs.
func (r *DefaultRegistry) GetExpired() (uuids []string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	now := time.Now()

	for _, n := range r.nodes {
		if now.After(n.value.Expires) {
			uuids = append(uuids, n.value.UUID)
		}
	}

	return
}

// AddCallback adds callback c to the service s.
func (r *DefaultRegistry) AddCallback(s msg.Service, c msg.Callback) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[s.UUID]; ok {
		if n.value.Callback == nil {
			n.value.Callback = make(map[string]msg.Callback)
		}
		n.value.Callback[c.UUID] = c
		return nil
	}
	return ErrNotExists
}

// Len returns the size of the registry r.
func (r *DefaultRegistry) Len() int {
	return r.tree.size()
}

type node struct {
	leaves map[string]*node
	depth  int
	length int

	value msg.Service
}

func newNode() *node {
	return &node{
		leaves: make(map[string]*node),
	}
}

func (n *node) remove(tree []string) error {
	// We are the last element, remove
	if len(tree) == 1 {
		if _, ok := n.leaves[tree[0]]; !ok {
			return ErrNotExists
		} else {
			delete(n.leaves, tree[0])
			n.length--

			return nil
		}
	}

	// Forward removal
	k := tree[len(tree)-1]
	if _, ok := n.leaves[k]; !ok {
		return ErrNotExists
	}

	var err error
	if err = n.leaves[k].remove(tree[:len(tree)-1]); err == nil {
		n.length--

		// Cleanup empty paths
		if n.leaves[k].size() == 0 {
			delete(n.leaves, k)
		}
	}

	return err
}

func (n *node) add(tree []string, s msg.Service) (*node, error) {
	// We are the last element, insert
	if len(tree) == 1 {
		if _, ok := n.leaves[tree[0]]; ok {
			return nil, ErrExists
		}

		n.leaves[tree[0]] = &node{
			value:  s,
			leaves: make(map[string]*node),
			depth:  n.depth + 1,
		}

		n.length++

		return n.leaves[tree[0]], nil
	}

	// Forward entry
	k := tree[len(tree)-1]

	if _, ok := n.leaves[k]; !ok {
		n.leaves[k] = newNode()
		n.leaves[k].depth = n.depth + 1
	}

	newNode, err := n.leaves[k].add(tree[:len(tree)-1], s)
	if err != nil {
		return nil, err
	}

	// This node length should account for all nodes below it
	n.length++
	return newNode, nil
}

func (n *node) size() int {
	return n.length
}

func (n *node) get(tree []string) (services []msg.Service, err error) {
	// We've hit the bottom
	if len(tree) == 1 {
		switch tree[0] {
		case "*":
			if len(n.leaves) == 0 {
				return services, ErrNotExists
			}

			for _, s := range n.leaves {
				s.value.UpdateTTL()

				if s.value.TTL > 1 {
					services = append(services, s.value)
				}
			}
		default:
			if _, ok := n.leaves[tree[0]]; !ok {
				return services, ErrNotExists
			}

			n.leaves[tree[0]].value.UpdateTTL()

			if n.leaves[tree[0]].value.TTL > 1 {
				services = append(services, n.leaves[tree[0]].value)
			}
		}

		return
	}

	k := tree[len(tree)-1]

	switch k {
	case "*":
		if len(n.leaves) == 0 {
			return services, ErrNotExists
		}

		var success bool
		for _, l := range n.leaves {
			if s, e := l.get(tree[:len(tree)-1]); e == nil {
				services = append(services, s...)
				success = true
			}
		}

		if !success {
			return services, ErrNotExists
		}
	default:
		if _, ok := n.leaves[k]; !ok {
			return services, ErrNotExists
		}

		return n.leaves[k].get(tree[:len(tree)-1])
	}
	return
}

func getRegistryKey(s msg.Service) string {
	return strings.ToLower(fmt.Sprintf("%s.%s.%s.%s.%s.%s", s.UUID, strings.Replace(s.Host, ".", "-", -1), s.Region, strings.Replace(s.Version, ".", "-", -1), s.Name, s.Environment))
}
<commit_msg>Extra comments<commit_after>// Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package registry

import (
	"errors"
	"fmt"
	"github.com/miekg/dns"
	"github.com/skynetservices/skydns/msg"
	"log"
	"sort"
	"strings"
	"sync"
	"time"
)

var (
	ErrExists    = errors.New("Service already exists in registry")
	ErrNotExists = errors.New("Service does not exist in registry")
)

type Registry interface {
	Add(s msg.Service) error
	Get(domain string) ([]msg.Service, error)
	GetUUID(uuid string) (msg.Service, error)
	GetExpired() []string
	Remove(s msg.Service) error
	RemoveUUID(uuid string) error
	UpdateTTL(uuid string, ttl uint32, expires time.Time) error
	AddCallback(s msg.Service, c msg.Callback) error
	Len() int
	GetNSEC(key string) (string, string)
	// TODO(miek): add DNSSEC on/off thingy
}

// New returns a new DefaultRegistry.
func New() Registry {
	return &DefaultRegistry{
		tree:  newNode(),
		nodes: make(map[string]*node),
		nsec:  make([]denialReference, 0, 10), // TODO(miek): 10?
	}
}

// DefaultRegistry is a datastore for registered services.
type DefaultRegistry struct {
	tree  *node
	nodes map[string]*node
	mutex sync.Mutex

	// holds a list of sorted domain names
	nsec []denialReference // D N S S E C
}

type denialReference struct {
	name      string // domain name
	reference int    // reference count
}

// Add adds a service to registry.
func (r *DefaultRegistry) Add(s msg.Service) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	// TODO: Validate service has correct values, and getRegistryKey returns a valid value
	if _, ok := r.nodes[s.UUID]; ok {
		return ErrExists
	}

	k := getRegistryKey(s)
	n, err := r.tree.add(strings.Split(k, "."), s)
	if err == nil {
		r.nodes[n.value.UUID] = n
		r.addNSEC(s.Region + "." + s.Version + "." + s.Name + "." + s.Environment)
		r.addNSEC(s.Version + "." + s.Name + "." + s.Environment)
		r.addNSEC(s.Name + "." + s.Environment)
		r.addNSEC(s.Environment)
	}
	return err
}

// the registry look is already being held.
func (r *DefaultRegistry) addNSEC(key string) {
	i := sort.Search(len(r.nsec), func(i int) bool { return r.nsec[i].name >= key })
	if i < len(r.nsec) && r.nsec[i].name == key {
		r.nsec[i].reference++
		return
	}
	r.nsec = append(r.nsec, denialReference{"", 0})
	copy(r.nsec[i+1:], r.nsec[i:])

	r.nsec[i].name = key
	r.nsec[i].reference = 1
}

// RemoveUUID removes a Service specified by an UUID.
func (r *DefaultRegistry) RemoveUUID(uuid string) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[uuid]; ok {
		return r.removeService(n.value)
	}
	return ErrNotExists
}

// UpdateTTL updates the TTL of a service, as well as pushes the expiration time out TTL seconds from now.
// This serves as a ping, for the service to keep SkyDNS aware of it's existence so that it is not expired, and purged.
func (r *DefaultRegistry) UpdateTTL(uuid string, ttl uint32, expires time.Time) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[uuid]; ok {
		n.value.TTL = ttl
		n.value.Expires = expires
		return nil
	}
	return ErrNotExists
}

// removeService remove service from registry while r.mutex is held.
func (r *DefaultRegistry) removeService(s msg.Service) error {
	// we can always delete, even if r.tree reports it doesn't exist,
	// because this means, we just removed a bad service entry.
	// Map deletion is also a no-op, if entry not found in map
	delete(r.nodes, s.UUID)
	// No matter what, call the callbacks
	log.Println("Calling", len(s.Callback), "callback(s) for service", s.UUID)
	for _, c := range s.Callback {
		c.Call(s)
	}

	// TODO: Validate service has correct values, and getRegistryKey returns a valid value
	k := getRegistryKey(s)
	r.removeNSEC(s.Region + "." + s.Version + "." + s.Name + "." + s.Environment)
	r.removeNSEC(s.Version + "." + s.Name + "." + s.Environment)
	r.removeNSEC(s.Name + "." + s.Environment)
	r.removeNSEC(s.Environment)

	return r.tree.remove(strings.Split(k, "."))
}

// registry lock is already being held.
func (r *DefaultRegistry) removeNSEC(key string) {
	i := sort.Search(len(r.nsec), func(i int) bool { return r.nsec[i].name >= key })
	if i < len(r.nsec) && r.nsec[i].name == key {
		r.nsec[i].reference--
		if r.nsec[i].reference == 0 {
			copy(r.nsec[i:], r.nsec[i+1:])
			r.nsec[len(r.nsec)-1] = denialReference{"", 0}
			r.nsec = r.nsec[:len(r.nsec)-1]
		}
	}
}

// Remove removes a service from registry.
func (r *DefaultRegistry) Remove(s msg.Service) (err error) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[s.UUID]; ok {
		return r.removeService(n.value)
	}
	return ErrNotExists
}

// GetUUID retrieves a service based on its UUID.
func (r *DefaultRegistry) GetUUID(uuid string) (s msg.Service, err error) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if s, ok := r.nodes[uuid]; ok {
		s.value.TTL = s.value.RemainingTTL()

		if s.value.TTL >= 1 {
			return s.value, nil
		}
	}
	return s, ErrNotExists
}

func (r *DefaultRegistry) GetNSEC(key string) (string, string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	if len(r.nsec) == 0 {
		return "", "" // @ -> @, empty zone
	}
	i := sort.Search(len(r.nsec), func(i int) bool { return r.nsec[i].name >= key })
	if i < len(r.nsec) && r.nsec[i].name == key {
		if i+1 == len(r.nsec) {
			return r.nsec[i].name + ".", ""
		}
		return r.nsec[i].name, r.nsec[i+1].name + "."
	}
	if i == 1 {
		return "", r.nsec[i].name + "."
	}
	return r.nsec[i-1].name + ".", r.nsec[i].name + "."
}

// Get retrieves a list of services from the registry that matches the given domain pattern:
//
// uuid.host.region.version.service.environment
// any of these positions may supply the wildcard "*", to have all values match in this position.
// additionally, you only need to specify as much of the domain as needed the domain version.service.environment is perfectly acceptable,
// and will assume "*" for all the ommited subdomain positions
func (r *DefaultRegistry) Get(domain string) ([]msg.Service, error) {
	// TODO: account for version wildcards
	r.mutex.Lock()
	defer r.mutex.Unlock()

	// DNS queries have a trailing .
	if strings.HasSuffix(domain, ".") {
		domain = domain[:len(domain)-1]
	}

	tree := dns.SplitDomainName(domain)

	// Domains can be partial, and we should assume wildcards for the unsupplied portions
	if len(tree) < 6 {
		pad := 6 - len(tree)
		t := make([]string, pad)

		for i := 0; i < pad; i++ {
			t[i] = "*"
		}

		tree = append(t, tree...)
	}
	return r.tree.get(tree)
}

// GetExpired returns a slice of expired UUIDs.
func (r *DefaultRegistry) GetExpired() (uuids []string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	now := time.Now()

	for _, n := range r.nodes {
		if now.After(n.value.Expires) {
			uuids = append(uuids, n.value.UUID)
		}
	}

	return
}

// AddCallback adds callback c to the service s.
func (r *DefaultRegistry) AddCallback(s msg.Service, c msg.Callback) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if n, ok := r.nodes[s.UUID]; ok {
		if n.value.Callback == nil {
			n.value.Callback = make(map[string]msg.Callback)
		}
		n.value.Callback[c.UUID] = c
		return nil
	}
	return ErrNotExists
}

// Len returns the size of the registry r.
func (r *DefaultRegistry) Len() int {
	return r.tree.size()
}

type node struct {
	leaves map[string]*node
	depth  int
	length int

	value msg.Service
}

func newNode() *node {
	return &node{
		leaves: make(map[string]*node),
	}
}

func (n *node) remove(tree []string) error {
	// We are the last element, remove
	if len(tree) == 1 {
		if _, ok := n.leaves[tree[0]]; !ok {
			return ErrNotExists
		} else {
			delete(n.leaves, tree[0])
			n.length--

			return nil
		}
	}

	// Forward removal
	k := tree[len(tree)-1]
	if _, ok := n.leaves[k]; !ok {
		return ErrNotExists
	}

	var err error
	if err = n.leaves[k].remove(tree[:len(tree)-1]); err == nil {
		n.length--

		// Cleanup empty paths
		if n.leaves[k].size() == 0 {
			delete(n.leaves, k)
		}
	}

	return err
}

func (n *node) add(tree []string, s msg.Service) (*node, error) {
	// We are the last element, insert
	if len(tree) == 1 {
		if _, ok := n.leaves[tree[0]]; ok {
			return nil, ErrExists
		}

		n.leaves[tree[0]] = &node{
			value:  s,
			leaves: make(map[string]*node),
			depth:  n.depth + 1,
		}

		n.length++

		return n.leaves[tree[0]], nil
	}

	// Forward entry
	k := tree[len(tree)-1]

	if _, ok := n.leaves[k]; !ok {
		n.leaves[k] = newNode()
		n.leaves[k].depth = n.depth + 1
	}

	newNode, err := n.leaves[k].add(tree[:len(tree)-1], s)
	if err != nil {
		return nil, err
	}

	// This node length should account for all nodes below it
	n.length++
	return newNode, nil
}

func (n *node) size() int {
	return n.length
}

func (n *node) get(tree []string) (services []msg.Service, err error) {
	// We've hit the bottom
	if len(tree) == 1 {
		switch tree[0] {
		case "*":
			if len(n.leaves) == 0 {
				return services, ErrNotExists
			}

			for _, s := range n.leaves {
				s.value.UpdateTTL()

				if s.value.TTL > 1 {
					services = append(services, s.value)
				}
			}
		default:
			if _, ok := n.leaves[tree[0]]; !ok {
				return services, ErrNotExists
			}

			n.leaves[tree[0]].value.UpdateTTL()

			if n.leaves[tree[0]].value.TTL > 1 {
				services = append(services, n.leaves[tree[0]].value)
			}
		}

		return
	}

	k := tree[len(tree)-1]

	switch k {
	case "*":
		if len(n.leaves) == 0 {
			return services, ErrNotExists
		}

		var success bool
		for _, l := range n.leaves {
			if s, e := l.get(tree[:len(tree)-1]); e == nil {
				services = append(services, s...)
				success = true
			}
		}

		if !success {
			return services, ErrNotExists
		}
	default:
		if _, ok := n.leaves[k]; !ok {
			return services, ErrNotExists
		}

		return n.leaves[k].get(tree[:len(tree)-1])
	}
	return
}

func getRegistryKey(s msg.Service) string {
	return strings.ToLower(fmt.Sprintf("%s.%s.%s.%s.%s.%s", s.UUID, strings.Replace(s.Host, ".", "-", -1), s.Region, strings.Replace(s.Version, ".", "-", -1), s.Name, s.Environment))
}
<|endoftext|>


<commit_before>package main

import "testing"

func TestEncodeDecode(t *testing.T) {
	dims := []int{3, 2, 4, 17, 26, 15, 1, 2, 1}
	for i := 0; i < 318240; i++ {   // Product of dims
		addr := DecodeIndex(dims, i)
		for j := 0; j < len(dims); j++ {
			if addr[j] >= dims[j] {
				t.Fail()
			}
		}
		if EncodeIndex(dims, addr) != i {
			t.Fail()
		}
	}
}
<commit_msg>Fix test: Add trading dimension.<commit_after>package main

import "testing"

func TestEncodeDecode(t *testing.T) {
	dims := []int{3, 2, 4, 17, 26, 15, 2, 1, 2, 1}
	var i int32
	for i = 0; i < 636480; i++ { // Product of dims
		addr := DecodeIndex(dims, i)
		for j := 0; j < len(dims); j++ {
			if addr[j] >= dims[j] {
				t.Fail()
			}
		}
		if EncodeIndex(dims, addr) != i {
			t.Fail()
		}
	}
}
<|endoftext|>


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

package oto

/*

#include <jni.h>
#include <stdlib.h>

static jclass android_media_AudioFormat;
static jclass android_media_AudioManager;
static jclass android_media_AudioTrack;

static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
    int sampleRate, int channelNum, int bytesPerSample, jobject* audioTrack, int* bufferSize) {
  // bufferSize has an initial value, and this is updated when bufferSize is
  // smaller than AudioTrack's minimum buffer size.

  JavaVM* vm = (JavaVM*)java_vm;
  JNIEnv* env = (JNIEnv*)jni_env;

  jclass local = (*env)->FindClass(env, "android/media/AudioFormat");
  android_media_AudioFormat = (*env)->NewGlobalRef(env, local);
  (*env)->DeleteLocalRef(env, local);

  local = (*env)->FindClass(env, "android/media/AudioManager");
  android_media_AudioManager = (*env)->NewGlobalRef(env, local);
  (*env)->DeleteLocalRef(env, local);

  local = (*env)->FindClass(env, "android/media/AudioTrack");
  android_media_AudioTrack = (*env)->NewGlobalRef(env, local);
  (*env)->DeleteLocalRef(env, local);

  const jint android_media_AudioManager_STREAM_MUSIC =
      (*env)->GetStaticIntField(
          env, android_media_AudioManager,
          (*env)->GetStaticFieldID(env, android_media_AudioManager, "STREAM_MUSIC", "I"));
  const jint android_media_AudioTrack_MODE_STREAM =
      (*env)->GetStaticIntField(
          env, android_media_AudioTrack,
          (*env)->GetStaticFieldID(env, android_media_AudioTrack, "MODE_STREAM", "I"));
  const jint android_media_AudioFormat_CHANNEL_OUT_MONO =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_MONO", "I"));
  const jint android_media_AudioFormat_CHANNEL_OUT_STEREO =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_STEREO", "I"));
  const jint android_media_AudioFormat_ENCODING_PCM_8BIT =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_8BIT", "I"));
  const jint android_media_AudioFormat_ENCODING_PCM_16BIT =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_16BIT", "I"));

  jint channel = android_media_AudioFormat_CHANNEL_OUT_MONO;
  switch (channelNum) {
  case 1:
    channel = android_media_AudioFormat_CHANNEL_OUT_MONO;
    break;
  case 2:
    channel = android_media_AudioFormat_CHANNEL_OUT_STEREO;
    break;
  default:
    return "invalid channel";
  }

  jint encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
  switch (bytesPerSample) {
  case 1:
    encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
    break;
  case 2:
    encoding = android_media_AudioFormat_ENCODING_PCM_16BIT;
    break;
  default:
    return "invalid bytesPerSample";
  }

  int minBufferSize =
      (*env)->CallStaticIntMethod(
          env, android_media_AudioTrack,
          (*env)->GetStaticMethodID(env, android_media_AudioTrack, "getMinBufferSize", "(III)I"),
          sampleRate, channel, encoding);
  if (*bufferSize < minBufferSize) {
    *bufferSize = minBufferSize;
  }

  const jobject tmpAudioTrack =
      (*env)->NewObject(
          env, android_media_AudioTrack,
          (*env)->GetMethodID(env, android_media_AudioTrack, "<init>", "(IIIIII)V"),
          android_media_AudioManager_STREAM_MUSIC,
          sampleRate, channel, encoding, *bufferSize,
          android_media_AudioTrack_MODE_STREAM);
  // Note that *audioTrack will never be released.
  *audioTrack = (*env)->NewGlobalRef(env, tmpAudioTrack);
  (*env)->DeleteLocalRef(env, tmpAudioTrack);

  (*env)->CallVoidMethod(
      env, *audioTrack,
      (*env)->GetMethodID(env, android_media_AudioTrack, "play", "()V"));

  return NULL;
}

static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
    jobject audioTrack, int bytesPerSample, void* data, int length) {
  JavaVM* vm = (JavaVM*)java_vm;
  JNIEnv* env = (JNIEnv*)jni_env;

  jbyteArray arrInBytes;
  jshortArray arrInShorts;
  switch (bytesPerSample) {
  case 1:
    arrInBytes = (*env)->NewByteArray(env, length);
    (*env)->SetByteArrayRegion(env, arrInBytes, 0, length, data);
    break;
  case 2:
    arrInShorts = (*env)->NewShortArray(env, length);
    (*env)->SetShortArrayRegion(env, arrInShorts, 0, length, data);
    break;
  }

  jint result;
  static jmethodID write1 = NULL;
  static jmethodID write2 = NULL;
  if (!write1) {
    write1 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BII)I");
  }
  if (!write2) {
    write2 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([SII)I");
  }
  switch (bytesPerSample) {
  case 1:
    result = (*env)->CallIntMethod(env, audioTrack, write1, arrInBytes, 0, length);
    (*env)->DeleteLocalRef(env, arrInBytes);
    break;
  case 2:
    result = (*env)->CallIntMethod(env, audioTrack, write2, arrInShorts, 0, length);
    (*env)->DeleteLocalRef(env, arrInShorts);
    break;
  }

  switch (result) {
  case -3: // ERROR_INVALID_OPERATION
    return "invalid operation";
  case -2: // ERROR_BAD_VALUE
    return "bad value";
  case -1: // ERROR
    return "error";
  }
  if (result < 0) {
    return "unknown error";
  }
  return NULL;
}

*/
import "C"

import (
	"errors"
	"runtime"
	"unsafe"

	"github.com/hajimehoshi/oto/internal/jni"
)

type player struct {
	sampleRate     int
	channelNum     int
	bytesPerSample int
	audioTrack     C.jobject
	chErr          chan error
	chBuffer       chan []uint8
	tmp    []uint8
	bufferSize     int
}

func newPlayer(sampleRate, channelNum, bytesPerSample, bufferSizeInBytes int) (*player, error) {
	p := &player{
		sampleRate:     sampleRate,
		channelNum:     channelNum,
		bytesPerSample: bytesPerSample,
		chErr:          make(chan error),
		chBuffer:       make(chan []uint8, 8),
	}
	runtime.SetFinalizer(p, (*player).Close)

	if err := jni.RunOnJVM(func(vm, env, ctx uintptr) error {
		audioTrack := C.jobject(nil)
		bufferSize := C.int(bufferSizeInBytes)
		if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
			C.int(sampleRate), C.int(channelNum), C.int(bytesPerSample),
			&audioTrack, &bufferSize); msg != nil {
			return errors.New("oto: initAutioTrack failed: " + C.GoString(msg))
		}
		p.audioTrack = audioTrack
		p.bufferSize = int(bufferSize) // Variable bufferSize can be updated at initAudioTrack.
		return nil
	}); err != nil {
		return nil, err
	}

	go p.loop()
	return p, nil
}

func (p *player) loop() {
	for bufInBytes := range p.chBuffer {
		var bufInShorts []int16
		if p.bytesPerSample == 2 {
			bufInShorts = make([]int16, len(bufInBytes)/2)
			for i := 0; i < len(bufInShorts); i++ {
				bufInShorts[i] = int16(bufInBytes[2*i]) | (int16(bufInBytes[2*i+1]) << 8)
			}
		}
		if err := jni.RunOnJVM(func(vm, env, ctx uintptr) error {
			msg := (*C.char)(nil)
			switch p.bytesPerSample {
			case 1:
				msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
					p.audioTrack, C.int(p.bytesPerSample),
					unsafe.Pointer(&bufInBytes[0]), C.int(len(bufInBytes)))
			case 2:
				msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
					p.audioTrack, C.int(p.bytesPerSample),
					unsafe.Pointer(&bufInShorts[0]), C.int(len(bufInShorts)))
			default:
				panic("not reach")
			}
			if msg != nil {
				return errors.New("oto: loop failed: " + C.GoString(msg))
			}
			return nil
		}); err != nil {
			p.chErr <- err
			return
		}
	}
}

func (p *player) Write(data []uint8) (int, error) {
	n := min(len(data), p.bufferSize-len(p.tmp))
	p.tmp = append(p.tmp, data[:n]...)

	if len(p.tmp) < p.bufferSize {
		return n, nil
	}

	select {
	case p.chBuffer <- p.tmp:
	case err := <-p.chErr:
		return 0, err
	}

	p.tmp = nil
	return n, nil
}

func (p *player) Close() error {
	runtime.SetFinalizer(p, nil)
	return nil
}
<commit_msg>gofmt<commit_after>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oto

/*

#include <jni.h>
#include <stdlib.h>

static jclass android_media_AudioFormat;
static jclass android_media_AudioManager;
static jclass android_media_AudioTrack;

static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
    int sampleRate, int channelNum, int bytesPerSample, jobject* audioTrack, int* bufferSize) {
  // bufferSize has an initial value, and this is updated when bufferSize is
  // smaller than AudioTrack's minimum buffer size.

  JavaVM* vm = (JavaVM*)java_vm;
  JNIEnv* env = (JNIEnv*)jni_env;

  jclass local = (*env)->FindClass(env, "android/media/AudioFormat");
  android_media_AudioFormat = (*env)->NewGlobalRef(env, local);
  (*env)->DeleteLocalRef(env, local);

  local = (*env)->FindClass(env, "android/media/AudioManager");
  android_media_AudioManager = (*env)->NewGlobalRef(env, local);
  (*env)->DeleteLocalRef(env, local);

  local = (*env)->FindClass(env, "android/media/AudioTrack");
  android_media_AudioTrack = (*env)->NewGlobalRef(env, local);
  (*env)->DeleteLocalRef(env, local);

  const jint android_media_AudioManager_STREAM_MUSIC =
      (*env)->GetStaticIntField(
          env, android_media_AudioManager,
          (*env)->GetStaticFieldID(env, android_media_AudioManager, "STREAM_MUSIC", "I"));
  const jint android_media_AudioTrack_MODE_STREAM =
      (*env)->GetStaticIntField(
          env, android_media_AudioTrack,
          (*env)->GetStaticFieldID(env, android_media_AudioTrack, "MODE_STREAM", "I"));
  const jint android_media_AudioFormat_CHANNEL_OUT_MONO =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_MONO", "I"));
  const jint android_media_AudioFormat_CHANNEL_OUT_STEREO =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_STEREO", "I"));
  const jint android_media_AudioFormat_ENCODING_PCM_8BIT =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_8BIT", "I"));
  const jint android_media_AudioFormat_ENCODING_PCM_16BIT =
      (*env)->GetStaticIntField(
          env, android_media_AudioFormat,
          (*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_16BIT", "I"));

  jint channel = android_media_AudioFormat_CHANNEL_OUT_MONO;
  switch (channelNum) {
  case 1:
    channel = android_media_AudioFormat_CHANNEL_OUT_MONO;
    break;
  case 2:
    channel = android_media_AudioFormat_CHANNEL_OUT_STEREO;
    break;
  default:
    return "invalid channel";
  }

  jint encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
  switch (bytesPerSample) {
  case 1:
    encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
    break;
  case 2:
    encoding = android_media_AudioFormat_ENCODING_PCM_16BIT;
    break;
  default:
    return "invalid bytesPerSample";
  }

  int minBufferSize =
      (*env)->CallStaticIntMethod(
          env, android_media_AudioTrack,
          (*env)->GetStaticMethodID(env, android_media_AudioTrack, "getMinBufferSize", "(III)I"),
          sampleRate, channel, encoding);
  if (*bufferSize < minBufferSize) {
    *bufferSize = minBufferSize;
  }

  const jobject tmpAudioTrack =
      (*env)->NewObject(
          env, android_media_AudioTrack,
          (*env)->GetMethodID(env, android_media_AudioTrack, "<init>", "(IIIIII)V"),
          android_media_AudioManager_STREAM_MUSIC,
          sampleRate, channel, encoding, *bufferSize,
          android_media_AudioTrack_MODE_STREAM);
  // Note that *audioTrack will never be released.
  *audioTrack = (*env)->NewGlobalRef(env, tmpAudioTrack);
  (*env)->DeleteLocalRef(env, tmpAudioTrack);

  (*env)->CallVoidMethod(
      env, *audioTrack,
      (*env)->GetMethodID(env, android_media_AudioTrack, "play", "()V"));

  return NULL;
}

static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
    jobject audioTrack, int bytesPerSample, void* data, int length) {
  JavaVM* vm = (JavaVM*)java_vm;
  JNIEnv* env = (JNIEnv*)jni_env;

  jbyteArray arrInBytes;
  jshortArray arrInShorts;
  switch (bytesPerSample) {
  case 1:
    arrInBytes = (*env)->NewByteArray(env, length);
    (*env)->SetByteArrayRegion(env, arrInBytes, 0, length, data);
    break;
  case 2:
    arrInShorts = (*env)->NewShortArray(env, length);
    (*env)->SetShortArrayRegion(env, arrInShorts, 0, length, data);
    break;
  }

  jint result;
  static jmethodID write1 = NULL;
  static jmethodID write2 = NULL;
  if (!write1) {
    write1 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BII)I");
  }
  if (!write2) {
    write2 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([SII)I");
  }
  switch (bytesPerSample) {
  case 1:
    result = (*env)->CallIntMethod(env, audioTrack, write1, arrInBytes, 0, length);
    (*env)->DeleteLocalRef(env, arrInBytes);
    break;
  case 2:
    result = (*env)->CallIntMethod(env, audioTrack, write2, arrInShorts, 0, length);
    (*env)->DeleteLocalRef(env, arrInShorts);
    break;
  }

  switch (result) {
  case -3: // ERROR_INVALID_OPERATION
    return "invalid operation";
  case -2: // ERROR_BAD_VALUE
    return "bad value";
  case -1: // ERROR
    return "error";
  }
  if (result < 0) {
    return "unknown error";
  }
  return NULL;
}

*/
import "C"

import (
	"errors"
	"runtime"
	"unsafe"

	"github.com/hajimehoshi/oto/internal/jni"
)

type player struct {
	sampleRate     int
	channelNum     int
	bytesPerSample int
	audioTrack     C.jobject
	chErr          chan error
	chBuffer       chan []uint8
	tmp            []uint8
	bufferSize     int
}

func newPlayer(sampleRate, channelNum, bytesPerSample, bufferSizeInBytes int) (*player, error) {
	p := &player{
		sampleRate:     sampleRate,
		channelNum:     channelNum,
		bytesPerSample: bytesPerSample,
		chErr:          make(chan error),
		chBuffer:       make(chan []uint8, 8),
	}
	runtime.SetFinalizer(p, (*player).Close)

	if err := jni.RunOnJVM(func(vm, env, ctx uintptr) error {
		audioTrack := C.jobject(nil)
		bufferSize := C.int(bufferSizeInBytes)
		if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
			C.int(sampleRate), C.int(channelNum), C.int(bytesPerSample),
			&audioTrack, &bufferSize); msg != nil {
			return errors.New("oto: initAutioTrack failed: " + C.GoString(msg))
		}
		p.audioTrack = audioTrack
		p.bufferSize = int(bufferSize) // Variable bufferSize can be updated at initAudioTrack.
		return nil
	}); err != nil {
		return nil, err
	}

	go p.loop()
	return p, nil
}

func (p *player) loop() {
	for bufInBytes := range p.chBuffer {
		var bufInShorts []int16
		if p.bytesPerSample == 2 {
			bufInShorts = make([]int16, len(bufInBytes)/2)
			for i := 0; i < len(bufInShorts); i++ {
				bufInShorts[i] = int16(bufInBytes[2*i]) | (int16(bufInBytes[2*i+1]) << 8)
			}
		}
		if err := jni.RunOnJVM(func(vm, env, ctx uintptr) error {
			msg := (*C.char)(nil)
			switch p.bytesPerSample {
			case 1:
				msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
					p.audioTrack, C.int(p.bytesPerSample),
					unsafe.Pointer(&bufInBytes[0]), C.int(len(bufInBytes)))
			case 2:
				msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
					p.audioTrack, C.int(p.bytesPerSample),
					unsafe.Pointer(&bufInShorts[0]), C.int(len(bufInShorts)))
			default:
				panic("not reach")
			}
			if msg != nil {
				return errors.New("oto: loop failed: " + C.GoString(msg))
			}
			return nil
		}); err != nil {
			p.chErr <- err
			return
		}
	}
}

func (p *player) Write(data []uint8) (int, error) {
	n := min(len(data), p.bufferSize-len(p.tmp))
	p.tmp = append(p.tmp, data[:n]...)

	if len(p.tmp) < p.bufferSize {
		return n, nil
	}

	select {
	case p.chBuffer <- p.tmp:
	case err := <-p.chErr:
		return 0, err
	}

	p.tmp = nil
	return n, nil
}

func (p *player) Close() error {
	runtime.SetFinalizer(p, nil)
	return nil
}
<|endoftext|>


<commit_before>package data

import (
	"context"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/helper/clickhouse"
	"github.com/lomik/graphite-clickhouse/pkg/dry"
	"github.com/lomik/graphite-clickhouse/pkg/reverse"
	"github.com/lomik/graphite-clickhouse/pkg/scope"
	"github.com/lomik/graphite-clickhouse/pkg/where"
	"go.uber.org/zap"
)

// from, until, step, function, table, prewhere, where
// arrayFilter(x->isNotNull(x)) - do not pass nulls to client
// -Resample - group time and values by time intervals and apply aggregation function
// -OrNull - if there aren't points in an interval, null will be returned
// intDiv(Time, x)*x - round Time down to step multiplier
// TODO: support custom aggregating functions
const queryAggregated = `WITH anyResample(%[1]d, %[2]d, %[3]d)(toUInt32(intDiv(Time, %[3]d)*%[3]d), Time) AS mask
SELECT Path,
 arrayFilter(m->m!=0, mask) AS times,
 arrayFilter((v,m)->m!=0, %[4]sResample(%[1]d, %[2]d, %[3]d)(Value, Time), mask) AS values
FROM %[5]s
%[6]s
%[7]s
GROUP BY Path
FORMAT RowBinary`

// table, prewhere, where
const queryUnaggregated = `SELECT Path, groupArray(Time), groupArray(Value), groupArray(Timestamp)
FROM %s
%s
%s
GROUP BY Path
FORMAT RowBinary`

// name of external-data table with metrics paths
const extTableName = "metrics_list"

type query struct {
	CHResponses
	cStep            *commonStep
	chURL            string
	chDataTimeout    time.Duration
	chConnectTimeout time.Duration
	debugDir         string
	debugExtDataPerm os.FileMode
	lock             sync.RWMutex
}

type conditions struct {
	*TimeFrame
	*Targets
	// aggregated shows is it request with ClickHouse aggregation or not
	aggregated bool
	// step is used in requests for proper until/from calculation. It's max(steps) for non-aggregated
	// requests and LCM(steps) for aggregated requests
	step int64
	// from is aligned to step
	from int64
	// until is aligned to step
	until int64
	// metricUnreversed grouped by step
	steps map[uint32][]string
	// prewhere contains PREWHERE condition
	prewhere string
	// where contains WHERE condition
	where string
	// metricUnreversed grouped by aggregating function
	aggregations map[string][]string
	// External-data bodies grouped by aggregatig function. For non-aggregated requests "" used as a key
	extDataBodies    map[string]*strings.Builder
	metricsRequested []string
	metricsUnreverse []string
	metricsLookup    []string
}

func newQuery(cfg *config.Config, targets int) *query {
	var cStep *commonStep = nil
	if cfg.ClickHouse.InternalAggregation {
		cStep = &commonStep{
			result: 0,
			wg:     sync.WaitGroup{},
			lock:   sync.RWMutex{},
		}

		cStep.addTargets(targets)
	}

	query := &query{
		CHResponses:      make([]CHResponse, 0, targets),
		cStep:            cStep,
		chURL:            cfg.ClickHouse.URL,
		chDataTimeout:    cfg.ClickHouse.DataTimeout,
		chConnectTimeout: cfg.Carbonlink.ConnectTimeout,
		debugDir:         cfg.Debug.Directory,
		debugExtDataPerm: cfg.Debug.ExternalDataPerm,
		lock:             sync.RWMutex{},
	}

	return query
}

func (q *query) appendReply(chr CHResponse) {
	q.lock.Lock()
	q.CHResponses = append(q.CHResponses, chr)
	q.lock.Unlock()
}

func (q *query) getDataPoints(ctx context.Context, cond *conditions) error {
	logger := scope.Logger(ctx)
	var err error

	cond.prepareMetricsLists()
	if len(cond.metricsRequested) == 0 {
		return nil
	}

	// carbonlink request
	carbonlinkResponseRead := queryCarbonlink(ctx, carbonlink, cond.metricsUnreverse)

	cond.prepareLookup()
	cond.setStep(q.cStep)
	cond.setFromUntil()
	cond.setPrewhere()
	cond.setWhere()

	queryContext, queryCancel := context.WithCancel(ctx)
	defer queryCancel()
	data := prepareData(queryContext, len(cond.extDataBodies), carbonlinkResponseRead)

	for agg, extTableBody := range cond.extDataBodies {
		extData := q.metricsListExtData(extTableBody)
		query := cond.generateQuery(agg)
		data.wg.Add(1)
		go func() {
			defer data.wg.Done()
			body, err := clickhouse.Reader(
				scope.WithTable(ctx, cond.pointsTable),
				q.chURL,
				query,
				clickhouse.Options{
					Timeout:        q.chDataTimeout,
					ConnectTimeout: q.chConnectTimeout,
				},
				extData,
			)
			if err != nil {
				logger.Error("reader", zap.Error(err))
				queryCancel()
				data.e <- err
			}
			err = data.parseResponse(queryContext, body, cond)
			if err != nil {
				logger.Error("reader", zap.Error(err))
				queryCancel()
				data.e <- err
			}
		}()
	}

	err = data.wait(queryContext)
	if err != nil {
		logger.Error(
			"data_parser", zap.Error(err), zap.Int("read_bytes", data.length),
			zap.String("runtime", data.spent.String()), zap.Duration("runtime_ns", data.spent),
		)
		return err
	}
	logger.Info(
		"data_parse", zap.Int("read_bytes", data.length), zap.Int("read_points", data.Points.Len()),
		zap.String("runtime", data.spent.String()), zap.Duration("runtime_ns", data.spent),
	)

	data.setSteps(cond)
	data.Points.SetAggregations(cond.aggregations)

	// ClickHouse returns sorted and uniq values, when internal aggregation is used
	// But if carbonlink is used, we still need to sort, filter and rollup points
	if !cond.aggregated || carbonlink != nil {
		sortStart := time.Now()
		data.Points.Sort()
		d := time.Since(sortStart)
		logger.Debug("sort", zap.String("runtime", d.String()), zap.Duration("runtime_ns", d))

		data.Points.Uniq()
		rollupStart := time.Now()
		err = cond.rollupRules.RollupPoints(data.Points, cond.From, data.commonStep)
		if err != nil {
			logger.Error("rollup failed", zap.Error(err))
			return err
		}
		rollupTime := time.Since(rollupStart)
		logger.Debug(
			"rollup",
			zap.String("runtime", rollupTime.String()),
			zap.Duration("runtime_ns", rollupTime),
		)
	}

	data.AM = cond.AM

	q.appendReply(CHResponse{
		Data:  data.Data,
		From:  cond.From,
		Until: cond.Until,
	})
	return nil
}

func (q *query) metricsListExtData(body *strings.Builder) *clickhouse.ExternalData {
	extTable := clickhouse.ExternalTable{
		Name: extTableName,
		Columns: []clickhouse.Column{{
			Name: "Path",
			Type: "String",
		}},
		Format: "TSV",
		Data:   []byte(body.String()),
	}

	extData := clickhouse.NewExternalData(extTable)
	extData.SetDebug(q.debugDir, q.debugExtDataPerm)
	return extData
}

func (c *conditions) prepareMetricsLists() {
	c.metricsUnreverse = c.AM.Series(false)
	c.metricsRequested = c.metricsUnreverse

	if c.isReverse {
		c.metricsRequested = make([]string, len(c.metricsRequested))
		for i := range c.metricsRequested {
			c.metricsRequested[i] = reverse.String(c.metricsUnreverse[i])
		}
	}

	c.metricsLookup = c.metricsRequested
	if c.rollupUseReverted {
		c.metricsLookup = c.metricsUnreverse
	}
}

func (c *conditions) prepareLookup() {
	age := uint32(dry.Max(0, time.Now().Unix()-c.From))
	c.aggregations = make(map[string][]string)
	c.extDataBodies = make(map[string]*strings.Builder)
	c.steps = make(map[uint32][]string)
	aggName := ""

	for i := range c.metricsRequested {
		step, agg := c.rollupRules.Lookup(c.metricsLookup[i], age)

		if _, ok := c.steps[step]; !ok {
			c.steps[step] = make([]string, 0)
		}
		// Fill up metric names for steps only for non-aggregated requests.
		// Aggregated use commonStep
		if !c.aggregated {
			c.steps[step] = append(c.steps[step], c.metricsUnreverse[i])
		}

		// Fill up metric names for aggregations
		if mm, ok := c.aggregations[agg.Name()]; ok {
			c.aggregations[agg.Name()] = append(mm, c.metricsUnreverse[i])
		} else {
			c.aggregations[agg.Name()] = []string{c.metricsUnreverse[i]}
		}

		// Build external-data bodies. For non-aggregated requests there is only one request
		if c.aggregated {
			aggName = agg.Name()
		}
		if mm, ok := c.extDataBodies[aggName]; ok {
			mm.WriteString(c.metricsRequested[i] + "\n")
		} else {
			var mm strings.Builder
			c.extDataBodies[aggName] = &mm
			mm.WriteString(c.metricsRequested[i] + "\n")
		}
	}
}

func (c *conditions) setStep(cStep *commonStep) {
	step := int64(0)
	if !c.aggregated {
		// Use max(steps)
		for s := range c.steps {
			step = dry.Max(step, int64(s))
		}
		c.step = step
		return
	}

	// Use LCM(steps)
	// XXX: This could cause problems, when MutliFetchRequest uses different MaxDataPoints,
	// but currently (2021-04-22) it's not possible
	for s := range c.steps {
		step = cStep.calculateUnsafe(step, int64(s))
	}
	cStep.calculate(step)
	step = dry.Max(cStep.getResult(), dry.Ceil(c.Until-c.From, c.MaxDataPoints))
	c.step = dry.CeilToMultiplier(step, cStep.getResult())
	return
}

func (c *conditions) setFromUntil() {
	c.from = dry.CeilToMultiplier(c.From, c.step)
	c.until = dry.FloorToMultiplier(c.Until, c.step) + c.step - 1
}

func (c *conditions) setPrewhere() {
	pw := where.New()
	pw.And(where.DateBetween("Date", c.from, c.until))
	c.prewhere = pw.PreWhereSQL()
}

func (c *conditions) setWhere() {
	wr := where.New()
	wr.And(where.InTable("Path", extTableName))
	wr.And(where.TimestampBetween("Time", c.from, c.until))
	c.where = wr.SQL()
}

func (c *conditions) generateQuery(agg string) string {
	if c.aggregated {
		return c.generateQueryaAggregated(agg)
	}
	return c.generateQueryUnaggregated()
}

func (c *conditions) generateQueryaAggregated(agg string) string {
	return fmt.Sprintf(
		queryAggregated,
		c.from, c.until, c.step, agg,
		c.pointsTable, c.prewhere, c.where,
	)
}

func (c *conditions) generateQueryUnaggregated() string {
	return fmt.Sprintf(queryUnaggregated, c.pointsTable, c.prewhere, c.where)
}
<commit_msg>ClickHouse connection timeout should be controlled by cfg.Clickhouse.ConnectTimeout option, not cfg.Carbonlink.ConnectTimeout<commit_after>package data

import (
	"context"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/helper/clickhouse"
	"github.com/lomik/graphite-clickhouse/pkg/dry"
	"github.com/lomik/graphite-clickhouse/pkg/reverse"
	"github.com/lomik/graphite-clickhouse/pkg/scope"
	"github.com/lomik/graphite-clickhouse/pkg/where"
	"go.uber.org/zap"
)

// from, until, step, function, table, prewhere, where
// arrayFilter(x->isNotNull(x)) - do not pass nulls to client
// -Resample - group time and values by time intervals and apply aggregation function
// -OrNull - if there aren't points in an interval, null will be returned
// intDiv(Time, x)*x - round Time down to step multiplier
// TODO: support custom aggregating functions
const queryAggregated = `WITH anyResample(%[1]d, %[2]d, %[3]d)(toUInt32(intDiv(Time, %[3]d)*%[3]d), Time) AS mask
SELECT Path,
 arrayFilter(m->m!=0, mask) AS times,
 arrayFilter((v,m)->m!=0, %[4]sResample(%[1]d, %[2]d, %[3]d)(Value, Time), mask) AS values
FROM %[5]s
%[6]s
%[7]s
GROUP BY Path
FORMAT RowBinary`

// table, prewhere, where
const queryUnaggregated = `SELECT Path, groupArray(Time), groupArray(Value), groupArray(Timestamp)
FROM %s
%s
%s
GROUP BY Path
FORMAT RowBinary`

// name of external-data table with metrics paths
const extTableName = "metrics_list"

type query struct {
	CHResponses
	cStep            *commonStep
	chURL            string
	chDataTimeout    time.Duration
	chConnectTimeout time.Duration
	debugDir         string
	debugExtDataPerm os.FileMode
	lock             sync.RWMutex
}

type conditions struct {
	*TimeFrame
	*Targets
	// aggregated shows is it request with ClickHouse aggregation or not
	aggregated bool
	// step is used in requests for proper until/from calculation. It's max(steps) for non-aggregated
	// requests and LCM(steps) for aggregated requests
	step int64
	// from is aligned to step
	from int64
	// until is aligned to step
	until int64
	// metricUnreversed grouped by step
	steps map[uint32][]string
	// prewhere contains PREWHERE condition
	prewhere string
	// where contains WHERE condition
	where string
	// metricUnreversed grouped by aggregating function
	aggregations map[string][]string
	// External-data bodies grouped by aggregatig function. For non-aggregated requests "" used as a key
	extDataBodies    map[string]*strings.Builder
	metricsRequested []string
	metricsUnreverse []string
	metricsLookup    []string
}

func newQuery(cfg *config.Config, targets int) *query {
	var cStep *commonStep = nil
	if cfg.ClickHouse.InternalAggregation {
		cStep = &commonStep{
			result: 0,
			wg:     sync.WaitGroup{},
			lock:   sync.RWMutex{},
		}

		cStep.addTargets(targets)
	}

	query := &query{
		CHResponses:      make([]CHResponse, 0, targets),
		cStep:            cStep,
		chURL:            cfg.ClickHouse.URL,
		chDataTimeout:    cfg.ClickHouse.DataTimeout,
		chConnectTimeout: cfg.ClickHouse.ConnectTimeout,
		debugDir:         cfg.Debug.Directory,
		debugExtDataPerm: cfg.Debug.ExternalDataPerm,
		lock:             sync.RWMutex{},
	}

	return query
}

func (q *query) appendReply(chr CHResponse) {
	q.lock.Lock()
	q.CHResponses = append(q.CHResponses, chr)
	q.lock.Unlock()
}

func (q *query) getDataPoints(ctx context.Context, cond *conditions) error {
	logger := scope.Logger(ctx)
	var err error

	cond.prepareMetricsLists()
	if len(cond.metricsRequested) == 0 {
		return nil
	}

	// carbonlink request
	carbonlinkResponseRead := queryCarbonlink(ctx, carbonlink, cond.metricsUnreverse)

	cond.prepareLookup()
	cond.setStep(q.cStep)
	cond.setFromUntil()
	cond.setPrewhere()
	cond.setWhere()

	queryContext, queryCancel := context.WithCancel(ctx)
	defer queryCancel()
	data := prepareData(queryContext, len(cond.extDataBodies), carbonlinkResponseRead)

	for agg, extTableBody := range cond.extDataBodies {
		extData := q.metricsListExtData(extTableBody)
		query := cond.generateQuery(agg)
		data.wg.Add(1)
		go func() {
			defer data.wg.Done()
			body, err := clickhouse.Reader(
				scope.WithTable(ctx, cond.pointsTable),
				q.chURL,
				query,
				clickhouse.Options{
					Timeout:        q.chDataTimeout,
					ConnectTimeout: q.chConnectTimeout,
				},
				extData,
			)
			if err != nil {
				logger.Error("reader", zap.Error(err))
				queryCancel()
				data.e <- err
			}
			err = data.parseResponse(queryContext, body, cond)
			if err != nil {
				logger.Error("reader", zap.Error(err))
				queryCancel()
				data.e <- err
			}
		}()
	}

	err = data.wait(queryContext)
	if err != nil {
		logger.Error(
			"data_parser", zap.Error(err), zap.Int("read_bytes", data.length),
			zap.String("runtime", data.spent.String()), zap.Duration("runtime_ns", data.spent),
		)
		return err
	}
	logger.Info(
		"data_parse", zap.Int("read_bytes", data.length), zap.Int("read_points", data.Points.Len()),
		zap.String("runtime", data.spent.String()), zap.Duration("runtime_ns", data.spent),
	)

	data.setSteps(cond)
	data.Points.SetAggregations(cond.aggregations)

	// ClickHouse returns sorted and uniq values, when internal aggregation is used
	// But if carbonlink is used, we still need to sort, filter and rollup points
	if !cond.aggregated || carbonlink != nil {
		sortStart := time.Now()
		data.Points.Sort()
		d := time.Since(sortStart)
		logger.Debug("sort", zap.String("runtime", d.String()), zap.Duration("runtime_ns", d))

		data.Points.Uniq()
		rollupStart := time.Now()
		err = cond.rollupRules.RollupPoints(data.Points, cond.From, data.commonStep)
		if err != nil {
			logger.Error("rollup failed", zap.Error(err))
			return err
		}
		rollupTime := time.Since(rollupStart)
		logger.Debug(
			"rollup",
			zap.String("runtime", rollupTime.String()),
			zap.Duration("runtime_ns", rollupTime),
		)
	}

	data.AM = cond.AM

	q.appendReply(CHResponse{
		Data:  data.Data,
		From:  cond.From,
		Until: cond.Until,
	})
	return nil
}

func (q *query) metricsListExtData(body *strings.Builder) *clickhouse.ExternalData {
	extTable := clickhouse.ExternalTable{
		Name: extTableName,
		Columns: []clickhouse.Column{{
			Name: "Path",
			Type: "String",
		}},
		Format: "TSV",
		Data:   []byte(body.String()),
	}

	extData := clickhouse.NewExternalData(extTable)
	extData.SetDebug(q.debugDir, q.debugExtDataPerm)
	return extData
}

func (c *conditions) prepareMetricsLists() {
	c.metricsUnreverse = c.AM.Series(false)
	c.metricsRequested = c.metricsUnreverse

	if c.isReverse {
		c.metricsRequested = make([]string, len(c.metricsRequested))
		for i := range c.metricsRequested {
			c.metricsRequested[i] = reverse.String(c.metricsUnreverse[i])
		}
	}

	c.metricsLookup = c.metricsRequested
	if c.rollupUseReverted {
		c.metricsLookup = c.metricsUnreverse
	}
}

func (c *conditions) prepareLookup() {
	age := uint32(dry.Max(0, time.Now().Unix()-c.From))
	c.aggregations = make(map[string][]string)
	c.extDataBodies = make(map[string]*strings.Builder)
	c.steps = make(map[uint32][]string)
	aggName := ""

	for i := range c.metricsRequested {
		step, agg := c.rollupRules.Lookup(c.metricsLookup[i], age)

		if _, ok := c.steps[step]; !ok {
			c.steps[step] = make([]string, 0)
		}
		// Fill up metric names for steps only for non-aggregated requests.
		// Aggregated use commonStep
		if !c.aggregated {
			c.steps[step] = append(c.steps[step], c.metricsUnreverse[i])
		}

		// Fill up metric names for aggregations
		if mm, ok := c.aggregations[agg.Name()]; ok {
			c.aggregations[agg.Name()] = append(mm, c.metricsUnreverse[i])
		} else {
			c.aggregations[agg.Name()] = []string{c.metricsUnreverse[i]}
		}

		// Build external-data bodies. For non-aggregated requests there is only one request
		if c.aggregated {
			aggName = agg.Name()
		}
		if mm, ok := c.extDataBodies[aggName]; ok {
			mm.WriteString(c.metricsRequested[i] + "\n")
		} else {
			var mm strings.Builder
			c.extDataBodies[aggName] = &mm
			mm.WriteString(c.metricsRequested[i] + "\n")
		}
	}
}

func (c *conditions) setStep(cStep *commonStep) {
	step := int64(0)
	if !c.aggregated {
		// Use max(steps)
		for s := range c.steps {
			step = dry.Max(step, int64(s))
		}
		c.step = step
		return
	}

	// Use LCM(steps)
	// XXX: This could cause problems, when MutliFetchRequest uses different MaxDataPoints,
	// but currently (2021-04-22) it's not possible
	for s := range c.steps {
		step = cStep.calculateUnsafe(step, int64(s))
	}
	cStep.calculate(step)
	step = dry.Max(cStep.getResult(), dry.Ceil(c.Until-c.From, c.MaxDataPoints))
	c.step = dry.CeilToMultiplier(step, cStep.getResult())
	return
}

func (c *conditions) setFromUntil() {
	c.from = dry.CeilToMultiplier(c.From, c.step)
	c.until = dry.FloorToMultiplier(c.Until, c.step) + c.step - 1
}

func (c *conditions) setPrewhere() {
	pw := where.New()
	pw.And(where.DateBetween("Date", c.from, c.until))
	c.prewhere = pw.PreWhereSQL()
}

func (c *conditions) setWhere() {
	wr := where.New()
	wr.And(where.InTable("Path", extTableName))
	wr.And(where.TimestampBetween("Time", c.from, c.until))
	c.where = wr.SQL()
}

func (c *conditions) generateQuery(agg string) string {
	if c.aggregated {
		return c.generateQueryaAggregated(agg)
	}
	return c.generateQueryUnaggregated()
}

func (c *conditions) generateQueryaAggregated(agg string) string {
	return fmt.Sprintf(
		queryAggregated,
		c.from, c.until, c.step, agg,
		c.pointsTable, c.prewhere, c.where,
	)
}

func (c *conditions) generateQueryUnaggregated() string {
	return fmt.Sprintf(queryUnaggregated, c.pointsTable, c.prewhere, c.where)
}
<|endoftext|>


<commit_before>package render

import (
	"golang.org/x/exp/shiny/screen"
	"image"
	"image/draw"
	//"image/color"
)

var (
	gameScreen *screen.Screen
	// spriteNames = []string{"Empty":"textures/tile1.png", "textures/tile2.png"}
	spriteNames = map[string]string{"Empty": "textures/tile1.png", "Wall": "textures/tile2.png"}
)

type Renderable interface {
	GetRGBA() *image.RGBA
	//ApplyColor(*image.color) *Renderable
	//ApplyMask(??) *Renderable
	//Rotate(degrees int) *Renderable
	//Scale(xRatio int, yRatio int) *Renderable
	//ShiftX(x int)
	//ShiftY(y int)
	Draw(buff screen.Buffer)

	// Squish the renderable into a geometry
	// FitTo(geometry) *Renderable
}

func ParseSprite(s string) Sprite {
	return LoadSprite(spriteNames[s])
}

func SetScreen(s *screen.Screen) {
	gameScreen = s
}

func GetScreen() *screen.Screen {
	return gameScreen
}

type Sprite struct {
	x, y   float64
	buffer *screen.Buffer
}

func (s Sprite) GetRGBA() *image.RGBA {
	return (*s.buffer).RGBA()
}

//func (s *Sprite) ApplyColor(*image.color) *Renderable {

///}

//func (s *Sprite) ApplyMask(??) *Renderable
//func (s *Sprite) Rotate(degrees int) *Renderable {

//}
//func (s *Sprite) Scale(xRatio int, yRatio int) *Renderable {

//}
func (s_p *Sprite) ShiftX(x float64) {
	s_p.x += x
}
func (s_p *Sprite) ShiftY(y float64) {
	s_p.y += y
}

// func (s_p *Sprite) Draw(w screen.Window) {
// 	s := *s_p
// 	w.Upload(image.Point{s.x, s.y}, *(s.buffer), (*(s.buffer)).Bounds())
// }
func (s Sprite) Draw(buff screen.Buffer) {
	// s := *s_p
	img := (&s).GetRGBA()
	draw.Draw(buff.RGBA(), buff.Bounds(),
		img, image.Point{int((&s).x),
			int((&s).y)}, draw.Src)
}
<commit_msg>basic connection of rooms, Empty level tiles now supported<commit_after>package render

import (
	"golang.org/x/exp/shiny/screen"
	"image"
	"image/draw"
	//"image/color"
)

var (
	gameScreen *screen.Screen
	// spriteNames = []string{"Empty":"textures/tile1.png", "textures/tile2.png"}
	spriteNames = map[string]string{"Empty": "textures/tile1.png", "Wall": "textures/tile2.png"}
)

type Renderable interface {
	GetRGBA() *image.RGBA
	//ApplyColor(*image.color) *Renderable
	//ApplyMask(??) *Renderable
	//Rotate(degrees int) *Renderable
	//Scale(xRatio int, yRatio int) *Renderable
	//ShiftX(x int)
	//ShiftY(y int)
	Draw(buff screen.Buffer)

	// Squish the renderable into a geometry
	// FitTo(geometry) *Renderable
}

func ParseSprite(s string) Sprite {
	return LoadSprite(spriteNames[s])
}

func SetScreen(s *screen.Screen) {
	gameScreen = s
}

func GetScreen() *screen.Screen {
	return gameScreen
}

type Sprite struct {
	x, y   float64
	buffer *screen.Buffer
}

func (s Sprite) GetRGBA() *image.RGBA {
	return (*s.buffer).RGBA()
}

func (s Sprite) HasBuffer() bool {
	if s.buffer != nil {
		return true
	}
	return false
}

//func (s *Sprite) ApplyColor(*image.color) *Renderable {

///}

//func (s *Sprite) ApplyMask(??) *Renderable

//func (s *Sprite) Rotate(degrees int) *Renderable {

//}
//func (s *Sprite) Scale(xRatio int, yRatio int) *Renderable {

//}
func (s_p *Sprite) ShiftX(x float64) {
	s_p.x += x
}
func (s_p *Sprite) ShiftY(y float64) {
	s_p.y += y
}

// func (s_p *Sprite) Draw(w screen.Window) {
// 	s := *s_p
// 	w.Upload(image.Point{s.x, s.y}, *(s.buffer), (*(s.buffer)).Bounds())
// }
func (s Sprite) Draw(buff screen.Buffer) {
	// s := *s_p
	img := (&s).GetRGBA()
	draw.Draw(buff.RGBA(), buff.Bounds(),
		img, image.Point{int((&s).x),
			int((&s).y)}, draw.Src)
}
<|endoftext|>


<commit_before>package plugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/mcilloni/go-openbaton/util"
	log "github.com/sirupsen/logrus"
	"github.com/streadway/amqp"
)

const pluginExchange = "plugin-exchange"

type Params struct {
	BrokerIP, LogFile, Name, Password, Type, Username string
	Workers, Port                                     int
	LogLevel                                          log.Level
}

type Plugin interface {
	Logger() *log.Logger
	Serve() error
	Stop() error
	Type() string
}

func New(impl interface{}, p *Params) (Plugin, error) {
	tag := util.FuncName()

	if p.Workers < 1 {
		p.Workers = 10
	}

	plug := &plug{
		connstr:              util.AmqpUriBuilder(p.Username, p.Password, p.BrokerIP, "", p.Port, false),
		params:               p,
		quitChan:             make(chan error, 1),
		receiverDeliveryChan: make(chan (<-chan amqp.Delivery), 1),
		reqChan:              make(chan request, 30),
	}

	if err := plug.initLogger(); err != nil {
		return nil, err
	}

	var rh reqHandler

	switch v := impl.(type) {
	case Driver:
		rh = driverHandler{v, plug.l}

	// in case we are reinitialising the plugin
	case reqHandler:
		rh = v

	default:
		plug.l.WithField("tag", tag).Panicf("invalid plugin implementation %T", impl)
	}

	plug.rh = rh

	return plug, nil
}

type plug struct {
	cnl     *amqp.Channel
	conn    *amqp.Connection
	connstr string

	l                    *log.Logger
	e                    logData
	params               *Params
	quitChan             chan error
	receiverDeliveryChan chan (<-chan amqp.Delivery)
	reqChan              chan request
	rh                   reqHandler
	stopped              bool
	wg                   sync.WaitGroup
}

func (p *plug) Logger() *log.Logger {
	return p.l
}

func (p *plug) Serve() error {
	tag := util.FuncName()

	// reinit the plugin if already stopped
	if p.stopped {
		panic("plugin already stopped")
	}

	p.l.WithFields(log.Fields{
		"tag":    tag,
		"params": *p.params,
	}).Debug("plugin starting")

	errChan, err := p.setup()
	if err != nil {
		return err
	}

	p.spawnWorkers()
	p.spawnReceiver()

MainLoop:
	for {
		select {
		case <-p.quitChan:
			if err = p.conn.Close(); err != nil {
				p.l.WithError(err).WithFields(log.Fields{
					"tag": tag,
				}).Error("closing Connection failed")

				p.closeQueues()

				// send the error to stop
				p.quitChan <- err
				return nil
			}

			p.l.WithFields(log.Fields{
				"tag": tag,
			}).Info("initiating clean shutdown")

			// Close will cause the reception of nil on errChan.

		case amqpErr := <-errChan:
			// The connection closed cleanly after invoking Close().
			if amqpErr == nil {
				// notify the receiver and workers
				p.closeQueues()

				p.wg.Wait()

				// send nil to Stop
				close(p.quitChan)

				p.l.WithFields(log.Fields{
					"tag": tag,
				}).Debug("main loop quitting")

				break MainLoop
			}

			p.l.WithError(amqpErr).WithFields(log.Fields{
				"tag": tag,
			}).Error("received AMQP error for current connection")

			// The connection crashed for some reason. Try to bring it up again.
			for {
				if errChan, err = p.setup(); err != nil {
					p.l.WithError(err).WithFields(log.Fields{
						"tag": tag,
					}).Error("can't re-establish connection with AMQP; queues stalled. Retrying in 30 seconds.")
					time.Sleep(30 * time.Second)
				}
			}

		}
	}

	return nil
}

func (p *plug) Stop() error {
	tag := util.FuncName()

	defer p.deinitLogger()

	if p.stopped {
		return fmt.Errorf("plugin %s already stopped", p.params.Name)
	}

	// first step: signal the main routine to quit.
	select {
	case p.quitChan <- nil:

	case <-time.After(time.Second):
		return errors.New("the plugin is not listening")
	}

	// second step: wait for it to quit
	select {
	case err := <-p.quitChan:
		if err != nil {
			return err
		}
	case <-time.After(1 * time.Minute):
		return errors.New("the plugin refused to quit")
	}

	p.stopped = true

	p.l.WithFields(log.Fields{
		"tag": tag,
	}).Info("plugin stopped cleanly")

	return nil
}

func (p *plug) Type() string {
	return p.rh.Type()
}

func (p *plug) closeQueues() {
	// closes the workers
	close(p.reqChan)

	// closes the receiver
	close(p.receiverDeliveryChan)
}

func (p *plug) id() string {
	return fmt.Sprintf("%s.%s.%s", p.rh.QueueTag(), p.params.Type, p.params.Name)
}

func (p *plug) setup() (<-chan *amqp.Error, error) {
	tag := util.FuncName()

	p.l.WithFields(log.Fields{
		"tag": tag,
	}).Info("dialing AMQP")

	conn, err := amqp.Dial(p.connstr)
	if err != nil {
		return nil, err
	}

	cnl, err := conn.Channel()
	if err != nil {
		return nil, err
	}

	if err := cnl.ExchangeDeclare(pluginExchange, "topic", false,
		false, false, false, nil); err != nil {
		return nil, err
	}

	queueName := p.id()
	if _, err := cnl.QueueDeclare(queueName, false, true,
		false, false, nil); err != nil {
		return nil, err
	}

	if err := cnl.QueueBind(queueName, queueName, pluginExchange, false, nil); err != nil {
		return nil, err
	}

	if err := cnl.Qos(1, 0, false); err != nil {
		return nil, err
	}

	p.conn = conn
	p.cnl = cnl

	// setup incoming deliveries
	deliveries, err := cnl.Consume(
		queueName, // queue
		"",        // consumer
		false,     // auto-ack
		false,     // exclusive
		false,     // no-local
		false,     // no-wait
		nil,       // args
	)
	if err != nil {
		return nil, err
	}

	p.receiverDeliveryChan <- deliveries

	return conn.NotifyClose(make(chan *amqp.Error)), nil
}

type reqHandler interface {
	Handle(call string, args []json.RawMessage) (interface{}, error)
	QueueTag() string
	Type() string
}
<commit_msg>quitChan bug<commit_after>package plugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/mcilloni/go-openbaton/util"
	log "github.com/sirupsen/logrus"
	"github.com/streadway/amqp"
)

const pluginExchange = "plugin-exchange"

type Params struct {
	BrokerIP, LogFile, Name, Password, Type, Username string
	Workers, Port                                     int
	LogLevel                                          log.Level
}

type Plugin interface {
	Logger() *log.Logger
	Serve() error
	Stop() error
	Type() string
}

func New(impl interface{}, p *Params) (Plugin, error) {
	tag := util.FuncName()

	if p.Workers < 1 {
		p.Workers = 10
	}

	plug := &plug{
		connstr:              util.AmqpUriBuilder(p.Username, p.Password, p.BrokerIP, "", p.Port, false),
		params:               p,
		quitChan:             make(chan error),
		receiverDeliveryChan: make(chan (<-chan amqp.Delivery), 1),
		reqChan:              make(chan request, 30),
	}

	if err := plug.initLogger(); err != nil {
		return nil, err
	}

	var rh reqHandler

	switch v := impl.(type) {
	case Driver:
		rh = driverHandler{v, plug.l}

	// in case we are reinitialising the plugin
	case reqHandler:
		rh = v

	default:
		plug.l.WithField("tag", tag).Panicf("invalid plugin implementation %T", impl)
	}

	plug.rh = rh

	return plug, nil
}

type plug struct {
	cnl     *amqp.Channel
	conn    *amqp.Connection
	connstr string

	l                    *log.Logger
	e                    logData
	params               *Params
	quitChan             chan error
	receiverDeliveryChan chan (<-chan amqp.Delivery)
	reqChan              chan request
	rh                   reqHandler
	stopped              bool
	wg                   sync.WaitGroup
}

func (p *plug) Logger() *log.Logger {
	return p.l
}

func (p *plug) Serve() error {
	tag := util.FuncName()

	// reinit the plugin if already stopped
	if p.stopped {
		panic("plugin already stopped")
	}

	p.l.WithFields(log.Fields{
		"tag":    tag,
		"params": *p.params,
	}).Debug("plugin starting")

	errChan, err := p.setup()
	if err != nil {
		return err
	}

	p.spawnWorkers()
	p.spawnReceiver()

MainLoop:
	for {
		select {
		case <-p.quitChan:
			if err = p.conn.Close(); err != nil {
				p.l.WithError(err).WithFields(log.Fields{
					"tag": tag,
				}).Error("closing Connection failed")

				p.closeQueues()

				// send the error to stop
				p.quitChan <- err
				return nil
			}

			p.l.WithFields(log.Fields{
				"tag": tag,
			}).Info("initiating clean shutdown")

			// Close will cause the reception of nil on errChan.

		case amqpErr := <-errChan:
			// The connection closed cleanly after invoking Close().
			if amqpErr == nil {
				// notify the receiver and workers
				p.closeQueues()

				p.wg.Wait()

				// send nil to Stop
				close(p.quitChan)

				p.l.WithFields(log.Fields{
					"tag": tag,
				}).Debug("main loop quitting")

				break MainLoop
			}

			p.l.WithError(amqpErr).WithFields(log.Fields{
				"tag": tag,
			}).Error("received AMQP error for current connection")

			// The connection crashed for some reason. Try to bring it up again.
			for {
				if errChan, err = p.setup(); err != nil {
					p.l.WithError(err).WithFields(log.Fields{
						"tag": tag,
					}).Error("can't re-establish connection with AMQP; queues stalled. Retrying in 30 seconds.")
					time.Sleep(30 * time.Second)
				}
			}

		}
	}

	return nil
}

func (p *plug) Stop() error {
	tag := util.FuncName()

	defer p.deinitLogger()

	if p.stopped {
		return fmt.Errorf("plugin %s already stopped", p.params.Name)
	}

	// first step: signal the main routine to quit.
	select {
	case p.quitChan <- nil:

	case <-time.After(time.Second):
		return errors.New("the plugin is not listening")
	}

	// second step: wait for it to quit
	select {
	case err := <-p.quitChan:
		if err != nil {
			return err
		}
	case <-time.After(1 * time.Minute):
		return errors.New("the plugin refused to quit")
	}

	p.stopped = true

	p.l.WithFields(log.Fields{
		"tag": tag,
	}).Info("plugin stopped cleanly")

	return nil
}

func (p *plug) Type() string {
	return p.rh.Type()
}

func (p *plug) closeQueues() {
	// closes the workers
	close(p.reqChan)

	// closes the receiver
	close(p.receiverDeliveryChan)
}

func (p *plug) id() string {
	return fmt.Sprintf("%s.%s.%s", p.rh.QueueTag(), p.params.Type, p.params.Name)
}

func (p *plug) setup() (<-chan *amqp.Error, error) {
	tag := util.FuncName()

	p.l.WithFields(log.Fields{
		"tag": tag,
	}).Info("dialing AMQP")

	conn, err := amqp.Dial(p.connstr)
	if err != nil {
		return nil, err
	}

	cnl, err := conn.Channel()
	if err != nil {
		return nil, err
	}

	if err := cnl.ExchangeDeclare(pluginExchange, "topic", false,
		false, false, false, nil); err != nil {
		return nil, err
	}

	queueName := p.id()
	if _, err := cnl.QueueDeclare(queueName, false, true,
		false, false, nil); err != nil {
		return nil, err
	}

	if err := cnl.QueueBind(queueName, queueName, pluginExchange, false, nil); err != nil {
		return nil, err
	}

	if err := cnl.Qos(1, 0, false); err != nil {
		return nil, err
	}

	p.conn = conn
	p.cnl = cnl

	// setup incoming deliveries
	deliveries, err := cnl.Consume(
		queueName, // queue
		"",        // consumer
		false,     // auto-ack
		false,     // exclusive
		false,     // no-local
		false,     // no-wait
		nil,       // args
	)
	if err != nil {
		return nil, err
	}

	p.receiverDeliveryChan <- deliveries

	return conn.NotifyClose(make(chan *amqp.Error)), nil
}

type reqHandler interface {
	Handle(call string, args []json.RawMessage) (interface{}, error)
	QueueTag() string
	Type() string
}
<|endoftext|>


<commit_before>// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package jsre

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/fatih/color"
	"github.com/robertkrimen/otto"
)

const (
	maxPrettyPrintLevel = 3
	indentString        = "  "
)

var (
	functionColor = color.New(color.FgMagenta)
	specialColor  = color.New(color.Bold)
	numberColor   = color.New(color.FgRed)
	stringColor   = color.New(color.FgGreen)
)

// these fields are hidden when printing objects.
var boringKeys = map[string]bool{
	"valueOf":              true,
	"toString":             true,
	"toLocaleString":       true,
	"hasOwnProperty":       true,
	"isPrototypeOf":        true,
	"propertyIsEnumerable": true,
	"constructor":          true,
}

// prettyPrint writes value to standard output.
func prettyPrint(vm *otto.Otto, value otto.Value) {
	ppctx{vm}.printValue(value, 0)
}

func prettyPrintJS(call otto.FunctionCall) otto.Value {
	for _, v := range call.ArgumentList {
		prettyPrint(call.Otto, v)
		fmt.Println()
	}
	return otto.UndefinedValue()
}

type ppctx struct{ vm *otto.Otto }

func (ctx ppctx) indent(level int) string {
	return strings.Repeat(indentString, level)
}

func (ctx ppctx) printValue(v otto.Value, level int) {
	switch {
	case v.IsObject():
		ctx.printObject(v.Object(), level)
	case v.IsNull():
		specialColor.Print("null")
	case v.IsUndefined():
		specialColor.Print("undefined")
	case v.IsString():
		s, _ := v.ToString()
		stringColor.Printf("%q", s)
	case v.IsBoolean():
		b, _ := v.ToBoolean()
		specialColor.Printf("%t", b)
	case v.IsNaN():
		numberColor.Printf("NaN")
	case v.IsNumber():
		s, _ := v.ToString()
		numberColor.Printf("%s", s)
	default:
		fmt.Printf("<unprintable>")
	}
}

func (ctx ppctx) printObject(obj *otto.Object, level int) {
	switch obj.Class() {
	case "Array":
		lv, _ := obj.Get("length")
		len, _ := lv.ToInteger()
		if len == 0 {
			fmt.Printf("[]")
			return
		}
		if level > maxPrettyPrintLevel {
			fmt.Print("[...]")
			return
		}
		fmt.Print("[")
		for i := int64(0); i < len; i++ {
			el, err := obj.Get(strconv.FormatInt(i, 10))
			if err == nil {
				ctx.printValue(el, level+1)
			}
			if i < len-1 {
				fmt.Printf(", ")
			}
		}
		fmt.Print("]")

	case "Object":
		// Print values from bignumber.js as regular numbers.
		if ctx.isBigNumber(obj) {
			numberColor.Print(toString(obj))
			return
		}
		// Otherwise, print all fields indented, but stop if we're too deep.
		keys := ctx.fields(obj)
		if len(keys) == 0 {
			fmt.Print("{}")
			return
		}
		if level > maxPrettyPrintLevel {
			fmt.Print("{...}")
			return
		}
		fmt.Println("{")
		for i, k := range keys {
			v, _ := obj.Get(k)
			fmt.Printf("%s%s: ", ctx.indent(level+1), k)
			ctx.printValue(v, level+1)
			if i < len(keys)-1 {
				fmt.Printf(",")
			}
			fmt.Println()
		}
		fmt.Printf("%s}", ctx.indent(level))

	case "Function":
		// Use toString() to display the argument list if possible.
		if robj, err := obj.Call("toString"); err != nil {
			functionColor.Print("function()")
		} else {
			desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
			desc = strings.Replace(desc, " (", "(", 1)
			functionColor.Print(desc)
		}

	case "RegExp":
		stringColor.Print(toString(obj))

	default:
		if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel {
			s, _ := obj.Call("toString")
			fmt.Printf("<%s %s>", obj.Class(), s.String())
		} else {
			fmt.Printf("<%s>", obj.Class())
		}
	}
}

func (ctx ppctx) fields(obj *otto.Object) []string {
	var (
		vals, methods []string
		seen          = make(map[string]bool)
	)
	add := func(k string) {
		if seen[k] || boringKeys[k] {
			return
		}
		seen[k] = true
		if v, _ := obj.Get(k); v.IsFunction() {
			methods = append(methods, k)
		} else {
			vals = append(vals, k)
		}
	}
	// add own properties
	ctx.doOwnProperties(obj.Value(), add)
	// add properties of the constructor
	if cp := constructorPrototype(obj); cp != nil {
		ctx.doOwnProperties(cp.Value(), add)
	}
	sort.Strings(vals)
	sort.Strings(methods)
	return append(vals, methods...)
}

func (ctx ppctx) doOwnProperties(v otto.Value, f func(string)) {
	Object, _ := ctx.vm.Object("Object")
	rv, _ := Object.Call("getOwnPropertyNames", v)
	gv, _ := rv.Export()
	for _, v := range gv.([]interface{}) {
		f(v.(string))
	}
}

func (ctx ppctx) isBigNumber(v *otto.Object) bool {
	BigNumber, err := ctx.vm.Run("BigNumber.prototype")
	if err != nil {
		panic(err)
	}
	cp := constructorPrototype(v)
	return cp != nil && cp.Value() == BigNumber
}

func toString(obj *otto.Object) string {
	s, _ := obj.Call("toString")
	return s.String()
}

func constructorPrototype(obj *otto.Object) *otto.Object {
	if v, _ := obj.Get("constructor"); v.Object() != nil {
		if v, _ = v.Object().Get("prototype"); v.Object() != nil {
			return v.Object()
		}
	}
	return nil
}
<commit_msg>jsre: fix annoying indentation when printing arrays of objects<commit_after>// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package jsre

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/fatih/color"
	"github.com/robertkrimen/otto"
)

const (
	maxPrettyPrintLevel = 3
	indentString        = "  "
)

var (
	functionColor = color.New(color.FgMagenta)
	specialColor  = color.New(color.Bold)
	numberColor   = color.New(color.FgRed)
	stringColor   = color.New(color.FgGreen)
)

// these fields are hidden when printing objects.
var boringKeys = map[string]bool{
	"valueOf":              true,
	"toString":             true,
	"toLocaleString":       true,
	"hasOwnProperty":       true,
	"isPrototypeOf":        true,
	"propertyIsEnumerable": true,
	"constructor":          true,
}

// prettyPrint writes value to standard output.
func prettyPrint(vm *otto.Otto, value otto.Value) {
	ppctx{vm}.printValue(value, 0, false)
}

func prettyPrintJS(call otto.FunctionCall) otto.Value {
	for _, v := range call.ArgumentList {
		prettyPrint(call.Otto, v)
		fmt.Println()
	}
	return otto.UndefinedValue()
}

type ppctx struct{ vm *otto.Otto }

func (ctx ppctx) indent(level int) string {
	return strings.Repeat(indentString, level)
}

func (ctx ppctx) printValue(v otto.Value, level int, inArray bool) {
	switch {
	case v.IsObject():
		ctx.printObject(v.Object(), level, inArray)
	case v.IsNull():
		specialColor.Print("null")
	case v.IsUndefined():
		specialColor.Print("undefined")
	case v.IsString():
		s, _ := v.ToString()
		stringColor.Printf("%q", s)
	case v.IsBoolean():
		b, _ := v.ToBoolean()
		specialColor.Printf("%t", b)
	case v.IsNaN():
		numberColor.Printf("NaN")
	case v.IsNumber():
		s, _ := v.ToString()
		numberColor.Printf("%s", s)
	default:
		fmt.Printf("<unprintable>")
	}
}

func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
	switch obj.Class() {
	case "Array":
		lv, _ := obj.Get("length")
		len, _ := lv.ToInteger()
		if len == 0 {
			fmt.Printf("[]")
			return
		}
		if level > maxPrettyPrintLevel {
			fmt.Print("[...]")
			return
		}
		fmt.Print("[")
		for i := int64(0); i < len; i++ {
			el, err := obj.Get(strconv.FormatInt(i, 10))
			if err == nil {
				ctx.printValue(el, level+1, true)
			}
			if i < len-1 {
				fmt.Printf(", ")
			}
		}
		fmt.Print("]")

	case "Object":
		// Print values from bignumber.js as regular numbers.
		if ctx.isBigNumber(obj) {
			numberColor.Print(toString(obj))
			return
		}
		// Otherwise, print all fields indented, but stop if we're too deep.
		keys := ctx.fields(obj)
		if len(keys) == 0 {
			fmt.Print("{}")
			return
		}
		if level > maxPrettyPrintLevel {
			fmt.Print("{...}")
			return
		}
		fmt.Println("{")
		for i, k := range keys {
			v, _ := obj.Get(k)
			fmt.Printf("%s%s: ", ctx.indent(level+1), k)
			ctx.printValue(v, level+1, false)
			if i < len(keys)-1 {
				fmt.Printf(",")
			}
			fmt.Println()
		}
		if inArray {
			level--
		}
		fmt.Printf("%s}", ctx.indent(level))

	case "Function":
		// Use toString() to display the argument list if possible.
		if robj, err := obj.Call("toString"); err != nil {
			functionColor.Print("function()")
		} else {
			desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
			desc = strings.Replace(desc, " (", "(", 1)
			functionColor.Print(desc)
		}

	case "RegExp":
		stringColor.Print(toString(obj))

	default:
		if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel {
			s, _ := obj.Call("toString")
			fmt.Printf("<%s %s>", obj.Class(), s.String())
		} else {
			fmt.Printf("<%s>", obj.Class())
		}
	}
}

func (ctx ppctx) fields(obj *otto.Object) []string {
	var (
		vals, methods []string
		seen          = make(map[string]bool)
	)
	add := func(k string) {
		if seen[k] || boringKeys[k] {
			return
		}
		seen[k] = true
		if v, _ := obj.Get(k); v.IsFunction() {
			methods = append(methods, k)
		} else {
			vals = append(vals, k)
		}
	}
	// add own properties
	ctx.doOwnProperties(obj.Value(), add)
	// add properties of the constructor
	if cp := constructorPrototype(obj); cp != nil {
		ctx.doOwnProperties(cp.Value(), add)
	}
	sort.Strings(vals)
	sort.Strings(methods)
	return append(vals, methods...)
}

func (ctx ppctx) doOwnProperties(v otto.Value, f func(string)) {
	Object, _ := ctx.vm.Object("Object")
	rv, _ := Object.Call("getOwnPropertyNames", v)
	gv, _ := rv.Export()
	for _, v := range gv.([]interface{}) {
		f(v.(string))
	}
}

func (ctx ppctx) isBigNumber(v *otto.Object) bool {
	BigNumber, err := ctx.vm.Run("BigNumber.prototype")
	if err != nil {
		panic(err)
	}
	cp := constructorPrototype(v)
	return cp != nil && cp.Value() == BigNumber
}

func toString(obj *otto.Object) string {
	s, _ := obj.Call("toString")
	return s.String()
}

func constructorPrototype(obj *otto.Object) *otto.Object {
	if v, _ := obj.Get("constructor"); v.Object() != nil {
		if v, _ = v.Object().Get("prototype"); v.Object() != nil {
			return v.Object()
		}
	}
	return nil
}
<|endoftext|>