<commit_before>package integrate

import (
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/ajvb/kala/api"
	"github.com/ajvb/kala/client"
	"github.com/ajvb/kala/job"
	"github.com/mixer/clock"
)

func TestIntegrationTest(t *testing.T) {

	jobDB := &job.MockDB{}
	cache := job.NewLockFreeJobCache(jobDB)

	clk := clock.NewMockClock()
	cache.Clock.SetClock(clk)

	addr := freeTCPAddr(t)

	kalaApi := api.MakeServer(addr, cache, jobDB, "default@example.com")
	go kalaApi.ListenAndServe()
	kalaClient := client.New("http://" + addr)

	hit := make(chan struct{})
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hit <- struct{}{}
		w.WriteHeader(200)
	}))

	scheduleTime := clk.Now().Add(time.Minute * 3)
	parsedTime := scheduleTime.Format(time.RFC3339)
	delay := "PT5M"
	scheduleStr := fmt.Sprintf("R/%s/%s", parsedTime, delay)
	body := &job.Job{
		Name:     "Increment HitCount",
		Schedule: scheduleStr,
		JobType:  job.RemoteJob,
		RemoteProperties: job.RemoteProperties{
			Url:                   "http://" + srv.Listener.Addr().String(),
			Method:                http.MethodGet,
			ExpectedResponseCodes: []int{200},
		},
	}
	_, err := kalaClient.CreateJob(body)
	if err != nil {
		t.Fatal(err)
	}

	clk.AddTime(time.Minute)
	select {
	case <-hit:
		t.Fatalf("Did not expect job to have run yet.")
	case <-time.After(time.Second):
	}

	clk.AddTime(time.Minute)
	select {
	case <-hit:
		t.Fatalf("Still did not expect job to have run yet.")
	case <-time.After(time.Second):
	}

	clk.AddTime(time.Minute * 3)
	select {
	case <-hit:
	case <-time.After(time.Second * 5):
		t.Fatalf("Expected job to have run.")
	}

	clk.AddTime(time.Minute * 5)
	select {
	case <-hit:
	case <-time.After(time.Second * 5):
		t.Fatalf("Expected job to have run again.")
	}

}

func freeTCPAddr(t *testing.T) string {
	addr := &net.TCPAddr{
		IP: net.IPv4(127, 0, 0, 1),
	}

	lis, err := net.ListenTCP("tcp", addr)
	if err != nil {
		t.Fatal(err)
	}
	defer lis.Close()

	return lis.Addr().String()
}
<commit_msg>Make the integration test listener code more closely match that of httptest<commit_after>package integrate

import (
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/ajvb/kala/api"
	"github.com/ajvb/kala/client"
	"github.com/ajvb/kala/job"
	"github.com/mixer/clock"
)

func TestIntegrationTest(t *testing.T) {

	jobDB := &job.MockDB{}
	cache := job.NewLockFreeJobCache(jobDB)

	clk := clock.NewMockClock()
	cache.Clock.SetClock(clk)

	addr := newLocalListener(t)

	kalaApi := api.MakeServer(addr, cache, jobDB, "default@example.com")
	go kalaApi.ListenAndServe()
	kalaClient := client.New("http://" + addr)

	hit := make(chan struct{})
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hit <- struct{}{}
		w.WriteHeader(200)
	}))

	scheduleTime := clk.Now().Add(time.Minute * 3)
	parsedTime := scheduleTime.Format(time.RFC3339)
	delay := "PT5M"
	scheduleStr := fmt.Sprintf("R/%s/%s", parsedTime, delay)
	body := &job.Job{
		Name:     "Increment HitCount",
		Schedule: scheduleStr,
		JobType:  job.RemoteJob,
		RemoteProperties: job.RemoteProperties{
			Url:                   "http://" + srv.Listener.Addr().String(),
			Method:                http.MethodGet,
			ExpectedResponseCodes: []int{200},
		},
	}
	_, err := kalaClient.CreateJob(body)
	if err != nil {
		t.Fatal(err)
	}

	clk.AddTime(time.Minute)
	select {
	case <-hit:
		t.Fatalf("Did not expect job to have run yet.")
	case <-time.After(time.Second):
	}

	clk.AddTime(time.Minute)
	select {
	case <-hit:
		t.Fatalf("Still did not expect job to have run yet.")
	case <-time.After(time.Second):
	}

	clk.AddTime(time.Minute * 3)
	select {
	case <-hit:
	case <-time.After(time.Second * 5):
		t.Fatalf("Expected job to have run.")
	}

	clk.AddTime(time.Minute * 5)
	select {
	case <-hit:
	case <-time.After(time.Second * 5):
		t.Fatalf("Expected job to have run again.")
	}

}

func newLocalListener(t *testing.T) string {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
			t.Fatalf("failed to listen on a port: %v", err)
		}
	}
	defer func() {
		if err := l.Close(); err != nil {
			t.Fatal(err)
		}
	}()
	return l.Addr().String()
}
<|endoftext|>


<commit_before>package ws

import (
	"github.com/Vivena/babelweb2/parser"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"sync"
)

type node struct {
	m    *sync.Mutex
	desc *parser.BabelDesc
}

var nodes map[parser.Id]node
var nodesMutex sync.Mutex

func Init() {
	nodes = make(map[parser.Id]node)
}

func AddDesc(d *parser.BabelDesc) {
	nodesMutex.Lock()
	nodes[d.Id()] = node{desc: d, m: new(sync.Mutex)}
	nodesMutex.Unlock()
}

func RemoveDesc(id parser.Id) {
	nodesMutex.Lock()
	delete(nodes, id)
	nodesMutex.Unlock()
}

func GetDesc(id parser.Id) *parser.BabelDesc {
	return nodes[id].desc
}

func LockDesc(id parser.Id) {
	nodes[id].m.Lock()
}

func UnlockDesc(id parser.Id) {
	nodes[id].m.Unlock()
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

//Handler manage the websockets
func Handler(l *Listenergroup) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		log.Println("New connection to a websocket")
		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println("Could not create the socket.", err)
			return
		}

		for router := range nodes {
			nodes[router].m.Lock()
			nodes[router].desc.Iter(
				func(bu parser.BabelUpdate) error {
					sbu := bu.ToSUpdate()
					err := conn.WriteJSON(sbu)
					if err != nil {
						log.Println(err)
					}
					return err
				})
			nodes[router].m.Unlock()
		}
		updates := NewListener()
		l.Push(updates)

		// Ignore any data received on the websocket and detect
		// any errors.
		go func() {
			for {
				_, _, err := conn.NextReader()
				if err != nil {
					l.Flush(updates)
					conn.Close()
					break
				}
			}
		}()

		for {
			err := conn.WriteJSON(<-updates.Channel)
			if err != nil {
				log.Println(err)
			}
		}
	}
	return http.HandlerFunc(fn)
}
<commit_msg>Make ws.nodes a map to node pointers.<commit_after>package ws

import (
	"github.com/Vivena/babelweb2/parser"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"sync"
)

type node struct {
	m    *sync.Mutex
	desc *parser.BabelDesc
}

var nodes map[parser.Id]*node
var nodesMutex sync.Mutex

func Init() {
	nodes = make(map[parser.Id]*node)
}

func AddDesc(d *parser.BabelDesc) {
	nodesMutex.Lock()
	nodes[d.Id()] = &node{desc: d, m: new(sync.Mutex)}
	nodesMutex.Unlock()
}

func RemoveDesc(id parser.Id) {
	nodesMutex.Lock()
	delete(nodes, id)
	nodesMutex.Unlock()
}

func GetDesc(id parser.Id) *parser.BabelDesc {
	return nodes[id].desc
}

func LockDesc(id parser.Id) {
	nodes[id].m.Lock()
}

func UnlockDesc(id parser.Id) {
	nodes[id].m.Unlock()
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

//Handler manage the websockets
func Handler(l *Listenergroup) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		log.Println("New connection to a websocket")
		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println("Could not create the socket.", err)
			return
		}

		for router := range nodes {
			nodes[router].m.Lock()
			nodes[router].desc.Iter(
				func(bu parser.BabelUpdate) error {
					sbu := bu.ToSUpdate()
					err := conn.WriteJSON(sbu)
					if err != nil {
						log.Println(err)
					}
					return err
				})
			nodes[router].m.Unlock()
		}
		updates := NewListener()
		l.Push(updates)

		// Ignore any data received on the websocket and detect
		// any errors.
		go func() {
			for {
				_, _, err := conn.NextReader()
				if err != nil {
					l.Flush(updates)
					conn.Close()
					break
				}
			}
		}()

		for {
			err := conn.WriteJSON(<-updates.Channel)
			if err != nil {
				log.Println(err)
			}
		}
	}
	return http.HandlerFunc(fn)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func init() {
	s := new(string)
	wtCmd.PersistentFlags().StringVarP(s, "test", "t", "", "dummy for testing")

}

func TestStdin_import(t *testing.T) {
	wtCmd.ResetFlags()
	fh, err := os.Open("../test/sass/import.scss")
	if err != nil {
		t.Error(err)
	}

	oldStd := os.Stdin
	oldOut := os.Stdout

	os.Stdin = fh
	r, w, _ := os.Pipe()
	os.Stdout = w
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	includeDir := filepath.Join(pwd, "..", "test", "sass")
	wtCmd.SetArgs([]string{"-p", includeDir})
	main()

	outC := make(chan string)

	go func() {
		var buf bytes.Buffer
		io.Copy(&buf, r)
		outC <- buf.String()
	}()

	w.Close()
	os.Stdin = oldStd
	os.Stdout = oldOut

	out := <-outC
	out = strings.Replace(out, includeDir, "", 1)
	e := `div {
  background: #00FF00;
  font-size: 10pt; }
`

	if !bytes.Contains([]byte(e), []byte(e)) {
		t.Errorf("got:\n%s\nwanted:\n%s", out, e)
	}

}

func TestStdin_sprite(t *testing.T) {
	wtCmd.ResetFlags()
	fh, err := os.Open("../test/sass/sprite.scss")
	if err != nil {
		t.Error(err)
	}

	oldStd := os.Stdin
	oldOut := os.Stdout

	os.Stdin = fh
	r, w, _ := os.Pipe()
	os.Stdout = w
	wtCmd.SetArgs([]string{"--dir", "../test/img",
		"--gen", "../test/img/build"})
	main()

	outC := make(chan string)

	go func() {
		var buf bytes.Buffer
		io.Copy(&buf, r)
		outC <- buf.String()
	}()

	w.Close()
	os.Stdin = oldStd
	os.Stdout = oldOut

	out := <-outC

	e := `div {
  height: 139px;
  width: 96px;
  background: url("../test/img/build/91300a.png") 0px 0px; }
`
	if !bytes.Contains([]byte(e), []byte(e)) {
		t.Errorf("got:\n%s\nwanted:\n%s", out, e)
	}

}

func TestFile(t *testing.T) {
	// TODO: Tests for file importing here
}
<commit_msg>not a watch test<commit_after>package main

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func init() {
	s := new(string)
	wtCmd.PersistentFlags().StringVarP(s, "test", "t", "", "dummy for testing")

}

func TestWatch(t *testing.T) {
	t.Skip()
	tdir, err := ioutil.TempDir("", "TestWatch")
	if err != nil {
		t.Fatal(err)
	}
	wtCmd.ResetFlags()

	watch = true
	wtCmd.SetArgs([]string{
		"--dir", tdir,
		"watch",
	})
	main()

}

func TestStdin_import(t *testing.T) {
	wtCmd.ResetFlags()

	oldOut := os.Stdout

	r, w, _ := os.Pipe()
	os.Stdout = w
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	includeDir := filepath.Join(pwd, "..", "test", "sass")
	wtCmd.SetArgs([]string{
		"-p", includeDir,
		"compile", "../test/sass/import.scss"})
	main()

	outC := make(chan string)

	go func() {
		var buf bytes.Buffer
		io.Copy(&buf, r)
		outC <- buf.String()
	}()

	w.Close()
	os.Stdout = oldOut

	out := <-outC
	out = strings.Replace(out, includeDir, "", 1)
	e := `div {
  background: #00FF00;
  font-size: 10pt; }
`

	if !bytes.Contains([]byte(out), []byte(e)) {
		t.Errorf("got:\n%s\nwanted:\n%s", out, e)
	}

}

func TestStdin_sprite(t *testing.T) {
	wtCmd.ResetFlags()

	oldStd := os.Stdin
	oldOut := os.Stdout

	r, w, _ := os.Pipe()
	os.Stdout = w
	wtCmd.SetArgs([]string{
		"--dir", "../test/img",
		"--gen", "../test/img/build",
		"compile", "../test/sass/sprite.scss"})
	main()

	outC := make(chan string)

	go func() {
		var buf bytes.Buffer
		io.Copy(&buf, r)
		outC <- buf.String()
	}()

	w.Close()
	os.Stdin = oldStd
	os.Stdout = oldOut

	out := <-outC

	e := `div {
  height: 139px;
  width: 96px;
  background: url("../test/img/build/f0a220.png") 0px 0px; }
`

	if !bytes.Contains([]byte(out), []byte(e)) {
		t.Errorf("got:\n%s\nwanted:\n%s", out, e)
	}

}

func TestFile(t *testing.T) {
	// TODO: Tests for file importing here
}
<|endoftext|>


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

package graphicsutil

import (
	"image"
	"image/color"
	"image/draw"
	"runtime"
)

// CopyImage copies origImg to a new RGBA image.
//
// Basically CopyImage just calls draw.Draw.
// If origImg is a paletted image, an optimized copying method is used.
//
// CopyImage is used only internally but it is exposed for testing.
//
// TODO: CopyImage should return []byte (#521)
func CopyImage(origImg image.Image) *image.RGBA {
	size := origImg.Bounds().Size()
	w, h := size.X, size.Y
	newImg := image.NewRGBA(image.Rect(0, 0, w, h))
	switch origImg := origImg.(type) {
	case *image.Paletted:
		b := origImg.Bounds()
		x0 := b.Min.X
		y0 := b.Min.Y
		x1 := b.Max.X
		y1 := b.Max.Y
		palette := make([]uint8, len(origImg.Palette)*4)
		for i, c := range origImg.Palette {
			rgba := color.RGBAModel.Convert(c).(color.RGBA)
			palette[4*i] = rgba.R
			palette[4*i+1] = rgba.G
			palette[4*i+2] = rgba.B
			palette[4*i+3] = rgba.A
		}
		index0 := 0
		index1 := 0
		d0 := origImg.Stride - (x1 - x0)
		d1 := newImg.Stride - (x1-x0)*4
		pix0 := origImg.Pix
		pix1 := newImg.Pix
		for j := 0; j < y1-y0; j++ {
			for i := 0; i < x1-x0; i++ {
				p := int(pix0[index0])
				pix1[index1] = palette[4*p]
				pix1[index1+1] = palette[4*p+1]
				pix1[index1+2] = palette[4*p+2]
				pix1[index1+3] = palette[4*p+3]
				index0++
				index1 += 4
			}
			index0 += d0
			index1 += d1
		}
	default:
		draw.Draw(newImg, image.Rect(0, 0, w, h), origImg, origImg.Bounds().Min, draw.Src)
	}
	runtime.Gosched()
	return newImg
}
<commit_msg>graphicsutil: Remove Gosched that might be harmful on browsers<commit_after>// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphicsutil

import (
	"image"
	"image/color"
	"image/draw"
)

// CopyImage copies origImg to a new RGBA image.
//
// Basically CopyImage just calls draw.Draw.
// If origImg is a paletted image, an optimized copying method is used.
//
// CopyImage is used only internally but it is exposed for testing.
//
// TODO: CopyImage should return []byte (#521)
func CopyImage(origImg image.Image) *image.RGBA {
	size := origImg.Bounds().Size()
	w, h := size.X, size.Y
	newImg := image.NewRGBA(image.Rect(0, 0, w, h))
	switch origImg := origImg.(type) {
	case *image.Paletted:
		b := origImg.Bounds()
		x0 := b.Min.X
		y0 := b.Min.Y
		x1 := b.Max.X
		y1 := b.Max.Y
		palette := make([]uint8, len(origImg.Palette)*4)
		for i, c := range origImg.Palette {
			rgba := color.RGBAModel.Convert(c).(color.RGBA)
			palette[4*i] = rgba.R
			palette[4*i+1] = rgba.G
			palette[4*i+2] = rgba.B
			palette[4*i+3] = rgba.A
		}
		index0 := 0
		index1 := 0
		d0 := origImg.Stride - (x1 - x0)
		d1 := newImg.Stride - (x1-x0)*4
		pix0 := origImg.Pix
		pix1 := newImg.Pix
		for j := 0; j < y1-y0; j++ {
			for i := 0; i < x1-x0; i++ {
				p := int(pix0[index0])
				pix1[index1] = palette[4*p]
				pix1[index1+1] = palette[4*p+1]
				pix1[index1+2] = palette[4*p+2]
				pix1[index1+3] = palette[4*p+3]
				index0++
				index1 += 4
			}
			index0 += d0
			index1 += d1
		}
	default:
		draw.Draw(newImg, image.Rect(0, 0, w, h), origImg, origImg.Bounds().Min, draw.Src)
	}
	return newImg
}
<|endoftext|>


<commit_before>package imports

import (
	"context"
	"fmt"
	"sync"

	"golang.org/x/tools/internal/gopathwalk"
)

// To find packages to import, the resolver needs to know about all of the
// the packages that could be imported. This includes packages that are
// already in modules that are in (1) the current module, (2) replace targets,
// and (3) packages in the module cache. Packages in (1) and (2) may change over
// time, as the client may edit the current module and locally replaced modules.
// The module cache (which includes all of the packages in (3)) can only
// ever be added to.
//
// The resolver can thus save state about packages in the module cache
// and guarantee that this will not change over time. To obtain information
// about new modules added to the module cache, the module cache should be
// rescanned.
//
// It is OK to serve information about modules that have been deleted,
// as they do still exist.
// TODO(suzmue): can we share information with the caller about
// what module needs to be downloaded to import this package?

type directoryPackageStatus int

const (
	_ directoryPackageStatus = iota
	directoryScanned
	nameLoaded
	exportsLoaded
)

type directoryPackageInfo struct {
	// status indicates the extent to which this struct has been filled in.
	status directoryPackageStatus
	// err is non-nil when there was an error trying to reach status.
	err error

	// Set when status >= directoryScanned.

	// dir is the absolute directory of this package.
	dir      string
	rootType gopathwalk.RootType
	// nonCanonicalImportPath is the package's expected import path. It may
	// not actually be importable at that path.
	nonCanonicalImportPath string

	// Module-related information.
	moduleDir  string // The directory that is the module root of this dir.
	moduleName string // The module name that contains this dir.

	// Set when status >= nameLoaded.

	packageName string // the package name, as declared in the source.

	// Set when status >= exportsLoaded.

	exports []string
}

// reachedStatus returns true when info has a status at least target and any error associated with
// an attempt to reach target.
func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) {
	if info.err == nil {
		return info.status >= target, nil
	}
	if info.status == target {
		return true, info.err
	}
	return true, nil
}

// dirInfoCache is a concurrency safe map for storing information about
// directories that may contain packages.
//
// The information in this cache is built incrementally. Entries are initialized in scan.
// No new keys should be added in any other functions, as all directories containing
// packages are identified in scan.
//
// Other functions, including loadExports and findPackage, may update entries in this cache
// as they discover new things about the directory.
//
// The information in the cache is not expected to change for the cache's
// lifetime, so there is no protection against competing writes. Users should
// take care not to hold the cache across changes to the underlying files.
//
// TODO(suzmue): consider other concurrency strategies and data structures (RWLocks, sync.Map, etc)
type dirInfoCache struct {
	mu sync.Mutex
	// dirs stores information about packages in directories, keyed by absolute path.
	dirs      map[string]*directoryPackageInfo
	listeners map[*int]cacheListener
}

type cacheListener func(directoryPackageInfo)

// ScanAndListen calls listener on all the items in the cache, and on anything
// newly added. The returned stop function waits for all in-flight callbacks to
// finish and blocks new ones.
func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() {
	ctx, cancel := context.WithCancel(ctx)

	// Flushing out all the callbacks is tricky without knowing how many there
	// are going to be. Setting an arbitrary limit makes it much easier.
	const maxInFlight = 10
	sema := make(chan struct{}, maxInFlight)
	for i := 0; i < maxInFlight; i++ {
		sema <- struct{}{}
	}

	cookie := new(int) // A unique ID we can use for the listener.

	// We can't hold mu while calling the listener.
	d.mu.Lock()
	var keys []string
	for key := range d.dirs {
		keys = append(keys, key)
	}
	d.listeners[cookie] = func(info directoryPackageInfo) {
		select {
		case <-ctx.Done():
			return
		case <-sema:
		}
		listener(info)
		sema <- struct{}{}
	}
	d.mu.Unlock()

	// Process the pre-existing keys.
	for _, k := range keys {
		select {
		case <-ctx.Done():
			cancel()
			return func() {}
		default:
		}
		if v, ok := d.Load(k); ok {
			listener(v)
		}
	}

	return func() {
		cancel()
		d.mu.Lock()
		delete(d.listeners, cookie)
		d.mu.Unlock()
		for i := 0; i < maxInFlight; i++ {
			<-sema
		}
	}
}

// Store stores the package info for dir.
func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) {
	d.mu.Lock()
	_, old := d.dirs[dir]
	d.dirs[dir] = &info
	var listeners []cacheListener
	for _, l := range d.listeners {
		listeners = append(listeners, l)
	}
	d.mu.Unlock()

	if !old {
		for _, l := range listeners {
			l(info)
		}
	}
}

// Load returns a copy of the directoryPackageInfo for absolute directory dir.
func (d *dirInfoCache) Load(dir string) (directoryPackageInfo, bool) {
	d.mu.Lock()
	defer d.mu.Unlock()
	info, ok := d.dirs[dir]
	if !ok {
		return directoryPackageInfo{}, false
	}
	return *info, true
}

// Keys returns the keys currently present in d.
func (d *dirInfoCache) Keys() (keys []string) {
	d.mu.Lock()
	defer d.mu.Unlock()
	for key := range d.dirs {
		keys = append(keys, key)
	}
	return keys
}

func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) {
	if loaded, err := info.reachedStatus(nameLoaded); loaded {
		return info.packageName, err
	}
	if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
		return "", fmt.Errorf("cannot read package name, scan error: %v", err)
	}
	info.packageName, info.err = packageDirToName(info.dir)
	info.status = nameLoaded
	d.Store(info.dir, info)
	return info.packageName, info.err
}

func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) {
	if reached, _ := info.reachedStatus(exportsLoaded); reached {
		return info.packageName, info.exports, info.err
	}
	if reached, err := info.reachedStatus(nameLoaded); reached && err != nil {
		return "", nil, err
	}
	info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false)
	if info.err == context.Canceled || info.err == context.DeadlineExceeded {
		return info.packageName, info.exports, info.err
	}
	// The cache structure wants things to proceed linearly. We can skip a
	// step here, but only if we succeed.
	if info.status == nameLoaded || info.err == nil {
		info.status = exportsLoaded
	} else {
		info.status = nameLoaded
	}
	d.Store(info.dir, info)
	return info.packageName, info.exports, info.err
}
<commit_msg>internal/imports: stop leaking listeners<commit_after>package imports

import (
	"context"
	"fmt"
	"sync"

	"golang.org/x/tools/internal/gopathwalk"
)

// To find packages to import, the resolver needs to know about all of the
// the packages that could be imported. This includes packages that are
// already in modules that are in (1) the current module, (2) replace targets,
// and (3) packages in the module cache. Packages in (1) and (2) may change over
// time, as the client may edit the current module and locally replaced modules.
// The module cache (which includes all of the packages in (3)) can only
// ever be added to.
//
// The resolver can thus save state about packages in the module cache
// and guarantee that this will not change over time. To obtain information
// about new modules added to the module cache, the module cache should be
// rescanned.
//
// It is OK to serve information about modules that have been deleted,
// as they do still exist.
// TODO(suzmue): can we share information with the caller about
// what module needs to be downloaded to import this package?

type directoryPackageStatus int

const (
	_ directoryPackageStatus = iota
	directoryScanned
	nameLoaded
	exportsLoaded
)

type directoryPackageInfo struct {
	// status indicates the extent to which this struct has been filled in.
	status directoryPackageStatus
	// err is non-nil when there was an error trying to reach status.
	err error

	// Set when status >= directoryScanned.

	// dir is the absolute directory of this package.
	dir      string
	rootType gopathwalk.RootType
	// nonCanonicalImportPath is the package's expected import path. It may
	// not actually be importable at that path.
	nonCanonicalImportPath string

	// Module-related information.
	moduleDir  string // The directory that is the module root of this dir.
	moduleName string // The module name that contains this dir.

	// Set when status >= nameLoaded.

	packageName string // the package name, as declared in the source.

	// Set when status >= exportsLoaded.

	exports []string
}

// reachedStatus returns true when info has a status at least target and any error associated with
// an attempt to reach target.
func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) {
	if info.err == nil {
		return info.status >= target, nil
	}
	if info.status == target {
		return true, info.err
	}
	return true, nil
}

// dirInfoCache is a concurrency safe map for storing information about
// directories that may contain packages.
//
// The information in this cache is built incrementally. Entries are initialized in scan.
// No new keys should be added in any other functions, as all directories containing
// packages are identified in scan.
//
// Other functions, including loadExports and findPackage, may update entries in this cache
// as they discover new things about the directory.
//
// The information in the cache is not expected to change for the cache's
// lifetime, so there is no protection against competing writes. Users should
// take care not to hold the cache across changes to the underlying files.
//
// TODO(suzmue): consider other concurrency strategies and data structures (RWLocks, sync.Map, etc)
type dirInfoCache struct {
	mu sync.Mutex
	// dirs stores information about packages in directories, keyed by absolute path.
	dirs      map[string]*directoryPackageInfo
	listeners map[*int]cacheListener
}

type cacheListener func(directoryPackageInfo)

// ScanAndListen calls listener on all the items in the cache, and on anything
// newly added. The returned stop function waits for all in-flight callbacks to
// finish and blocks new ones.
func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() {
	ctx, cancel := context.WithCancel(ctx)

	// Flushing out all the callbacks is tricky without knowing how many there
	// are going to be. Setting an arbitrary limit makes it much easier.
	const maxInFlight = 10
	sema := make(chan struct{}, maxInFlight)
	for i := 0; i < maxInFlight; i++ {
		sema <- struct{}{}
	}

	cookie := new(int) // A unique ID we can use for the listener.

	// We can't hold mu while calling the listener.
	d.mu.Lock()
	var keys []string
	for key := range d.dirs {
		keys = append(keys, key)
	}
	d.listeners[cookie] = func(info directoryPackageInfo) {
		select {
		case <-ctx.Done():
			return
		case <-sema:
		}
		listener(info)
		sema <- struct{}{}
	}
	d.mu.Unlock()

	stop := func() {
		cancel()
		d.mu.Lock()
		delete(d.listeners, cookie)
		d.mu.Unlock()
		for i := 0; i < maxInFlight; i++ {
			<-sema
		}
	}

	// Process the pre-existing keys.
	for _, k := range keys {
		select {
		case <-ctx.Done():
			return stop
		default:
		}
		if v, ok := d.Load(k); ok {
			listener(v)
		}
	}

	return stop
}

// Store stores the package info for dir.
func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) {
	d.mu.Lock()
	_, old := d.dirs[dir]
	d.dirs[dir] = &info
	var listeners []cacheListener
	for _, l := range d.listeners {
		listeners = append(listeners, l)
	}
	d.mu.Unlock()

	if !old {
		for _, l := range listeners {
			l(info)
		}
	}
}

// Load returns a copy of the directoryPackageInfo for absolute directory dir.
func (d *dirInfoCache) Load(dir string) (directoryPackageInfo, bool) {
	d.mu.Lock()
	defer d.mu.Unlock()
	info, ok := d.dirs[dir]
	if !ok {
		return directoryPackageInfo{}, false
	}
	return *info, true
}

// Keys returns the keys currently present in d.
func (d *dirInfoCache) Keys() (keys []string) {
	d.mu.Lock()
	defer d.mu.Unlock()
	for key := range d.dirs {
		keys = append(keys, key)
	}
	return keys
}

func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) {
	if loaded, err := info.reachedStatus(nameLoaded); loaded {
		return info.packageName, err
	}
	if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
		return "", fmt.Errorf("cannot read package name, scan error: %v", err)
	}
	info.packageName, info.err = packageDirToName(info.dir)
	info.status = nameLoaded
	d.Store(info.dir, info)
	return info.packageName, info.err
}

func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) {
	if reached, _ := info.reachedStatus(exportsLoaded); reached {
		return info.packageName, info.exports, info.err
	}
	if reached, err := info.reachedStatus(nameLoaded); reached && err != nil {
		return "", nil, err
	}
	info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false)
	if info.err == context.Canceled || info.err == context.DeadlineExceeded {
		return info.packageName, info.exports, info.err
	}
	// The cache structure wants things to proceed linearly. We can skip a
	// step here, but only if we succeed.
	if info.status == nameLoaded || info.err == nil {
		info.status = exportsLoaded
	} else {
		info.status = nameLoaded
	}
	d.Store(info.dir, info)
	return info.packageName, info.exports, info.err
}
<|endoftext|>


<commit_before>// Copyright 2014 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.

// +build js

package opengl

import (
	"errors"
	"fmt"

	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/webgl"

	"github.com/hajimehoshi/ebiten/internal/web"
)

// Note that `type Texture *js.Object` doesn't work.
// There is no way to get the internal object in that case.

type Texture struct {
	*js.Object
}

type Framebuffer struct {
	*js.Object
}

type Shader struct {
	*js.Object
}

type Program struct {
	*js.Object
}

type Buffer struct {
	*js.Object
}

func (t Texture) equals(other Texture) bool {
	return t.Object == other.Object
}

func (f Framebuffer) equals(other Framebuffer) bool {
	return f.Object == other.Object
}

type uniformLocation struct {
	*js.Object
}

type attribLocation int

type programID int

var (
	invalidTexture     = Texture{}
	invalidFramebuffer = Framebuffer{}
)

func (p Program) id() programID {
	return programID(p.Get("__ebiten_programId").Int())
}

func init() {
	// Accessing the prototype is rquired on Safari.
	c := js.Global.Get("WebGLRenderingContext").Get("prototype")
	VertexShader = ShaderType(c.Get("VERTEX_SHADER").Int())
	FragmentShader = ShaderType(c.Get("FRAGMENT_SHADER").Int())
	ArrayBuffer = BufferType(c.Get("ARRAY_BUFFER").Int())
	ElementArrayBuffer = BufferType(c.Get("ELEMENT_ARRAY_BUFFER").Int())
	DynamicDraw = BufferUsage(c.Get("DYNAMIC_DRAW").Int())
	StaticDraw = BufferUsage(c.Get("STATIC_DRAW").Int())
	Triangles = Mode(c.Get("TRIANGLES").Int())
	Lines = Mode(c.Get("LINES").Int())
	Short = DataType(c.Get("SHORT").Int())
	Float = DataType(c.Get("FLOAT").Int())

	zero = operation(c.Get("ZERO").Int())
	one = operation(c.Get("ONE").Int())
	srcAlpha = operation(c.Get("SRC_ALPHA").Int())
	dstAlpha = operation(c.Get("DST_ALPHA").Int())
	oneMinusSrcAlpha = operation(c.Get("ONE_MINUS_SRC_ALPHA").Int())
	oneMinusDstAlpha = operation(c.Get("ONE_MINUS_DST_ALPHA").Int())
}

type context struct {
	gl            *webgl.Context
	loseContext   *js.Object
	lastProgramID programID
}

func Init() error {
	if web.IsNodeJS() {
		return fmt.Errorf("opengl: Node.js is not supported")
	}

	// TODO: Define id?
	canvas := js.Global.Get("document").Call("querySelector", "canvas")
	gl, err := webgl.NewContext(canvas, &webgl.ContextAttributes{
		Alpha:              true,
		PremultipliedAlpha: true,
	})
	if err != nil {
		return err
	}
	c := &Context{}
	c.gl = gl

	// Getting an extension might fail after the context is lost, so
	// it is required to get the extension here.
	c.loseContext = gl.GetExtension("WEBGL_lose_context")
	if c.loseContext != nil {
		// This testing function name is temporary.
		js.Global.Set("_ebiten_loseContextForTesting", func() {
			c.loseContext.Call("loseContext")
		})
	}
	theContext = c
	return nil
}

func (c *Context) Reset() error {
	c.locationCache = newLocationCache()
	c.lastTexture = invalidTexture
	c.lastFramebuffer = invalidFramebuffer
	c.lastViewportWidth = 0
	c.lastViewportHeight = 0
	c.lastCompositeMode = CompositeModeUnknown
	gl := c.gl
	gl.Enable(gl.BLEND)
	c.BlendFunc(CompositeModeSourceOver)
	f := gl.GetParameter(gl.FRAMEBUFFER_BINDING)
	c.screenFramebuffer = Framebuffer{f}
	return nil
}

func (c *Context) BlendFunc(mode CompositeMode) {
	if c.lastCompositeMode == mode {
		return
	}
	c.lastCompositeMode = mode
	s, d := operations(mode)
	gl := c.gl
	gl.BlendFunc(int(s), int(d))
}

func (c *Context) NewTexture(width, height int, pixels []uint8) (Texture, error) {
	gl := c.gl
	t := gl.CreateTexture()
	if t == nil {
		return Texture{nil}, errors.New("opengl: glGenTexture failed")
	}
	gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
	c.BindTexture(Texture{t})

	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)

	// TODO: Can we use glTexSubImage2D with linear filtering?

	// void texImage2D(GLenum target, GLint level, GLenum internalformat,
	//     GLsizei width, GLsizei height, GLint border, GLenum format,
	//     GLenum type, ArrayBufferView? pixels);
	var p interface{}
	if pixels != nil {
		p = pixels
	}
	gl.Call("texImage2D", gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, p)

	return Texture{t}, nil
}

func (c *Context) bindFramebufferImpl(f Framebuffer) {
	gl := c.gl
	gl.BindFramebuffer(gl.FRAMEBUFFER, f.Object)
}

func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) {
	gl := c.gl

	c.bindFramebuffer(f)

	pixels := js.Global.Get("Uint8Array").New(4 * width * height)
	gl.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
	if e := gl.GetError(); e != gl.NO_ERROR {
		return nil, errors.New(fmt.Sprintf("opengl: error: %d", e))
	}
	return pixels.Interface().([]uint8), nil
}

func (c *Context) bindTextureImpl(t Texture) {
	gl := c.gl
	gl.BindTexture(gl.TEXTURE_2D, t.Object)
}

func (c *Context) DeleteTexture(t Texture) {
	gl := c.gl
	if !gl.IsTexture(t.Object) {
		return
	}
	if c.lastTexture == t {
		c.lastTexture = invalidTexture
	}
	gl.DeleteTexture(t.Object)
}

func (c *Context) IsTexture(t Texture) bool {
	gl := c.gl
	b := gl.IsTexture(t.Object)
	return b
}

func (c *Context) TexSubImage2D(p []uint8, width, height int) {
	gl := c.gl
	// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
	//                    GLsizei width, GLsizei height,
	//                    GLenum format, GLenum type, ArrayBufferView? pixels);
	gl.Call("texSubImage2D", gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, p)
}

func (c *Context) NewFramebuffer(t Texture) (Framebuffer, error) {
	gl := c.gl
	f := gl.CreateFramebuffer()
	c.bindFramebuffer(Framebuffer{f})

	gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.Object, 0)
	s := gl.CheckFramebufferStatus(gl.FRAMEBUFFER)
	if s != gl.FRAMEBUFFER_COMPLETE {
		return Framebuffer{nil}, errors.New(fmt.Sprintf("opengl: creating framebuffer failed: %d", s))
	}

	return Framebuffer{f}, nil
}

func (c *Context) setViewportImpl(width, height int) {
	gl := c.gl
	gl.Viewport(0, 0, width, height)
}

func (c *Context) FillFramebuffer(r, g, b, a float32) error {
	// TODO: Use f?
	gl := c.gl
	gl.ClearColor(adjustForClearColor(r),
		adjustForClearColor(g),
		adjustForClearColor(b),
		adjustForClearColor(a))
	gl.Clear(gl.COLOR_BUFFER_BIT)
	return nil
}

func (c *Context) DeleteFramebuffer(f Framebuffer) {
	gl := c.gl
	if !gl.IsFramebuffer(f.Object) {
		return
	}
	// If a framebuffer to be deleted is bound, a newly bound framebuffer
	// will be a default framebuffer.
	// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteFramebuffers.xml
	if c.lastFramebuffer == f {
		c.lastFramebuffer = invalidFramebuffer
		c.lastViewportWidth = 0
		c.lastViewportHeight = 0
	}
	gl.DeleteFramebuffer(f.Object)
}

func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error) {
	gl := c.gl
	s := gl.CreateShader(int(shaderType))
	if s == nil {
		return Shader{nil}, fmt.Errorf("opengl: glCreateShader failed: shader type: %d", shaderType)
	}

	gl.ShaderSource(s, source)
	gl.CompileShader(s)

	if !gl.GetShaderParameterb(s, gl.COMPILE_STATUS) {
		log := gl.GetShaderInfoLog(s)
		return Shader{nil}, fmt.Errorf("opengl: shader compile failed: %s", log)
	}
	return Shader{s}, nil
}

func (c *Context) DeleteShader(s Shader) {
	gl := c.gl
	gl.DeleteShader(s.Object)
}

func (c *Context) NewProgram(shaders []Shader) (Program, error) {
	gl := c.gl
	p := gl.CreateProgram()
	if p == nil {
		return Program{nil}, errors.New("opengl: glCreateProgram failed")
	}
	p.Set("__ebiten_programId", c.lastProgramID)
	c.lastProgramID++

	for _, shader := range shaders {
		gl.AttachShader(p, shader.Object)
	}
	gl.LinkProgram(p)
	if !gl.GetProgramParameterb(p, gl.LINK_STATUS) {
		return Program{nil}, errors.New("opengl: program error")
	}
	return Program{p}, nil
}

func (c *Context) UseProgram(p Program) {
	gl := c.gl
	gl.UseProgram(p.Object)
}

func (c *Context) DeleteProgram(p Program) {
	gl := c.gl
	if !gl.IsProgram(p.Object) {
		return
	}
	gl.DeleteProgram(p.Object)
}

func (c *Context) getUniformLocationImpl(p Program, location string) uniformLocation {
	gl := c.gl
	return uniformLocation{gl.GetUniformLocation(p.Object, location)}
}

func (c *Context) UniformInt(p Program, location string, v int) {
	gl := c.gl
	l := c.locationCache.GetUniformLocation(c, p, location)
	gl.Uniform1i(l.Object, v)
}

func (c *Context) UniformFloats(p Program, location string, v []float32) {
	gl := c.gl
	l := c.locationCache.GetUniformLocation(c, p, location)
	switch len(v) {
	case 2:
		gl.Call("uniform2fv", l.Object, v)
	case 4:
		gl.Call("uniform4fv", l.Object, v)
	case 16:
		gl.UniformMatrix4fv(l.Object, false, v)
	default:
		panic("not reached")
	}
}

func (c *Context) getAttribLocationImpl(p Program, location string) attribLocation {
	gl := c.gl
	return attribLocation(gl.GetAttribLocation(p.Object, location))
}

func (c *Context) VertexAttribPointer(p Program, location string, size int, dataType DataType, stride int, offset int) {
	gl := c.gl
	l := c.locationCache.GetAttribLocation(c, p, location)
	gl.VertexAttribPointer(int(l), size, int(dataType), false, stride, offset)
}

func (c *Context) EnableVertexAttribArray(p Program, location string) {
	gl := c.gl
	l := c.locationCache.GetAttribLocation(c, p, location)
	gl.EnableVertexAttribArray(int(l))
}

func (c *Context) DisableVertexAttribArray(p Program, location string) {
	gl := c.gl
	l := c.locationCache.GetAttribLocation(c, p, location)
	gl.DisableVertexAttribArray(int(l))
}

func (c *Context) NewArrayBuffer(size int) Buffer {
	gl := c.gl
	b := gl.CreateBuffer()
	gl.BindBuffer(int(ArrayBuffer), b)
	gl.BufferData(int(ArrayBuffer), size, int(DynamicDraw))
	return Buffer{b}
}

func (c *Context) NewElementArrayBuffer(indices []uint16) Buffer {
	gl := c.gl
	b := gl.CreateBuffer()
	gl.BindBuffer(int(ElementArrayBuffer), b)
	gl.BufferData(int(ElementArrayBuffer), indices, int(StaticDraw))
	return Buffer{b}
}

func (c *Context) BindElementArrayBuffer(b Buffer) {
	gl := c.gl
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.Object)
}

func (c *Context) BufferSubData(bufferType BufferType, data []float32) {
	gl := c.gl
	gl.BufferSubData(int(bufferType), 0, data)
}

func (c *Context) DeleteBuffer(b Buffer) {
	gl := c.gl
	gl.DeleteBuffer(b.Object)
}

func (c *Context) DrawElements(mode Mode, len int, offsetInBytes int) {
	gl := c.gl
	gl.DrawElements(int(mode), len, gl.UNSIGNED_SHORT, offsetInBytes)
}

func (c *Context) Flush() {
	gl := c.gl
	gl.Flush()
}

func (c *Context) IsContextLost() bool {
	gl := c.gl
	return gl.IsContextLost()
}

func (c *Context) RestoreContext() {
	if c.loseContext != nil {
		c.loseContext.Call("restoreContext")
	}
}
<commit_msg>opengl: Remove struct usages to avoid copying<commit_after>// Copyright 2014 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.

// +build js

package opengl

import (
	"errors"
	"fmt"

	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/webgl"

	"github.com/hajimehoshi/ebiten/internal/web"
)

// Note that `type Texture *js.Object` doesn't work.
// There is no way to get the internal object in that case.

type Texture struct {
	*js.Object
}

type Framebuffer struct {
	*js.Object
}

type Shader struct {
	*js.Object
}

type Program struct {
	*js.Object
}

type Buffer struct {
	*js.Object
}

func (t Texture) equals(other Texture) bool {
	return t.Object == other.Object
}

func (f Framebuffer) equals(other Framebuffer) bool {
	return f.Object == other.Object
}

type uniformLocation interface{}

type attribLocation int

type programID int

var (
	invalidTexture     = Texture{}
	invalidFramebuffer = Framebuffer{}
)

func (p Program) id() programID {
	return programID(p.Get("__ebiten_programId").Int())
}

func init() {
	// Accessing the prototype is rquired on Safari.
	c := js.Global.Get("WebGLRenderingContext").Get("prototype")
	VertexShader = ShaderType(c.Get("VERTEX_SHADER").Int())
	FragmentShader = ShaderType(c.Get("FRAGMENT_SHADER").Int())
	ArrayBuffer = BufferType(c.Get("ARRAY_BUFFER").Int())
	ElementArrayBuffer = BufferType(c.Get("ELEMENT_ARRAY_BUFFER").Int())
	DynamicDraw = BufferUsage(c.Get("DYNAMIC_DRAW").Int())
	StaticDraw = BufferUsage(c.Get("STATIC_DRAW").Int())
	Triangles = Mode(c.Get("TRIANGLES").Int())
	Lines = Mode(c.Get("LINES").Int())
	Short = DataType(c.Get("SHORT").Int())
	Float = DataType(c.Get("FLOAT").Int())

	zero = operation(c.Get("ZERO").Int())
	one = operation(c.Get("ONE").Int())
	srcAlpha = operation(c.Get("SRC_ALPHA").Int())
	dstAlpha = operation(c.Get("DST_ALPHA").Int())
	oneMinusSrcAlpha = operation(c.Get("ONE_MINUS_SRC_ALPHA").Int())
	oneMinusDstAlpha = operation(c.Get("ONE_MINUS_DST_ALPHA").Int())
}

type context struct {
	gl            *webgl.Context
	loseContext   *js.Object
	lastProgramID programID
}

func Init() error {
	if web.IsNodeJS() {
		return fmt.Errorf("opengl: Node.js is not supported")
	}

	// TODO: Define id?
	canvas := js.Global.Get("document").Call("querySelector", "canvas")
	gl, err := webgl.NewContext(canvas, &webgl.ContextAttributes{
		Alpha:              true,
		PremultipliedAlpha: true,
	})
	if err != nil {
		return err
	}
	c := &Context{}
	c.gl = gl

	// Getting an extension might fail after the context is lost, so
	// it is required to get the extension here.
	c.loseContext = gl.GetExtension("WEBGL_lose_context")
	if c.loseContext != nil {
		// This testing function name is temporary.
		js.Global.Set("_ebiten_loseContextForTesting", func() {
			c.loseContext.Call("loseContext")
		})
	}
	theContext = c
	return nil
}

func (c *Context) Reset() error {
	c.locationCache = newLocationCache()
	c.lastTexture = invalidTexture
	c.lastFramebuffer = invalidFramebuffer
	c.lastViewportWidth = 0
	c.lastViewportHeight = 0
	c.lastCompositeMode = CompositeModeUnknown
	gl := c.gl
	gl.Enable(gl.BLEND)
	c.BlendFunc(CompositeModeSourceOver)
	f := gl.GetParameter(gl.FRAMEBUFFER_BINDING)
	c.screenFramebuffer = Framebuffer{f}
	return nil
}

func (c *Context) BlendFunc(mode CompositeMode) {
	if c.lastCompositeMode == mode {
		return
	}
	c.lastCompositeMode = mode
	s, d := operations(mode)
	gl := c.gl
	gl.BlendFunc(int(s), int(d))
}

func (c *Context) NewTexture(width, height int, pixels []uint8) (Texture, error) {
	gl := c.gl
	t := gl.CreateTexture()
	if t == nil {
		return Texture{nil}, errors.New("opengl: glGenTexture failed")
	}
	gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
	c.BindTexture(Texture{t})

	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)

	// TODO: Can we use glTexSubImage2D with linear filtering?

	// void texImage2D(GLenum target, GLint level, GLenum internalformat,
	//     GLsizei width, GLsizei height, GLint border, GLenum format,
	//     GLenum type, ArrayBufferView? pixels);
	var p interface{}
	if pixels != nil {
		p = pixels
	}
	gl.Call("texImage2D", gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, p)

	return Texture{t}, nil
}

func (c *Context) bindFramebufferImpl(f Framebuffer) {
	gl := c.gl
	gl.BindFramebuffer(gl.FRAMEBUFFER, f.Object)
}

func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) {
	gl := c.gl

	c.bindFramebuffer(f)

	pixels := js.Global.Get("Uint8Array").New(4 * width * height)
	gl.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
	if e := gl.GetError(); e != gl.NO_ERROR {
		return nil, errors.New(fmt.Sprintf("opengl: error: %d", e))
	}
	return pixels.Interface().([]uint8), nil
}

func (c *Context) bindTextureImpl(t Texture) {
	gl := c.gl
	gl.BindTexture(gl.TEXTURE_2D, t.Object)
}

func (c *Context) DeleteTexture(t Texture) {
	gl := c.gl
	if !gl.IsTexture(t.Object) {
		return
	}
	if c.lastTexture == t {
		c.lastTexture = invalidTexture
	}
	gl.DeleteTexture(t.Object)
}

func (c *Context) IsTexture(t Texture) bool {
	gl := c.gl
	b := gl.IsTexture(t.Object)
	return b
}

func (c *Context) TexSubImage2D(p []uint8, width, height int) {
	gl := c.gl
	// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
	//                    GLsizei width, GLsizei height,
	//                    GLenum format, GLenum type, ArrayBufferView? pixels);
	gl.Call("texSubImage2D", gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, p)
}

func (c *Context) NewFramebuffer(t Texture) (Framebuffer, error) {
	gl := c.gl
	f := gl.CreateFramebuffer()
	c.bindFramebuffer(Framebuffer{f})

	gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.Object, 0)
	s := gl.CheckFramebufferStatus(gl.FRAMEBUFFER)
	if s != gl.FRAMEBUFFER_COMPLETE {
		return Framebuffer{nil}, errors.New(fmt.Sprintf("opengl: creating framebuffer failed: %d", s))
	}

	return Framebuffer{f}, nil
}

func (c *Context) setViewportImpl(width, height int) {
	gl := c.gl
	gl.Viewport(0, 0, width, height)
}

func (c *Context) FillFramebuffer(r, g, b, a float32) error {
	// TODO: Use f?
	gl := c.gl
	gl.ClearColor(adjustForClearColor(r),
		adjustForClearColor(g),
		adjustForClearColor(b),
		adjustForClearColor(a))
	gl.Clear(gl.COLOR_BUFFER_BIT)
	return nil
}

func (c *Context) DeleteFramebuffer(f Framebuffer) {
	gl := c.gl
	if !gl.IsFramebuffer(f.Object) {
		return
	}
	// If a framebuffer to be deleted is bound, a newly bound framebuffer
	// will be a default framebuffer.
	// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteFramebuffers.xml
	if c.lastFramebuffer == f {
		c.lastFramebuffer = invalidFramebuffer
		c.lastViewportWidth = 0
		c.lastViewportHeight = 0
	}
	gl.DeleteFramebuffer(f.Object)
}

func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error) {
	gl := c.gl
	s := gl.CreateShader(int(shaderType))
	if s == nil {
		return Shader{nil}, fmt.Errorf("opengl: glCreateShader failed: shader type: %d", shaderType)
	}

	gl.ShaderSource(s, source)
	gl.CompileShader(s)

	if !gl.GetShaderParameterb(s, gl.COMPILE_STATUS) {
		log := gl.GetShaderInfoLog(s)
		return Shader{nil}, fmt.Errorf("opengl: shader compile failed: %s", log)
	}
	return Shader{s}, nil
}

func (c *Context) DeleteShader(s Shader) {
	gl := c.gl
	gl.DeleteShader(s.Object)
}

func (c *Context) NewProgram(shaders []Shader) (Program, error) {
	gl := c.gl
	p := gl.CreateProgram()
	if p == nil {
		return Program{nil}, errors.New("opengl: glCreateProgram failed")
	}
	p.Set("__ebiten_programId", c.lastProgramID)
	c.lastProgramID++

	for _, shader := range shaders {
		gl.AttachShader(p, shader.Object)
	}
	gl.LinkProgram(p)
	if !gl.GetProgramParameterb(p, gl.LINK_STATUS) {
		return Program{nil}, errors.New("opengl: program error")
	}
	return Program{p}, nil
}

func (c *Context) UseProgram(p Program) {
	gl := c.gl
	gl.UseProgram(p.Object)
}

func (c *Context) DeleteProgram(p Program) {
	gl := c.gl
	if !gl.IsProgram(p.Object) {
		return
	}
	gl.DeleteProgram(p.Object)
}

func (c *Context) getUniformLocationImpl(p Program, location string) uniformLocation {
	gl := c.gl
	return uniformLocation(gl.GetUniformLocation(p.Object, location))
}

func (c *Context) UniformInt(p Program, location string, v int) {
	gl := c.gl
	l := c.locationCache.GetUniformLocation(c, p, location)
	gl.Uniform1i(l.(*js.Object), v)
}

func (c *Context) UniformFloats(p Program, location string, v []float32) {
	gl := c.gl
	l := c.locationCache.GetUniformLocation(c, p, location)
	switch len(v) {
	case 2:
		gl.Call("uniform2fv", l.(*js.Object), v)
	case 4:
		gl.Call("uniform4fv", l.(*js.Object), v)
	case 16:
		gl.UniformMatrix4fv(l.(*js.Object), false, v)
	default:
		panic("not reached")
	}
}

func (c *Context) getAttribLocationImpl(p Program, location string) attribLocation {
	gl := c.gl
	return attribLocation(gl.GetAttribLocation(p.Object, location))
}

func (c *Context) VertexAttribPointer(p Program, location string, size int, dataType DataType, stride int, offset int) {
	gl := c.gl
	l := c.locationCache.GetAttribLocation(c, p, location)
	gl.VertexAttribPointer(int(l), size, int(dataType), false, stride, offset)
}

func (c *Context) EnableVertexAttribArray(p Program, location string) {
	gl := c.gl
	l := c.locationCache.GetAttribLocation(c, p, location)
	gl.EnableVertexAttribArray(int(l))
}

func (c *Context) DisableVertexAttribArray(p Program, location string) {
	gl := c.gl
	l := c.locationCache.GetAttribLocation(c, p, location)
	gl.DisableVertexAttribArray(int(l))
}

func (c *Context) NewArrayBuffer(size int) Buffer {
	gl := c.gl
	b := gl.CreateBuffer()
	gl.BindBuffer(int(ArrayBuffer), b)
	gl.BufferData(int(ArrayBuffer), size, int(DynamicDraw))
	return Buffer{b}
}

func (c *Context) NewElementArrayBuffer(indices []uint16) Buffer {
	gl := c.gl
	b := gl.CreateBuffer()
	gl.BindBuffer(int(ElementArrayBuffer), b)
	gl.BufferData(int(ElementArrayBuffer), indices, int(StaticDraw))
	return Buffer{b}
}

func (c *Context) BindElementArrayBuffer(b Buffer) {
	gl := c.gl
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.Object)
}

func (c *Context) BufferSubData(bufferType BufferType, data []float32) {
	gl := c.gl
	gl.BufferSubData(int(bufferType), 0, data)
}

func (c *Context) DeleteBuffer(b Buffer) {
	gl := c.gl
	gl.DeleteBuffer(b.Object)
}

func (c *Context) DrawElements(mode Mode, len int, offsetInBytes int) {
	gl := c.gl
	gl.DrawElements(int(mode), len, gl.UNSIGNED_SHORT, offsetInBytes)
}

func (c *Context) Flush() {
	gl := c.gl
	gl.Flush()
}

func (c *Context) IsContextLost() bool {
	gl := c.gl
	return gl.IsContextLost()
}

func (c *Context) RestoreContext() {
	if c.loseContext != nil {
		c.loseContext.Call("restoreContext")
	}
}
<|endoftext|>


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

package postgres

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"sync/atomic"
	"time"
	"unicode"

	"golang.org/x/discovery/internal/config"
	"golang.org/x/discovery/internal/derrors"
	"golang.org/x/discovery/internal/log"
)

// DB wraps a sql.DB to provide an API for interacting with discovery data
// stored in Postgres.
type DB struct {
	db *sql.DB
}

func (db *DB) exec(ctx context.Context, query string, args ...interface{}) (res sql.Result, err error) {
	defer logQuery(query, args)(&err)

	return db.db.ExecContext(ctx, query, args...)
}

func execTx(ctx context.Context, tx *sql.Tx, query string, args ...interface{}) (res sql.Result, err error) {
	defer logQuery(query, args)(&err)

	return tx.ExecContext(ctx, query, args...)
}

func (db *DB) query(ctx context.Context, query string, args ...interface{}) (_ *sql.Rows, err error) {
	defer logQuery(query, args)(&err)
	return db.db.QueryContext(ctx, query, args...)
}

func (db *DB) queryRow(ctx context.Context, query string, args ...interface{}) *sql.Row {
	defer logQuery(query, args)(nil)
	return db.db.QueryRowContext(ctx, query, args...)
}

var (
	queryCounter         int64 // atomic: per-process counter for unique query IDs
	queryLoggingDisabled bool  // For use in tests only: not concurrency-safe.
)

type queryEndLogEntry struct {
	ID              string
	Query           string
	Args            string
	DurationSeconds float64
	Error           string `json:",omitempty"`
}

func logQuery(query string, args []interface{}) func(*error) {
	if queryLoggingDisabled {
		return func(*error) {}
	}
	const maxlen = 300 // maximum length of displayed query

	// To make the query more compact and readable, replace newlines with spaces
	// and collapse adjacent whitespace.
	var r []rune
	for _, c := range query {
		if c == '\n' {
			c = ' '
		}
		if len(r) == 0 || !unicode.IsSpace(r[len(r)-1]) || !unicode.IsSpace(c) {
			r = append(r, c)
		}
	}
	query = string(r)
	if len(query) > maxlen {
		query = query[:maxlen] + "..."
	}

	instanceID := config.InstanceID()
	if instanceID == "" {
		instanceID = "local"
	} else {
		// Instance IDs are long strings. The low-order part seems quite random, so
		// shortening the ID will still likely result in something unique.
		instanceID = instanceID[len(instanceID)-4:]
	}
	n := atomic.AddInt64(&queryCounter, 1)
	uid := fmt.Sprintf("%s-%d", instanceID, n)

	// Construct a short string of the args.
	const (
		maxArgs   = 20
		maxArgLen = 50
	)
	var argStrings []string
	for i := 0; i < len(args) && i < maxArgs; i++ {
		s := fmt.Sprint(args[i])
		if len(s) > maxArgLen {
			s = s[:maxArgLen] + "..."
		}
		argStrings = append(argStrings, s)
	}
	if len(args) > maxArgs {
		argStrings = append(argStrings, "...")
	}
	argString := strings.Join(argStrings, ", ")

	log.Debugf("%s %s args=%s", uid, query, argString)
	start := time.Now()
	return func(errp *error) {
		dur := time.Since(start)
		if errp == nil { // happens with queryRow
			log.Debugf("%s done", uid)
		} else {
			derrors.Wrap(errp, "DB running query %s", uid)
			entry := queryEndLogEntry{
				ID:              uid,
				Query:           query,
				Args:            argString,
				DurationSeconds: dur.Seconds(),
			}
			if *errp == nil {
				log.Debug(entry)
			} else {
				entry.Error = (*errp).Error()
				log.Error(entry)
			}
		}
	}
}

// Open creates a new DB for the given Postgres connection string.
func Open(driverName, dbinfo string) (*DB, error) {
	db, err := sql.Open(driverName, dbinfo)
	if err != nil {
		return nil, err
	}

	if err = db.Ping(); err != nil {
		return nil, err
	}
	return &DB{db}, nil
}

// Transact executes the given function in the context of a SQL transaction,
// rolling back the transaction if the function panics or returns an error.
func (db *DB) Transact(txFunc func(*sql.Tx) error) (err error) {
	tx, err := db.db.Begin()
	if err != nil {
		return fmt.Errorf("db.Begin(): %v", err)
	}

	defer func() {
		if p := recover(); p != nil {
			tx.Rollback()
			panic(p)
		} else if err != nil {
			tx.Rollback()
		} else {
			if err = tx.Commit(); err != nil {
				err = fmt.Errorf("tx.Commit(): %v", err)
			}
		}
	}()

	if err := txFunc(tx); err != nil {
		return fmt.Errorf("txFunc(tx): %v", err)
	}
	return nil
}

// prepareAndExec prepares a query statement and executes it insde the provided
// transaction.
func prepareAndExec(tx *sql.Tx, query string, stmtFunc func(*sql.Stmt) error) (err error) {
	stmt, err := tx.Prepare(query)
	if err != nil {
		return fmt.Errorf("tx.Prepare(%q): %v", query, err)
	}

	defer func() {
		cerr := stmt.Close()
		if err == nil {
			err = cerr
		}
	}()
	if err := stmtFunc(stmt); err != nil {
		return fmt.Errorf("stmtFunc(stmt): %v", err)
	}
	return nil
}

const onConflictDoNothing = "ON CONFLICT DO NOTHING"

// bulkInsert constructs and executes a multi-value insert statement. The
// query is constructed using the format: INSERT TO <table> (<columns>) VALUES
// (<placeholders-for-each-item-in-values>) If conflictNoAction is true, it
// append ON CONFLICT DO NOTHING to the end of the query. The query is executed
// using a PREPARE statement with the provided values.
func bulkInsert(ctx context.Context, tx *sql.Tx, table string, columns []string, values []interface{}, conflictAction string) (err error) {
	defer derrors.Wrap(&err, "bulkInsert(ctx, tx, %q, %v, [%d values], %q)",
		table, columns, len(values), conflictAction)

	if remainder := len(values) % len(columns); remainder != 0 {
		return fmt.Errorf("modulus of len(values) and len(columns) must be 0: got %d", remainder)
	}

	const maxParameters = 65535 // maximum number of parameters allowed by Postgres
	stride := (maxParameters / len(columns)) * len(columns)
	if stride == 0 {
		// This is a pathological case (len(columns) > maxParameters), but we
		// handle it cautiously.
		return fmt.Errorf("too many columns to insert: %d", len(columns))
	}
	for leftBound := 0; leftBound < len(values); leftBound += stride {
		rightBound := leftBound + stride
		if rightBound > len(values) {
			rightBound = len(values)
		}
		valueSlice := values[leftBound:rightBound]
		query, err := buildInsertQuery(table, columns, valueSlice, conflictAction)
		if err != nil {
			return fmt.Errorf("buildInsertQuery(%q, %v, values[%d:%d], %q): %v", table, columns, leftBound, rightBound, conflictAction, err)
		}

		defer logQuery(query, valueSlice)(&err)
		if _, err := tx.ExecContext(ctx, query, valueSlice...); err != nil {
			return fmt.Errorf("tx.ExecContext(ctx, [bulk insert query], values[%d:%d]): %v", leftBound, rightBound, err)
		}
	}
	return nil
}

// buildInsertQuery builds an multi-value insert query, following the format:
// INSERT TO <table> (<columns>) VALUES
// (<placeholders-for-each-item-in-values>) If conflictNoAction is true, it
// append ON CONFLICT DO NOTHING to the end of the query.
//
// When calling buildInsertQuery, it must be true that
//	len(values) % len(columns) == 0
func buildInsertQuery(table string, columns []string, values []interface{}, conflictAction string) (string, error) {
	var b strings.Builder
	fmt.Fprintf(&b, "INSERT INTO %s", table)
	fmt.Fprintf(&b, "(%s) VALUES", strings.Join(columns, ", "))

	var placeholders []string
	for i := 1; i <= len(values); i++ {
		// Construct the full query by adding placeholders for each
		// set of values that we want to insert.
		placeholders = append(placeholders, fmt.Sprintf("$%d", i))
		if i%len(columns) != 0 {
			continue
		}

		// When the end of a set is reached, write it to the query
		// builder and reset placeholders.
		fmt.Fprintf(&b, "(%s)", strings.Join(placeholders, ", "))
		placeholders = []string{}

		// Do not add a comma delimiter after the last set of values.
		if i == len(values) {
			break
		}
		b.WriteString(", ")
	}
	if conflictAction != "" {
		b.WriteString(" " + conflictAction)
	}

	return b.String(), nil
}

// Close closes the database connection.
func (db *DB) Close() error {
	return db.db.Close()
}

// runQuery executes query, then calls f on each row.
func (db *DB) runQuery(ctx context.Context, query string, f func(*sql.Rows) error, params ...interface{}) error {
	rows, err := db.query(ctx, query, params...)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		if err := f(rows); err != nil {
			return err
		}
	}
	return rows.Err()
}

// emptyStringScanner wraps the functionality of sql.NullString to just write
// an empty string if the value is NULL.
type emptyStringScanner struct {
	ptr *string
}

func (e emptyStringScanner) Scan(value interface{}) error {
	var ns sql.NullString
	if err := ns.Scan(value); err != nil {
		return err
	}
	*e.ptr = ns.String
	return nil
}

// nullIsEmpty returns a sql.Scanner that writes the empty string to s if the
// sql.Value is NULL.
func nullIsEmpty(s *string) sql.Scanner {
	return emptyStringScanner{s}
}
<commit_msg>internal/postgres: make smaller bulk insert statements<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package postgres

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"sync/atomic"
	"time"
	"unicode"

	"golang.org/x/discovery/internal/config"
	"golang.org/x/discovery/internal/derrors"
	"golang.org/x/discovery/internal/log"
)

// DB wraps a sql.DB to provide an API for interacting with discovery data
// stored in Postgres.
type DB struct {
	db *sql.DB
}

func (db *DB) exec(ctx context.Context, query string, args ...interface{}) (res sql.Result, err error) {
	defer logQuery(query, args)(&err)

	return db.db.ExecContext(ctx, query, args...)
}

func execTx(ctx context.Context, tx *sql.Tx, query string, args ...interface{}) (res sql.Result, err error) {
	defer logQuery(query, args)(&err)

	return tx.ExecContext(ctx, query, args...)
}

func (db *DB) query(ctx context.Context, query string, args ...interface{}) (_ *sql.Rows, err error) {
	defer logQuery(query, args)(&err)
	return db.db.QueryContext(ctx, query, args...)
}

func (db *DB) queryRow(ctx context.Context, query string, args ...interface{}) *sql.Row {
	defer logQuery(query, args)(nil)
	return db.db.QueryRowContext(ctx, query, args...)
}

var (
	queryCounter         int64 // atomic: per-process counter for unique query IDs
	queryLoggingDisabled bool  // For use in tests only: not concurrency-safe.
)

type queryEndLogEntry struct {
	ID              string
	Query           string
	Args            string
	DurationSeconds float64
	Error           string `json:",omitempty"`
}

func logQuery(query string, args []interface{}) func(*error) {
	if queryLoggingDisabled {
		return func(*error) {}
	}
	const maxlen = 300 // maximum length of displayed query

	// To make the query more compact and readable, replace newlines with spaces
	// and collapse adjacent whitespace.
	var r []rune
	for _, c := range query {
		if c == '\n' {
			c = ' '
		}
		if len(r) == 0 || !unicode.IsSpace(r[len(r)-1]) || !unicode.IsSpace(c) {
			r = append(r, c)
		}
	}
	query = string(r)
	if len(query) > maxlen {
		query = query[:maxlen] + "..."
	}

	instanceID := config.InstanceID()
	if instanceID == "" {
		instanceID = "local"
	} else {
		// Instance IDs are long strings. The low-order part seems quite random, so
		// shortening the ID will still likely result in something unique.
		instanceID = instanceID[len(instanceID)-4:]
	}
	n := atomic.AddInt64(&queryCounter, 1)
	uid := fmt.Sprintf("%s-%d", instanceID, n)

	// Construct a short string of the args.
	const (
		maxArgs   = 20
		maxArgLen = 50
	)
	var argStrings []string
	for i := 0; i < len(args) && i < maxArgs; i++ {
		s := fmt.Sprint(args[i])
		if len(s) > maxArgLen {
			s = s[:maxArgLen] + "..."
		}
		argStrings = append(argStrings, s)
	}
	if len(args) > maxArgs {
		argStrings = append(argStrings, "...")
	}
	argString := strings.Join(argStrings, ", ")

	log.Debugf("%s %s args=%s", uid, query, argString)
	start := time.Now()
	return func(errp *error) {
		dur := time.Since(start)
		if errp == nil { // happens with queryRow
			log.Debugf("%s done", uid)
		} else {
			derrors.Wrap(errp, "DB running query %s", uid)
			entry := queryEndLogEntry{
				ID:              uid,
				Query:           query,
				Args:            argString,
				DurationSeconds: dur.Seconds(),
			}
			if *errp == nil {
				log.Debug(entry)
			} else {
				entry.Error = (*errp).Error()
				log.Error(entry)
			}
		}
	}
}

// Open creates a new DB for the given Postgres connection string.
func Open(driverName, dbinfo string) (*DB, error) {
	db, err := sql.Open(driverName, dbinfo)
	if err != nil {
		return nil, err
	}

	if err = db.Ping(); err != nil {
		return nil, err
	}
	return &DB{db}, nil
}

// Transact executes the given function in the context of a SQL transaction,
// rolling back the transaction if the function panics or returns an error.
func (db *DB) Transact(txFunc func(*sql.Tx) error) (err error) {
	tx, err := db.db.Begin()
	if err != nil {
		return fmt.Errorf("db.Begin(): %v", err)
	}

	defer func() {
		if p := recover(); p != nil {
			tx.Rollback()
			panic(p)
		} else if err != nil {
			tx.Rollback()
		} else {
			if err = tx.Commit(); err != nil {
				err = fmt.Errorf("tx.Commit(): %v", err)
			}
		}
	}()

	if err := txFunc(tx); err != nil {
		return fmt.Errorf("txFunc(tx): %v", err)
	}
	return nil
}

// prepareAndExec prepares a query statement and executes it insde the provided
// transaction.
func prepareAndExec(tx *sql.Tx, query string, stmtFunc func(*sql.Stmt) error) (err error) {
	stmt, err := tx.Prepare(query)
	if err != nil {
		return fmt.Errorf("tx.Prepare(%q): %v", query, err)
	}

	defer func() {
		cerr := stmt.Close()
		if err == nil {
			err = cerr
		}
	}()
	if err := stmtFunc(stmt); err != nil {
		return fmt.Errorf("stmtFunc(stmt): %v", err)
	}
	return nil
}

const onConflictDoNothing = "ON CONFLICT DO NOTHING"

// bulkInsert constructs and executes a multi-value insert statement. The
// query is constructed using the format: INSERT TO <table> (<columns>) VALUES
// (<placeholders-for-each-item-in-values>) If conflictNoAction is true, it
// append ON CONFLICT DO NOTHING to the end of the query. The query is executed
// using a PREPARE statement with the provided values.
func bulkInsert(ctx context.Context, tx *sql.Tx, table string, columns []string, values []interface{}, conflictAction string) (err error) {
	defer derrors.Wrap(&err, "bulkInsert(ctx, tx, %q, %v, [%d values], %q)",
		table, columns, len(values), conflictAction)

	if remainder := len(values) % len(columns); remainder != 0 {
		return fmt.Errorf("modulus of len(values) and len(columns) must be 0: got %d", remainder)
	}

	// Postgres supports up to 65535 parameters, but stop well before that
	// so we don't construct humongous queries.
	const maxParameters = 1000
	stride := (maxParameters / len(columns)) * len(columns)
	if stride == 0 {
		// This is a pathological case (len(columns) > maxParameters), but we
		// handle it cautiously.
		return fmt.Errorf("too many columns to insert: %d", len(columns))
	}
	for leftBound := 0; leftBound < len(values); leftBound += stride {
		rightBound := leftBound + stride
		if rightBound > len(values) {
			rightBound = len(values)
		}
		valueSlice := values[leftBound:rightBound]
		query, err := buildInsertQuery(table, columns, valueSlice, conflictAction)
		if err != nil {
			return fmt.Errorf("buildInsertQuery(%q, %v, values[%d:%d], %q): %v", table, columns, leftBound, rightBound, conflictAction, err)
		}

		if _, err := execTx(ctx, tx, query, valueSlice...); err != nil {
			return fmt.Errorf("tx.ExecContext(ctx, [bulk insert query], values[%d:%d]): %v", leftBound, rightBound, err)
		}
	}
	return nil
}

// buildInsertQuery builds an multi-value insert query, following the format:
// INSERT TO <table> (<columns>) VALUES
// (<placeholders-for-each-item-in-values>) If conflictNoAction is true, it
// append ON CONFLICT DO NOTHING to the end of the query.
//
// When calling buildInsertQuery, it must be true that
//	len(values) % len(columns) == 0
func buildInsertQuery(table string, columns []string, values []interface{}, conflictAction string) (string, error) {
	var b strings.Builder
	fmt.Fprintf(&b, "INSERT INTO %s", table)
	fmt.Fprintf(&b, "(%s) VALUES", strings.Join(columns, ", "))

	var placeholders []string
	for i := 1; i <= len(values); i++ {
		// Construct the full query by adding placeholders for each
		// set of values that we want to insert.
		placeholders = append(placeholders, fmt.Sprintf("$%d", i))
		if i%len(columns) != 0 {
			continue
		}

		// When the end of a set is reached, write it to the query
		// builder and reset placeholders.
		fmt.Fprintf(&b, "(%s)", strings.Join(placeholders, ", "))
		placeholders = []string{}

		// Do not add a comma delimiter after the last set of values.
		if i == len(values) {
			break
		}
		b.WriteString(", ")
	}
	if conflictAction != "" {
		b.WriteString(" " + conflictAction)
	}

	return b.String(), nil
}

// Close closes the database connection.
func (db *DB) Close() error {
	return db.db.Close()
}

// runQuery executes query, then calls f on each row.
func (db *DB) runQuery(ctx context.Context, query string, f func(*sql.Rows) error, params ...interface{}) error {
	rows, err := db.query(ctx, query, params...)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		if err := f(rows); err != nil {
			return err
		}
	}
	return rows.Err()
}

// emptyStringScanner wraps the functionality of sql.NullString to just write
// an empty string if the value is NULL.
type emptyStringScanner struct {
	ptr *string
}

func (e emptyStringScanner) Scan(value interface{}) error {
	var ns sql.NullString
	if err := ns.Scan(value); err != nil {
		return err
	}
	*e.ptr = ns.String
	return nil
}

// nullIsEmpty returns a sql.Scanner that writes the empty string to s if the
// sql.Value is NULL.
func nullIsEmpty(s *string) sql.Scanner {
	return emptyStringScanner{s}
}
<|endoftext|>


<commit_before>/* provider is a Terraform provider for MAAS.
 */
package provider

import (
	"github.com/hashicorp/terraform/terraform"
	"github.com/roblox/terraform-provider-maas/pkg/gmaw"

	"github.com/hashicorp/terraform/helper/schema"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"api_key": {
				Type:        schema.TypeString,
				Required:    true,
				Description: "The MAAS API key",
			},
			"api_url": {
				Type:        schema.TypeString,
				Required:    true,
				Description: "The MAAS server URL (eg http://1.2.3.4:80/MAAS)",
			},
			"api_version": {
				Type:        schema.TypeString,
				Optional:    true,
				Default:     "2.0",
				Description: "The MAAS API version (default 2.0)",
			},
		},
		ResourcesMap: map[string]*schema.Resource{
			"maas_instance": resourceInstance(),
		},
		ConfigureFunc: providerConfigure,
	}
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
	return gmaw.GetClient(d.Get("api_url").(string), d.Get("api_version").(string), d.Get("api_key").(string))
}
<commit_msg>Note that default MAAS API port is 5240<commit_after>/* provider is a Terraform provider for MAAS.
 */
package provider

import (
	"github.com/hashicorp/terraform/terraform"
	"github.com/roblox/terraform-provider-maas/pkg/gmaw"

	"github.com/hashicorp/terraform/helper/schema"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"api_key": {
				Type:        schema.TypeString,
				Required:    true,
				Description: "The MAAS API key",
			},
			"api_url": {
				Type:        schema.TypeString,
				Required:    true,
				Description: "The MAAS server URL (eg http://1.2.3.4:5240/MAAS)",
			},
			"api_version": {
				Type:        schema.TypeString,
				Optional:    true,
				Default:     "2.0",
				Description: "The MAAS API version (default 2.0)",
			},
		},
		ResourcesMap: map[string]*schema.Resource{
			"maas_instance": resourceInstance(),
		},
		ConfigureFunc: providerConfigure,
	}
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
	return gmaw.GetClient(d.Get("api_url").(string), d.Get("api_version").(string), d.Get("api_key").(string))
}
<|endoftext|>


<commit_before>package rivet

import (
	"io"
	"net/http"
	"reflect"
)

var (
	id_httpRequest        = TypeIdOf((*http.Request)(nil))
	id_HttpResponseWriter = TypeIdOf((*ResponseWriter)(nil))
	id_ResponseWriter     = TypeIdOf((*http.ResponseWriter)(nil))
	id_Context            = TypeIdOf((*Context)(nil))
	id_Params             = TypeIdOf(Params(nil))
	id_MapStringInterface = TypeIdOf(map[string]interface{}(nil))
	id_PathParams         = TypeIdOf(PathParams(nil))
	id_MapStringString    = TypeIdOf(map[string]string(nil))
)

/**
TypeIdOf 返回 v 的类型签名地址, 转换为 uint 类型.
内部使用 reflect 获取类型地址.

示例:
获取 fmt.Stringer 接口类型签名:

	var v *fmt.Stringer
	_ = TypeIdOf(v)
	// 或者
	_ = TypeIdOf((*fmt.Stringer)(nil))

获取 reflect.Type 本身的类型签名:

	var rt *reflect.Type
	_ = TypeIdOf(rt) // reflect.Type 也是接口类型
	// 或者
	t := reflect.TypeOf(nil)
	_ = TypeIdOf(&t)

获取函数的参数类型签名:

	t := reflect.TypeOf(fmt.Println)
	_ = TypeIdOf(t.In(0))

非接口类型:

	var s string
	_ = TypeIdOf(s) // 等同 TypeIdOf("")
	var v AnyNotInterfaceType
	_ = TypeIdOf(v)
*/
func TypeIdOf(v interface{}) uint {
	t, ok := v.(reflect.Type)
	if !ok {
		t = reflect.TypeOf(v)
	}
	if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Interface {
		t = t.Elem()
	}

	return uint(reflect.ValueOf(t).Pointer())
}

/**
Rivet 符合 Context 接口. 请使用 NewContext 生成实例.
*/
type Rivet struct {
	Params
	PathParams
	val     map[uint]interface{}
	res     ResponseWriter
	req     *http.Request
	handler []interface{}
	mapv    bool // 是否已经 Map 相关参数
}

// NewContext 新建 *Rivet 实例实现的 Context.
func NewContext(res http.ResponseWriter, req *http.Request) Context {

	c := new(Rivet)
	c.res = NewResponseWriterFakeFlusher(res)
	c.req = req
	return c
}

// Request 返回生成 Context 的 *http.Request
func (c *Rivet) Request() *http.Request {
	return c.req
}

// Response 返回生成 Context 的 http.ResponseWriter
func (c *Rivet) Response() http.ResponseWriter {
	return c.res
}

// WriteString 向 http.ResponseWriter 写入 data.
func (c *Rivet) WriteString(data string) (int, error) {
	return io.WriteString(c.res, data)
}

//	GetParams 返回路由匹配时从 URL.Path 中提取的参数
func (c *Rivet) GetParams() Params {
	return c.Params
}

/**
PathParams 返回路由匹配时从 URL.Path 中提取的参数
此方法与 Scene/NewScene 配套使用.
*/
func (c *Rivet) GetPathParams() PathParams {
	return c.PathParams
}

/**
ParamsReceiver 逐个接收从 URL.Path 中提取的参数.
此方法把参数值 val 保存在 Params 字段中.
*/
func (c *Rivet) ParamsReceiver(name, _ string, val interface{}) {

	if c.Params == nil {
		c.Params = make(Params, 1)
	}
	c.Params[name] = val
}

// Handlers 设置 handler, 首次使用有效.
func (c *Rivet) Handlers(handler ...interface{}) {
	if c.handler == nil {
		c.handler = handler
	}
}

/**
Get 以类型标识 t 为 key, 获取关联到 context 的变量.
如果未找到, 通常返回 nil, 特别的:

	如果 t 代表 map[string]interface{}, 用 Params 标识再试一次.
	如果 t 代表 map[string]string, 用 PathParams 标识再试一次.

这样做, 如果不用 Map 功能, 所写的 Handler 就不需要 import rivet.
*/
func (r *Rivet) Get(t uint) (v interface{}) {
	v, _ = r.get(t)
	return
}

func (r *Rivet) get(t uint) (interface{}, bool) {

	if !r.mapv {
		r.mapv = true
		r.MapTo(r.Params, id_Params)
		r.MapTo(r.PathParams, id_PathParams)
		r.MapTo(r, id_Context)
		r.MapTo(r.req, id_httpRequest)
		r.MapTo(r.res, id_ResponseWriter)
		r.MapTo(r.res, id_HttpResponseWriter)
	}

	i, ok := r.val[t]
	if ok {
		return i, true
	}

	if t == id_MapStringInterface {
		return r.Params, true
	}

	if t == id_MapStringString {
		return r.PathParams, true
	}

	return nil, false
}

/**
Map 等同 MapTo(v, TypeIdOf(v)). 以 v 的类型标识为 key.
Rivet 自动 Map 的变量类型有:
	Context
	Params
	PathParams
	ResponseWriter
	http.ResponseWriter
	*http.Request
*/
func (r *Rivet) Map(v interface{}) {
	r.MapTo(v, TypeIdOf(v))
}

/**
MapTo 以 t 为 key 把变量 v 关联到 context. 相同 t 值只保留一个.
调用者也许会自己定义一个值, 注意不要和真实类型标识冲突.
否则会导致不可预计的错误.
*/
func (r *Rivet) MapTo(v interface{}, t uint) {
	if r.val == nil {
		r.val = make(map[uint]interface{}, 6)
	}
	r.val[t] = v
}

/**
Next 遍历 Handlers 保存的 handler, 通过 Invoke 调用.
如果 ResponseWriter.Written() 为 true, 终止遍历.
Next 最后会调用 ResponseWriter.Flush(), 清空 handler.
*/
func (c *Rivet) Next() {

	var h interface{}
	for {
		if len(c.handler) == 0 || c.res.Written() {
			break
		}
		h = c.handler[0]
		c.handler = c.handler[1:]
		c.Invoke(h)
	}

	c.res.Flush()
	c.handler = nil
}

/**
Invoke 处理 handler.

参数:
	handler 可以是任意值
		如果 handler 可被调用, 准备相应参数, 并调用 handler.
		否则 使用 Map 关联到 context.
返回:
	如果 handler 可被调用, 但是无法获取其参数, 返回 false.
	否则返回 true.

算法:
	如果 handler 是函数或者是有 ServeHTTP 方法的对象, 准备参数并调用.
	否则使用 Map 关联到 context.
	ServeHTTP 支持泛类型, 当然包括 http.Handler 实例.
	下列 handler 类型使用 switch 匹配, 参数直接传递, 未用 Get 从 context 获取:

	func()
	func(Context)
	func(*http.Request)
	func(ResponseWriter)
	func(ResponseWriter, *http.Request)
	func(http.ResponseWriter)
	func(http.ResponseWriter, *http.Request)
	func(Params)
	func(Params, *http.Request)
	func(Params, ResponseWriter)
	func(Params, http.ResponseWriter)
	func(Params, ResponseWriter, *http.Request)
	func(Params, http.ResponseWriter, *http.Request)

	func(PathParams)
	func(PathParams, *http.Request)
	func(PathParams, ResponseWriter)
	func(PathParams, http.ResponseWriter)
	func(PathParams, ResponseWriter, *http.Request)
	func(PathParams, http.ResponseWriter, *http.Request)
	http.Handler

提示:
	Invoke 未捕获可能产生的 panic, 需要使用者处理.
*/
func (c *Rivet) Invoke(handler interface{}) bool {

	switch fn := handler.(type) {
	default:

		// 反射调用或者 Map 对象
		fun := reflect.ValueOf(handler)
		if fun.Kind() != reflect.Func {
			fun = fun.MethodByName("ServeHTTP")
		}

		if fun.Kind() != reflect.Func {
			c.Map(handler)
			return true
		}

		t := fun.Type()

		in := make([]reflect.Value, t.NumIn())

		for i := 0; i < len(in); i++ {

			val, ok := c.get(TypeIdOf(t.In(i)))
			if !ok {
				return false
			}
			in[i] = reflect.ValueOf(val)
		}

		if t.IsVariadic() {
			fun.CallSlice(in)
		} else {
			fun.Call(in)
		}

	case http.Handler:
		fn.ServeHTTP(c.res, c.req)

	case func():
		fn()
	case func(Context):
		fn(c)

	case func(ResponseWriter):
		fn(c.res)
	case func(http.ResponseWriter):
		fn(c.res)
	case func(*http.Request):
		fn(c.req)

	case func(ResponseWriter, *http.Request):
		fn(c.res, c.req)
	case func(http.ResponseWriter, *http.Request):
		fn(c.res, c.req)

	case func(Params):
		fn(c.Params)

	case func(Params, ResponseWriter, *http.Request):
		fn(c.Params, c.res, c.req)
	case func(Params, http.ResponseWriter, *http.Request):
		fn(c.Params, c.res, c.req)

	case func(Params, ResponseWriter):
		fn(c.Params, c.res)

	case func(Params, http.ResponseWriter):
		fn(c.Params, c.res)

	case func(Params, *http.Request):
		fn(c.Params, c.req)

	// PathParams
	case func(PathParams):
		fn(c.PathParams)

	case func(PathParams, ResponseWriter, *http.Request):
		fn(c.PathParams, c.res, c.req)
	case func(PathParams, http.ResponseWriter, *http.Request):
		fn(c.PathParams, c.res, c.req)

	case func(PathParams, ResponseWriter):
		fn(c.PathParams, c.res)

	case func(PathParams, http.ResponseWriter):
		fn(c.PathParams, c.res)

	case func(PathParams, *http.Request):
		fn(c.PathParams, c.req)
	}
	return true
}

/**
Scene 支持 Handler 参数类型为 PathParams 的风格.
*/
type Scene struct {
	*Rivet
}

// NewScene 新建 *Scene 实例实现的 Context.
func NewScene(res http.ResponseWriter, req *http.Request) Context {
	return Scene{NewContext(res, req).(*Rivet)}
}

/**
ParamsReceiver 逐个接收从 URL.Path 中提取的参数.
此方法把参数值 text 保存在 PathParams 字段中.
*/
func (c Scene) ParamsReceiver(key, text string, _ interface{}) {
	if c.PathParams == nil {
		c.PathParams = make(PathParams, 1)
	}
	c.PathParams[key] = text
}
<commit_msg>更改 Invoke 中有关 Params, PathParams 参数匹配 此更改明示 import rivet 不是必要的.<commit_after>package rivet

import (
	"io"
	"net/http"
	"reflect"
)

var (
	id_httpRequest        = TypeIdOf((*http.Request)(nil))
	id_HttpResponseWriter = TypeIdOf((*ResponseWriter)(nil))
	id_ResponseWriter     = TypeIdOf((*http.ResponseWriter)(nil))
	id_Context            = TypeIdOf((*Context)(nil))
	id_Params             = TypeIdOf(Params(nil))
	id_MapStringInterface = TypeIdOf(map[string]interface{}(nil))
	id_PathParams         = TypeIdOf(PathParams(nil))
	id_MapStringString    = TypeIdOf(map[string]string(nil))
)

/**
TypeIdOf 返回 v 的类型签名地址, 转换为 uint 类型.
内部使用 reflect 获取类型地址.

示例:
获取 fmt.Stringer 接口类型签名:

	var v *fmt.Stringer
	_ = TypeIdOf(v)
	// 或者
	_ = TypeIdOf((*fmt.Stringer)(nil))

获取 reflect.Type 本身的类型签名:

	var rt *reflect.Type
	_ = TypeIdOf(rt) // reflect.Type 也是接口类型
	// 或者
	t := reflect.TypeOf(nil)
	_ = TypeIdOf(&t)

获取函数的参数类型签名:

	t := reflect.TypeOf(fmt.Println)
	_ = TypeIdOf(t.In(0))

非接口类型:

	var s string
	_ = TypeIdOf(s) // 等同 TypeIdOf("")
	var v AnyNotInterfaceType
	_ = TypeIdOf(v)
*/
func TypeIdOf(v interface{}) uint {
	t, ok := v.(reflect.Type)
	if !ok {
		t = reflect.TypeOf(v)
	}
	if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Interface {
		t = t.Elem()
	}

	return uint(reflect.ValueOf(t).Pointer())
}

/**
Rivet 符合 Context 接口. 请使用 NewContext 生成实例.
*/
type Rivet struct {
	Params
	PathParams
	val     map[uint]interface{}
	res     ResponseWriter
	req     *http.Request
	handler []interface{}
	mapv    bool // 是否已经 Map 相关参数
}

// NewContext 新建 *Rivet 实例实现的 Context.
func NewContext(res http.ResponseWriter, req *http.Request) Context {

	c := new(Rivet)
	c.res = NewResponseWriterFakeFlusher(res)
	c.req = req
	return c
}

// Request 返回生成 Context 的 *http.Request
func (c *Rivet) Request() *http.Request {
	return c.req
}

// Response 返回生成 Context 的 http.ResponseWriter
func (c *Rivet) Response() http.ResponseWriter {
	return c.res
}

// WriteString 向 http.ResponseWriter 写入 data.
func (c *Rivet) WriteString(data string) (int, error) {
	return io.WriteString(c.res, data)
}

//	GetParams 返回路由匹配时从 URL.Path 中提取的参数
func (c *Rivet) GetParams() Params {
	return c.Params
}

/**
PathParams 返回路由匹配时从 URL.Path 中提取的参数
此方法与 Scene/NewScene 配套使用.
*/
func (c *Rivet) GetPathParams() PathParams {
	return c.PathParams
}

/**
ParamsReceiver 逐个接收从 URL.Path 中提取的参数.
此方法把参数值 val 保存在 Params 字段中.
*/
func (c *Rivet) ParamsReceiver(name, _ string, val interface{}) {

	if c.Params == nil {
		c.Params = make(Params, 1)
	}
	c.Params[name] = val
}

// Handlers 设置 handler, 首次使用有效.
func (c *Rivet) Handlers(handler ...interface{}) {
	if c.handler == nil {
		c.handler = handler
	}
}

/**
Get 以类型标识 t 为 key, 获取关联到 context 的变量.
如果未找到, 通常返回 nil, 特别的:

	如果 t 代表 map[string]interface{}, 用 Params 标识再试一次.
	如果 t 代表 map[string]string, 用 PathParams 标识再试一次.

这样做, 如果不用 Map 功能, 所写的 Handler 就不需要 import rivet.
*/
func (r *Rivet) Get(t uint) (v interface{}) {
	v, _ = r.get(t)
	return
}

func (r *Rivet) get(t uint) (interface{}, bool) {

	if !r.mapv {
		r.mapv = true
		r.MapTo(r.Params, id_Params)
		r.MapTo(r.PathParams, id_PathParams)
		r.MapTo(r, id_Context)
		r.MapTo(r.req, id_httpRequest)
		r.MapTo(r.res, id_ResponseWriter)
		r.MapTo(r.res, id_HttpResponseWriter)
	}

	i, ok := r.val[t]
	if ok {
		return i, true
	}

	if t == id_MapStringInterface {
		return r.Params, true
	}

	if t == id_MapStringString {
		return r.PathParams, true
	}

	return nil, false
}

/**
Map 等同 MapTo(v, TypeIdOf(v)). 以 v 的类型标识为 key.
Rivet 自动 Map 的变量类型有:
	Context
	Params
	PathParams
	ResponseWriter
	http.ResponseWriter
	*http.Request
*/
func (r *Rivet) Map(v interface{}) {
	r.MapTo(v, TypeIdOf(v))
}

/**
MapTo 以 t 为 key 把变量 v 关联到 context. 相同 t 值只保留一个.
调用者也许会自己定义一个值, 注意不要和真实类型标识冲突.
否则会导致不可预计的错误.
*/
func (r *Rivet) MapTo(v interface{}, t uint) {
	if r.val == nil {
		r.val = make(map[uint]interface{}, 6)
	}
	r.val[t] = v
}

/**
Next 遍历 Handlers 保存的 handler, 通过 Invoke 调用.
如果 ResponseWriter.Written() 为 true, 终止遍历.
Next 最后会调用 ResponseWriter.Flush(), 清空 handler.
*/
func (c *Rivet) Next() {

	var h interface{}
	for {
		if len(c.handler) == 0 || c.res.Written() {
			break
		}
		h = c.handler[0]
		c.handler = c.handler[1:]
		c.Invoke(h)
	}

	c.res.Flush()
	c.handler = nil
}

/**
Invoke 处理 handler.

参数:
	handler 可以是任意值
		如果 handler 可被调用, 准备相应参数, 并调用 handler.
		否则 使用 Map 关联到 context.
返回:
	如果 handler 可被调用, 但是无法获取其参数, 返回 false.
	否则返回 true.

算法:
	如果 handler 是函数或者是有 ServeHTTP 方法的对象, 准备参数并调用.
	否则使用 Map 关联到 context.
	ServeHTTP 支持泛类型, 当然包括 http.Handler 实例.
	下列 handler 类型使用 switch 匹配, 参数直接传递, 未用 Get 从 context 获取:

	func()
	func(Context)
	func(*http.Request)
	func(ResponseWriter)
	func(ResponseWriter, *http.Request)
	func(http.ResponseWriter)
	func(http.ResponseWriter, *http.Request)

	func(map[string]interface{}, http.ResponseWriter, *http.Request)
	func(Params, *http.Request)
	func(Params, ResponseWriter)
	func(Params, http.ResponseWriter)
	func(Params, ResponseWriter, *http.Request)
	func(Params, http.ResponseWriter, *http.Request)

	func(map[string]string, http.ResponseWriter, *http.Request)
	func(PathParams, *http.Request)
	func(PathParams, ResponseWriter)
	func(PathParams, http.ResponseWriter)
	func(PathParams, ResponseWriter, *http.Request)
	func(PathParams, http.ResponseWriter, *http.Request)
	http.Handler

提示:
	Invoke 未捕获可能产生的 panic, 需要使用者处理.
*/
func (c *Rivet) Invoke(handler interface{}) bool {

	switch fn := handler.(type) {
	default:

		// 反射调用或者 Map 对象
		fun := reflect.ValueOf(handler)
		if fun.Kind() != reflect.Func {
			fun = fun.MethodByName("ServeHTTP")
		}

		if fun.Kind() != reflect.Func {
			c.Map(handler)
			return true
		}

		t := fun.Type()

		in := make([]reflect.Value, t.NumIn())

		for i := 0; i < len(in); i++ {

			val, ok := c.get(TypeIdOf(t.In(i)))
			if !ok {
				return false
			}
			in[i] = reflect.ValueOf(val)
		}

		if t.IsVariadic() {
			fun.CallSlice(in)
		} else {
			fun.Call(in)
		}

	case http.Handler:
		fn.ServeHTTP(c.res, c.req)

	case func():
		fn()
	case func(Context):
		fn(c)

	case func(ResponseWriter):
		fn(c.res)
	case func(http.ResponseWriter):
		fn(c.res)
	case func(*http.Request):
		fn(c.req)

	case func(ResponseWriter, *http.Request):
		fn(c.res, c.req)
	case func(http.ResponseWriter, *http.Request):
		fn(c.res, c.req)

	case func(map[string]interface{}, http.ResponseWriter, *http.Request):
		fn(c.Params, c.res, c.req)

	case func(Params, ResponseWriter, *http.Request):
		fn(c.Params, c.res, c.req)
	case func(Params, http.ResponseWriter, *http.Request):
		fn(c.Params, c.res, c.req)

	case func(Params, ResponseWriter):
		fn(c.Params, c.res)

	case func(Params, http.ResponseWriter):
		fn(c.Params, c.res)

	case func(Params, *http.Request):
		fn(c.Params, c.req)

	// PathParams
	case func(map[string]string, http.ResponseWriter, *http.Request):
		fn(c.PathParams, c.res, c.req)

	case func(PathParams, ResponseWriter, *http.Request):
		fn(c.PathParams, c.res, c.req)
	case func(PathParams, http.ResponseWriter, *http.Request):
		fn(c.PathParams, c.res, c.req)

	case func(PathParams, ResponseWriter):
		fn(c.PathParams, c.res)

	case func(PathParams, http.ResponseWriter):
		fn(c.PathParams, c.res)

	case func(PathParams, *http.Request):
		fn(c.PathParams, c.req)
	}
	return true
}

/**
Scene 支持 Handler 参数类型为 PathParams 的风格.
*/
type Scene struct {
	*Rivet
}

// NewScene 新建 *Scene 实例实现的 Context.
func NewScene(res http.ResponseWriter, req *http.Request) Context {
	return Scene{NewContext(res, req).(*Rivet)}
}

/**
ParamsReceiver 逐个接收从 URL.Path 中提取的参数.
此方法把参数值 text 保存在 PathParams 字段中.
*/
func (c Scene) ParamsReceiver(key, text string, _ interface{}) {
	if c.PathParams == nil {
		c.PathParams = make(PathParams, 1)
	}
	c.PathParams[key] = text
}
<|endoftext|>