<commit_before>package gotomic

import (
	"testing"
	"reflect"
	"fmt"
	"math/rand"
	"runtime"
	"time"
	"hash/crc32"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

type key string
func (self key) HashCode() uint32 {
	return crc32.ChecksumIEEE([]byte(self))
}
func (self key) Equals(t Thing) bool {
	if s, ok := t.(key); ok {
		return s == self
	}
	return false
}

func assertMappy(t *testing.T, h *Hash, cmp map[Hashable]Thing) {
	if e := h.Verify(); e != nil {
		fmt.Println(h.Describe())
		t.Errorf("%v should be valid, got %v", h, e)
	}
	if h.Size() != len(cmp) {
		t.Errorf("%v should have size %v, but had size %v", h, len(cmp), h.Size())
	}
	if tm := h.ToMap(); !reflect.DeepEqual(tm, cmp) {
		t.Errorf("%v should be %#v but is %#v", h, cmp, tm)
	}
	for k, v := range cmp {
		if mv, _ := h.Get(k); !reflect.DeepEqual(mv, v) {
			t.Errorf("%v.get(%v) should produce %v but produced %v", h, k, v, mv)
		}
	}
}

func fiddleHash(t *testing.T, h *Hash, s string, do, done chan bool) {
	<- do
	cmp := make(map[Hashable]Thing)
	n := 10000
	for i := 0; i < n; i++ {
		k := key(fmt.Sprint(s, rand.Int()))
		v := fmt.Sprint(k, "value")
		if hv := h.Put(k, v); hv != nil {
			fmt.Println(h.Describe())
			t.Errorf("1 Put(%v, %v) should produce nil but produced %v", k, v, hv)
		}
		cmp[k] = v
	}
	for k, v := range cmp {
		if hv, _ := h.Get(k); !reflect.DeepEqual(hv, v) {
			t.Errorf("1 Get(%v) should produce %v but produced %v", k, v, hv)
		}
	}
	for k, v := range cmp {
		v2 := fmt.Sprint(v, ".2")
		cmp[k] = v2
		if hv := h.Put(k, v2); !reflect.DeepEqual(hv, v) {
			t.Errorf("2 Put(%v, %v) should produce %v but produced %v", k, v2, v, hv)
		}
	}
	for k, v := range cmp {
		if hv, _ := h.Get(k); !reflect.DeepEqual(hv, v) {
			t.Errorf("2 Get(%v) should produce %v but produced %v", k, v, hv)
		}
	}
	for k, v := range cmp {
		if hv := h.Delete(k); !reflect.DeepEqual(hv, v) {
			t.Errorf("1 Delete(%v) should produce %v but produced %v", k, v, hv)
		}
	}
	for k, _ := range cmp {
		if hv := h.Delete(k); hv != nil {
			t.Errorf("2 Delete(%v) should produce nil but produced %v", k, hv)
		}
	}
	for k, _ := range cmp {
		if hv, _ := h.Get(k); hv != nil {
			t.Errorf("3 Get(%v) should produce nil but produced %v", k, hv)
		}
	}
	done <- true
}

type hashInt int
func (self hashInt) HashCode() uint32 {
	return uint32(self)
}
func (self hashInt) Equals(t Thing) bool {
	if i, ok := t.(hashInt); ok {
		return i == self
	}
	return false
}

func BenchmarkHash(b *testing.B) {
	m := NewHash()
	for i := 0; i < b.N; i++ {
		k := hashInt(i)
		m.Put(k, i)
		j, _ := m.Get(k)
		if j != i {
			b.Error("should be same value")
		}
	}
}

func action(b *testing.B, m *Hash, i int, do, done chan bool) {
	<- do
	for j := 0; j < i; j++ {
		k := hashInt(j)
		m.Put(k, j)
		l, _ := m.Get(k)
		if l != j {
			b.Error("should be same value")
		}
	}
	done <- true
}

func BenchmarkHashConc(b *testing.B) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	do := make(chan bool)
	done := make(chan bool)
	m := NewHash()
	for i := 0; i < runtime.NumCPU(); i++ {
		go action(b, m, b.N, do, done)
	}
	close(do)
	b.StartTimer()
	for i := 0; i < runtime.NumCPU(); i++ {
		<- done
	}
	b.StopTimer()
	runtime.GOMAXPROCS(1)
}

func TestConcurrency(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	h := NewHash()
	cmp := make(map[Hashable]Thing)
	for i := 0; i < 1000; i++ {
		k := key(fmt.Sprint("key", i))
		v := fmt.Sprint("value", i)
		h.Put(k, v)
		cmp[k] = v
	}
	assertMappy(t, h, cmp)
	do := make(chan bool)
	done := make(chan bool)
	for i := 0; i < runtime.NumCPU(); i++ {
		go fiddleHash(t, h, "fiddlerA", do, done)
	}
	close(do)
	for i := 0; i < runtime.NumCPU(); i++ {
		<- done
	}
	assertMappy(t, h, cmp)
}

func TestPutDelete(t *testing.T) {
	h := NewHash()
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{})
	h.Put(key("a"), "b")
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("a"): "b"})
	h.Put(key("a"), "b")
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("a"): "b"})
	h.Put(key("c"), "d")
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("a"): "b", key("c"): "d"})
	if v := h.Delete(key("a")); v != "b" {
		t.Error(h, "should be able to delete 'a' but got ", v)
	}
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("c"): "d"})
	if v := h.Delete(key("a")); v != nil {
		t.Error(h, "should not be able to delete 'a' but got ", v)
	}
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("c"): "d"})
	if v := h.Delete(key("c")); v != "d" {
		t.Error(h, "should be able to delete 'c' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{})
	if v := h.Delete(key("c")); v != nil {
		t.Error(h, "should not be able to delete 'c' but got ", v)
	}
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{})
}

<commit_msg>argh. god damn it randomizers gonna randomize<commit_after>package gotomic

import (
	"testing"
	"reflect"
	"fmt"
	"math/rand"
	"runtime"
	"time"
	"hash/crc32"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

type key string
func (self key) HashCode() uint32 {
	return crc32.ChecksumIEEE([]byte(self))
}
func (self key) Equals(t Thing) bool {
	if s, ok := t.(key); ok {
		return s == self
	}
	return false
}

func assertMappy(t *testing.T, h *Hash, cmp map[Hashable]Thing) {
	if e := h.Verify(); e != nil {
		fmt.Println(h.Describe())
		t.Errorf("%v should be valid, got %v", h, e)
	}
	if h.Size() != len(cmp) {
		t.Errorf("%v should have size %v, but had size %v", h, len(cmp), h.Size())
	}
	if tm := h.ToMap(); !reflect.DeepEqual(tm, cmp) {
		t.Errorf("%v should be %#v but is %#v", h, cmp, tm)
	}
	for k, v := range cmp {
		if mv, _ := h.Get(k); !reflect.DeepEqual(mv, v) {
			t.Errorf("%v.get(%v) should produce %v but produced %v", h, k, v, mv)
		}
	}
}

func fiddleHash(t *testing.T, h *Hash, s string, do, done chan bool) {
	<- do
	cmp := make(map[Hashable]Thing)
	n := 100000
	for i := 0; i < n; i++ {
		k := key(fmt.Sprint(s, i))
		v := fmt.Sprint(k, "value")
		if hv := h.Put(k, v); hv != nil {
			t.Errorf("1 Put(%v, %v) should produce nil but produced %v", k, v, hv)
		}
		cmp[k] = v
	}
	for k, v := range cmp {
		if hv, _ := h.Get(k); !reflect.DeepEqual(hv, v) {
			t.Errorf("1 Get(%v) should produce %v but produced %v", k, v, hv)
		}
	}
	for k, v := range cmp {
		v2 := fmt.Sprint(v, ".2")
		cmp[k] = v2
		if hv := h.Put(k, v2); !reflect.DeepEqual(hv, v) {
			t.Errorf("2 Put(%v, %v) should produce %v but produced %v", k, v2, v, hv)
		}
	}
	for k, v := range cmp {
		if hv, _ := h.Get(k); !reflect.DeepEqual(hv, v) {
			t.Errorf("2 Get(%v) should produce %v but produced %v", k, v, hv)
		}
	}
	for k, v := range cmp {
		if hv := h.Delete(k); !reflect.DeepEqual(hv, v) {
			t.Errorf("1 Delete(%v) should produce %v but produced %v", k, v, hv)
		}
	}
	for k, _ := range cmp {
		if hv := h.Delete(k); hv != nil {
			t.Errorf("2 Delete(%v) should produce nil but produced %v", k, hv)
		}
	}
	for k, _ := range cmp {
		if hv, _ := h.Get(k); hv != nil {
			t.Errorf("3 Get(%v) should produce nil but produced %v", k, hv)
		}
	}
	done <- true
}

type hashInt int
func (self hashInt) HashCode() uint32 {
	return uint32(self)
}
func (self hashInt) Equals(t Thing) bool {
	if i, ok := t.(hashInt); ok {
		return i == self
	}
	return false
}

func BenchmarkHash(b *testing.B) {
	m := NewHash()
	for i := 0; i < b.N; i++ {
		k := hashInt(i)
		m.Put(k, i)
		j, _ := m.Get(k)
		if j != i {
			b.Error("should be same value")
		}
	}
}

func action(b *testing.B, m *Hash, i int, do, done chan bool) {
	<- do
	for j := 0; j < i; j++ {
		k := hashInt(j)
		m.Put(k, j)
		l, _ := m.Get(k)
		if l != j {
			b.Error("should be same value")
		}
	}
	done <- true
}

func BenchmarkHashConc(b *testing.B) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	do := make(chan bool)
	done := make(chan bool)
	m := NewHash()
	for i := 0; i < runtime.NumCPU(); i++ {
		go action(b, m, b.N, do, done)
	}
	close(do)
	b.StartTimer()
	for i := 0; i < runtime.NumCPU(); i++ {
		<- done
	}
	b.StopTimer()
	runtime.GOMAXPROCS(1)
}

func TestConcurrency(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	h := NewHash()
	cmp := make(map[Hashable]Thing)
	for i := 0; i < 1000; i++ {
		k := key(fmt.Sprint("key", i))
		v := fmt.Sprint("value", i)
		h.Put(k, v)
		cmp[k] = v
	}
	assertMappy(t, h, cmp)
	do := make(chan bool)
	done := make(chan bool)
	for i := 0; i < runtime.NumCPU(); i++ {
		go fiddleHash(t, h, fmt.Sprint("fiddler-", i, "-"), do, done)
	}
	close(do)
	for i := 0; i < runtime.NumCPU(); i++ {
		<- done
	}
	assertMappy(t, h, cmp)
}

func TestPutDelete(t *testing.T) {
	h := NewHash()
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{})
	h.Put(key("a"), "b")
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("a"): "b"})
	h.Put(key("a"), "b")
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("a"): "b"})
	h.Put(key("c"), "d")
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("a"): "b", key("c"): "d"})
	if v := h.Delete(key("a")); v != "b" {
		t.Error(h, "should be able to delete 'a' but got ", v)
	}
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("c"): "d"})
	if v := h.Delete(key("a")); v != nil {
		t.Error(h, "should not be able to delete 'a' but got ", v)
	}
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{key("c"): "d"})
	if v := h.Delete(key("c")); v != "d" {
		t.Error(h, "should be able to delete 'c' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{})
	if v := h.Delete(key("c")); v != nil {
		t.Error(h, "should not be able to delete 'c' but got ", v)
	}
	if v := h.Delete(key("e")); v != nil {
		t.Error(h, "should not be able to delete 'e' but got ", v)
	}
	assertMappy(t, h, map[Hashable]Thing{})
}

<|endoftext|>


<commit_before>package indexer

import (
	"bytes"
	"encoding/json"
	"io"
	"strings"

	"github.com/dchest/stemmer/porter2"

	"github.com/dchest/static-search/indexer/tokenizer"
)

type Index struct {
	Docs  []*Document              `json:"docs"`
	Words map[string][]interface{} `json:"words"`

	HTMLTitleWeight        int `json:"-"`
	HTMLURLComponentWeight int `json:"-"`
}

type Document struct {
	URL   string `json:"u"`
	Title string `json:"t"`
}

func New() *Index {
	return &Index{
		Docs:                   make([]*Document, 0),
		Words:                  make(map[string][]interface{}),
		HTMLTitleWeight:        3,
		HTMLURLComponentWeight: 10,
	}
}

func (n *Index) WriteJSON(w io.Writer) error {
	return json.NewEncoder(w).Encode(n)
}

func (n *Index) addWord(word string, doc, weight int) {
	if weight == 1 {
		n.Words[word] = append(n.Words[word], doc)
	} else {
		n.Words[word] = append(n.Words[word], [2]int{doc, weight})
	}
}

func (n *Index) newDocument(url, title string) int {
	n.Docs = append(n.Docs, &Document{URL: url, Title: title})
	return len(n.Docs) - 1
}

func (n *Index) addString(doc int, text string, wordWeight int) {
	wordcnt := make(map[string]int)
	tk := tokenizer.Words(text)
	for tk.Next() {
		w := tk.Token()
		if len(w) < 2 || isStopWord(w) {
			continue
		}
		wordcnt[porter2.Stemmer.Stem(removeAccents(w))] += wordWeight
	}
	for w, c := range wordcnt {
		n.addWord(w, doc, c)
	}
}

func (n *Index) AddText(url, title string, r io.Reader) error {
	var b bytes.Buffer
	if _, err := io.Copy(&b, r); err != nil {
		return err
	}
	n.addString(n.newDocument(url, title), b.String(), 1)
	return nil
}

func (n *Index) AddHTML(url string, r io.Reader) error {
	title, content, err := parseHTML(r)
	if err != nil {
		return err
	}
	doc := n.newDocument(url, title)
	n.addString(doc, title, n.HTMLTitleWeight)
	n.addString(doc, content, 1)
	// Add URL components.
	url = strings.TrimPrefix(url, "http://")
	url = strings.TrimPrefix(url, "https://")
	url = strings.TrimPrefix(url, "www.")
	// The farther the component, the less its weight.
	// Also, each components weight depends on the total number of them, so
	// that "blog" in /blog/ weights more than in /blog/some-post/.
	components := strings.Split(url, "/")
	weight := n.HTMLURLComponentWeight / len(components)
	for _, v := range components {
		weight /= 2
		if weight < 1 {
			weight = 1
		}
		n.addString(doc, v, weight)
	}
	return nil
}
<commit_msg>Change HTMLTitleWeight back to 10<commit_after>package indexer

import (
	"bytes"
	"encoding/json"
	"io"
	"strings"

	"github.com/dchest/stemmer/porter2"

	"github.com/dchest/static-search/indexer/tokenizer"
)

type Index struct {
	Docs  []*Document              `json:"docs"`
	Words map[string][]interface{} `json:"words"`

	HTMLTitleWeight        int `json:"-"`
	HTMLURLComponentWeight int `json:"-"`
}

type Document struct {
	URL   string `json:"u"`
	Title string `json:"t"`
}

func New() *Index {
	return &Index{
		Docs:                   make([]*Document, 0),
		Words:                  make(map[string][]interface{}),
		HTMLTitleWeight:        10,
		HTMLURLComponentWeight: 10,
	}
}

func (n *Index) WriteJSON(w io.Writer) error {
	return json.NewEncoder(w).Encode(n)
}

func (n *Index) addWord(word string, doc, weight int) {
	if weight == 1 {
		n.Words[word] = append(n.Words[word], doc)
	} else {
		n.Words[word] = append(n.Words[word], [2]int{doc, weight})
	}
}

func (n *Index) newDocument(url, title string) int {
	n.Docs = append(n.Docs, &Document{URL: url, Title: title})
	return len(n.Docs) - 1
}

func (n *Index) addString(doc int, text string, wordWeight int) {
	wordcnt := make(map[string]int)
	tk := tokenizer.Words(text)
	for tk.Next() {
		w := tk.Token()
		if len(w) < 2 || isStopWord(w) {
			continue
		}
		wordcnt[porter2.Stemmer.Stem(removeAccents(w))] += wordWeight
	}
	for w, c := range wordcnt {
		n.addWord(w, doc, c)
	}
}

func (n *Index) AddText(url, title string, r io.Reader) error {
	var b bytes.Buffer
	if _, err := io.Copy(&b, r); err != nil {
		return err
	}
	n.addString(n.newDocument(url, title), b.String(), 1)
	return nil
}

func (n *Index) AddHTML(url string, r io.Reader) error {
	title, content, err := parseHTML(r)
	if err != nil {
		return err
	}
	doc := n.newDocument(url, title)
	n.addString(doc, title, n.HTMLTitleWeight)
	n.addString(doc, content, 1)
	// Add URL components.
	url = strings.TrimPrefix(url, "http://")
	url = strings.TrimPrefix(url, "https://")
	url = strings.TrimPrefix(url, "www.")
	// The farther the component, the less its weight.
	// Also, each components weight depends on the total number of them, so
	// that "blog" in /blog/ weights more than in /blog/some-post/.
	components := strings.Split(url, "/")
	weight := n.HTMLURLComponentWeight / len(components)
	for _, v := range components {
		weight /= 2
		if weight < 1 {
			weight = 1
		}
		n.addString(doc, v, weight)
	}
	return nil
}
<|endoftext|>


<commit_before>package indexer

import (
	"encoding/json"
	"errors"
	"golang.org/x/net/context"
	"gopkg.in/olivere/elastic.v5"
)

// Indexer performs indexing of items and its references using ElasticCloud
type Indexer struct {
	ElasticSearch *elastic.Client
}

// Reference to indexed item
type Reference struct {
	ParentHash string `json:"parent_hash"`
	Name       string `json:"name"`
}

// IndexItem adds or updates an IPFS item with arbitrary properties
func (i *Indexer) IndexItem(doctype string, hash string, properties map[string]interface{}) error {
	_, err := i.ElasticSearch.Update().
		Index("ipfs").
		Type(doctype).
		Id(hash).
		Doc(properties).
		DocAsUpsert(true).
		Do(context.TODO())

	if err != nil {
		// Handle error
		return err
	}

	return nil
}

// extractRefrences reads the refernces from the JSON response from ElasticSearch
func extractReferences(result *elastic.GetResult) ([]Reference, error) {
	var parsedResult map[string][]Reference

	err := json.Unmarshal(*result.Source, parsedResult)
	if err != nil {
		return nil, err
	}

	references, ok := parsedResult["references"]
	if !ok {
		return nil, errors.New("references not in output")
	}

	return references, nil
}

// GetReferences returns existing references and the type for an object, or nil.
// When no object is found nil is returned but no error is set.
// If no object is found, an empty list is returned.
func (i *Indexer) GetReferences(hash string) ([]Reference, string, error) {
	fsc := elastic.NewFetchSourceContext(true)
	fsc.Include("references")

	result, err := i.ElasticSearch.
		Get().
		Index("ipfs").Type("_all").
		FetchSourceContext(fsc).
		Id(hash).
		Do(context.TODO())

	if err != nil {
		if elastic.IsNotFound(err) {
			return nil, "", nil
		}
		return nil, "", err
	}

	references, err := extractReferences(result)
	if err != nil {
		return nil, "", err
	}

	return references, result.Type, nil
}
<commit_msg>Fix bugs.<commit_after>package indexer

import (
	"encoding/json"
	"golang.org/x/net/context"
	"gopkg.in/olivere/elastic.v5"
	"log"
)

// Indexer performs indexing of items and its references using ElasticCloud
type Indexer struct {
	ElasticSearch *elastic.Client
}

// Reference to indexed item
type Reference struct {
	ParentHash string `json:"parent_hash"`
	Name       string `json:"name"`
}

// IndexItem adds or updates an IPFS item with arbitrary properties
func (i *Indexer) IndexItem(doctype string, hash string, properties map[string]interface{}) error {
	_, err := i.ElasticSearch.Update().
		Index("ipfs").
		Type(doctype).
		Id(hash).
		Doc(properties).
		DocAsUpsert(true).
		Do(context.TODO())

	if err != nil {
		// Handle error
		return err
	}

	return nil
}

// extractRefrences reads the refernces from the JSON response from ElasticSearch
func extractReferences(result *elastic.GetResult) ([]Reference, error) {
	var parsedResult map[string][]Reference

	err := json.Unmarshal(*result.Source, &parsedResult)
	if err != nil {
		log.Printf("can't unmarshal references JSON: %s", *result.Source)
		return nil, err
	}

	references := parsedResult["references"]

	return references, nil
}

// GetReferences returns existing references and the type for an object, or nil.
// When no object is found nil is returned but no error is set.
// If no object is found, an empty list is returned.
func (i *Indexer) GetReferences(hash string) ([]Reference, string, error) {
	fsc := elastic.NewFetchSourceContext(true)
	fsc.Include("references")

	result, err := i.ElasticSearch.
		Get().
		Index("ipfs").Type("_all").
		FetchSourceContext(fsc).
		Id(hash).
		Do(context.TODO())

	if err != nil {
		if elastic.IsNotFound(err) {
			return nil, "", nil
		}
		return nil, "", err
	}

	references, err := extractReferences(result)
	if err != nil {
		return nil, "", err
	}

	return references, result.Type, nil
}
<|endoftext|>


<commit_before>package color

import "unicode"

// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*highlighter) stateFn

// highlighter holds the state of the scanner.
type highlighter struct {
	s     string // string being scanned
	pos   int    // position in buf
	start int    // start position of current verb
	attrs string // attributes of current highlight verb
}

// Highlight replaces the highlight verbs in s with their appropriate
// control sequences and then returns the resulting string
func Highlight(s string) string {
	h := &highlighter{s: s}
	h.run()
	return h.s
}

// run runs the state machine for the highlighter.
func (h *highlighter) run() {
	for state := scanText; state != nil; {
		state = state(h)
	}
}

func (h *highlighter) get() rune {
	return rune(h.s[h.pos])
}

// replaces the verb with a control sequence derived from h.attrs[1:].
func (h *highlighter) replace() {
	h.s = h.s[:h.start] + csi + h.attrs[1:] + "m" + h.s[h.pos:]
	h.pos += len(csi) + len(h.attrs) - (h.pos - h.start)
}

// scanText scans until the next highlight or reset verb.
func scanText(h *highlighter) stateFn {
	for ; h.pos < len(h.s); h.pos++ {
		if h.get() != '%' {
			continue
		}
		h.pos++
		if h.pos >= len(h.s) {
			return nil
		}
		switch h.get() {
		case 'r':
			h.start = h.pos - 1
			h.pos++
			return verbReset
		case 'h':
			h.start = h.pos - 1
			h.pos += 2
			return scanHighlight
		}
	}
	return nil
}

// verbReset replaces the reset verb with the reset control sequence.
func verbReset(h *highlighter) stateFn {
	h.attrs = attrs["reset"]
	h.replace()
	return scanText
}

// scanHighlight scans the highlight verb for attributes,
// then replaces it with a control sequence derived from said attributes.
func scanHighlight(h *highlighter) stateFn {
	for ; h.pos < len(h.s); h.pos++ {
		r := h.get()
		switch {
		case r == 'f':
			return scanColor256(h, preFg256)
		case r == 'b':
			return scanColor256(h, preBg256)
		case unicode.IsLetter(r):
			return scanAttribute
		case r == '+':
			// skip
		case r == ']':
			h.pos++
			if h.attrs != "" {
				h.replace()
			}
			fallthrough
		default:
			h.attrs = ""
			return scanText
		}
	}
	return nil
}

// scanAttribute scans a named attribute
func scanAttribute(h *highlighter) stateFn {
	start := h.pos
	for ; h.pos < len(h.s); h.pos++ {
		if !unicode.IsLetter(h.get()) {
			if a, ok := attrs[h.s[start:h.pos]]; ok {
				h.attrs += a
			}
			return scanHighlight
		}
	}
	return nil
}

// scanColor256 scans a 256 color attribute
func scanColor256(h *highlighter, pre string) stateFn {
	h.pos++
	if h.get() != 'g' {
		h.pos--
		return scanAttribute
	}
	h.pos++
	if !unicode.IsNumber(h.get()) {
		h.pos -= 2
		return scanAttribute
	}
	start := h.pos
	for ; h.pos < len(h.s); h.pos++ {
		if !unicode.IsNumber(h.get()) {

			h.attrs += pre + h.s[start:h.pos]
			return scanHighlight
		}
	}
	return nil
}
<commit_msg>use offset instead of decreasing h.pos<commit_after>package color

import "unicode"

// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*highlighter) stateFn

// highlighter holds the state of the scanner.
type highlighter struct {
	s     string // string being scanned
	pos   int    // position in buf
	start int    // start position of current verb
	attrs string // attributes of current highlight verb
}

// Highlight replaces the highlight verbs in s with their appropriate
// control sequences and then returns the resulting string
func Highlight(s string) string {
	h := &highlighter{s: s}
	h.run()
	return h.s
}

// run runs the state machine for the highlighter.
func (h *highlighter) run() {
	for state := scanText; state != nil; {
		state = state(h)
	}
}

func (h *highlighter) get() rune {
	return rune(h.s[h.pos])
}

// replaces the verb with a control sequence derived from h.attrs[1:].
func (h *highlighter) replace() {
	h.s = h.s[:h.start] + csi + h.attrs[1:] + "m" + h.s[h.pos:]
	h.pos += len(csi) + len(h.attrs) - (h.pos - h.start)
}

// scanText scans until the next highlight or reset verb.
func scanText(h *highlighter) stateFn {
	for ; h.pos < len(h.s); h.pos++ {
		if h.get() != '%' {
			continue
		}
		h.pos++
		if h.pos >= len(h.s) {
			return nil
		}
		switch h.get() {
		case 'r':
			h.start = h.pos - 1
			h.pos++
			return verbReset
		case 'h':
			h.start = h.pos - 1
			h.pos += 2
			return scanHighlight
		}
	}
	return nil
}

// verbReset replaces the reset verb with the reset control sequence.
func verbReset(h *highlighter) stateFn {
	h.attrs = attrs["reset"]
	h.replace()
	return scanText
}

// scanHighlight scans the highlight verb for attributes,
// then replaces it with a control sequence derived from said attributes.
func scanHighlight(h *highlighter) stateFn {
	for ; h.pos < len(h.s); h.pos++ {
		r := h.get()
		switch {
		case r == 'f':
			return scanColor256(h, preFg256)
		case r == 'b':
			return scanColor256(h, preBg256)
		case unicode.IsLetter(r):
			return scanAttribute(h, 0)
		case r == '+':
			// skip
		case r == ']':
			h.pos++
			if h.attrs != "" {
				h.replace()
			}
			fallthrough
		default:
			h.attrs = ""
			return scanText
		}
	}
	return nil
}

// scanAttribute scans a named attribute
func scanAttribute(h *highlighter, off int) stateFn {
	start := h.pos - off
	for ; h.pos < len(h.s); h.pos++ {
		if !unicode.IsLetter(h.get()) {
			if a, ok := attrs[h.s[start:h.pos]]; ok {
				h.attrs += a
			}
			return scanHighlight
		}
	}
	return nil
}

// scanColor256 scans a 256 color attribute
func scanColor256(h *highlighter, pre string) stateFn {
	h.pos++
	if h.get() != 'g' {
		return scanAttribute(h, 1)
	}
	h.pos++
	if !unicode.IsNumber(h.get()) {
		return scanAttribute(h, 2)
	}
	start := h.pos
	for ; h.pos < len(h.s); h.pos++ {
		if !unicode.IsNumber(h.get()) {
			h.attrs += pre + h.s[start:h.pos]
			return scanHighlight
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"sync"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-dockerclient"
	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/technoweenie/grohl"
	"github.com/flynn/flynn/host/types"
)

type attachHandler struct {
	state   *State
	backend Backend
}

func (h *attachHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var attachReq host.AttachReq
	if err := json.NewDecoder(req.Body).Decode(&attachReq); err != nil {
		http.Error(w, "invalid JSON", 400)
		return
	}
	conn, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		return
	}
	conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Type: application/vnd.flynn.attach-hijack\r\n\r\n"))
	h.attach(&attachReq, conn)
}

type dockerAttachClient interface {
	ResizeContainerTTY(string, int, int) error
	AttachToContainer(docker.AttachToContainerOptions) error
}

func (h *attachHandler) attach(req *host.AttachReq, conn io.ReadWriteCloser) {
	defer conn.Close()

	g := grohl.NewContext(grohl.Data{"fn": "attach", "job.id": req.JobID})
	g.Log(grohl.Data{"at": "start"})
	attachWait := make(chan struct{})
	job := h.state.AddAttacher(req.JobID, attachWait)
	if job == nil {
		defer h.state.RemoveAttacher(req.JobID, attachWait)
		if _, err := conn.Write([]byte{host.AttachWaiting}); err != nil {
			return
		}
		// TODO: add timeout
		g.Log(grohl.Data{"at": "wait"})
		<-attachWait
		job = h.state.GetJob(req.JobID)
	}
	w := bufio.NewWriter(conn)
	writeError := func(err string) {
		w.WriteByte(host.AttachError)
		binary.Write(w, binary.BigEndian, uint32(len(err)))
		w.WriteString(err)
		w.Flush()
	}
	if job.Status == host.StatusFailed {
		close(attachWait)
		writeError(*job.Error)
		return
	}

	writeMtx := &sync.Mutex{}
	writeMtx.Lock()

	success := make(chan struct{})
	attached := make(chan struct{})
	failed := make(chan struct{})
	opts := &AttachRequest{
		Job:      job,
		Logs:     req.Flags&host.AttachFlagLogs != 0,
		Stream:   req.Flags&host.AttachFlagStream != 0,
		Height:   req.Height,
		Width:    req.Width,
		Attached: attached,
	}
	var stdinW *io.PipeWriter
	if req.Flags&host.AttachFlagStdin != 0 {
		opts.Stdin, stdinW = io.Pipe()
	}
	if req.Flags&host.AttachFlagStdout != 0 {
		opts.Stdout = newFrameWriter(1, w, writeMtx)
	}
	if req.Flags&host.AttachFlagStderr != 0 {
		opts.Stderr = newFrameWriter(2, w, writeMtx)
	}

	go func() {
		defer func() {
			if stdinW != nil {
				stdinW.Close()
			}
		}()

		select {
		case <-attached:
			g.Log(grohl.Data{"at": "success"})
			conn.Write([]byte{host.AttachSuccess})
			writeMtx.Unlock()
			close(attached)
			close(success)
		case <-failed:
			g.Log(grohl.Data{"at": "failed"})
			return
		}
		close(attachWait)
		r := bufio.NewReader(conn)
		var buf [4]byte

		for {
			frameType, err := r.ReadByte()
			if err != nil {
				// TODO: signal close to attach and close all connections
				return
			}
			switch frameType {
			case host.AttachData:
				stream, err := r.ReadByte()
				if err != nil || stream != 0 || stdinW == nil {
					return
				}
				if _, err := io.ReadFull(r, buf[:]); err != nil {
					return
				}
				length := int64(binary.BigEndian.Uint32(buf[:]))
				if length == 0 {
					stdinW.Close()
					stdinW = nil
					continue
				}
				if _, err := io.CopyN(stdinW, r, length); err != nil {
					return
				}
			case host.AttachSignal:
				if _, err := io.ReadFull(r, buf[:]); err != nil {
					return
				}
				signal := int(binary.BigEndian.Uint32(buf[:]))
				g.Log(grohl.Data{"at": "signal", "signal": signal})
				if err := h.backend.Signal(req.JobID, signal); err != nil {
					g.Log(grohl.Data{"at": "signal", "status": "error", "err": err})
					return
				}
			case host.AttachResize:
				if !job.Job.Config.TTY {
					return
				}
				if _, err := io.ReadFull(r, buf[:]); err != nil {
					return
				}
				height := binary.BigEndian.Uint16(buf[:])
				width := binary.BigEndian.Uint16(buf[2:])
				g.Log(grohl.Data{"at": "tty_resize", "height": height, "width": width})
				if err := h.backend.ResizeTTY(req.JobID, height, width); err != nil {
					g.Log(grohl.Data{"at": "tty_resize", "status": "error", "err": err})
					return
				}
			default:
				return
			}
		}
	}()

	g.Log(grohl.Data{"at": "attach"})
	if err := h.backend.Attach(opts); err != nil && err != io.EOF {
		// TODO: send AttachExit if the job has exited
		select {
		case <-success:
			if exit, ok := err.(ExitError); ok {
				writeMtx.Lock()
				w.WriteByte(host.AttachExit)
				binary.Write(w, binary.BigEndian, uint32(exit))
				w.Flush()
				if exit == 0 {
					err = nil
				}
			}
		default:
			close(failed)
			writeMtx.Lock()
			writeError(err.Error())
		}
		if err != nil {
			g.Log(grohl.Data{"at": "attach", "status": "error", "err": err.Error()})
		}
	}
	g.Log(grohl.Data{"at": "finish"})
}

type ExitError int

func (e ExitError) Error() string {
	return fmt.Sprintf("exit status %d", e)
}

type frameWriter struct {
	mtx *sync.Mutex
	buf [6]byte
	w   *bufio.Writer
}

func newFrameWriter(stream byte, w *bufio.Writer, mtx *sync.Mutex) io.WriteCloser {
	f := &frameWriter{w: w, mtx: mtx}
	f.buf[0] = host.AttachData
	f.buf[1] = stream
	return f
}

func (w *frameWriter) Write(p []byte) (int, error) {
	w.mtx.Lock()
	defer w.mtx.Unlock()
	binary.BigEndian.PutUint32(w.buf[2:], uint32(len(p)))
	w.w.Write(w.buf[:])
	n, _ := w.w.Write(p)
	return n, w.w.Flush()
}

func (w *frameWriter) Close() error {
	w.mtx.Lock()
	defer w.mtx.Unlock()
	binary.BigEndian.PutUint32(w.buf[2:], 0)
	w.w.Write(w.buf[:])
	return w.w.Flush()
}
<commit_msg>host: Unlock writeMtx after writing attach exit<commit_after>package main

import (
	"bufio"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"sync"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-dockerclient"
	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/technoweenie/grohl"
	"github.com/flynn/flynn/host/types"
)

type attachHandler struct {
	state   *State
	backend Backend
}

func (h *attachHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var attachReq host.AttachReq
	if err := json.NewDecoder(req.Body).Decode(&attachReq); err != nil {
		http.Error(w, "invalid JSON", 400)
		return
	}
	conn, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		return
	}
	conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Type: application/vnd.flynn.attach-hijack\r\n\r\n"))
	h.attach(&attachReq, conn)
}

type dockerAttachClient interface {
	ResizeContainerTTY(string, int, int) error
	AttachToContainer(docker.AttachToContainerOptions) error
}

func (h *attachHandler) attach(req *host.AttachReq, conn io.ReadWriteCloser) {
	defer conn.Close()

	g := grohl.NewContext(grohl.Data{"fn": "attach", "job.id": req.JobID})
	g.Log(grohl.Data{"at": "start"})
	attachWait := make(chan struct{})
	job := h.state.AddAttacher(req.JobID, attachWait)
	if job == nil {
		defer h.state.RemoveAttacher(req.JobID, attachWait)
		if _, err := conn.Write([]byte{host.AttachWaiting}); err != nil {
			return
		}
		// TODO: add timeout
		g.Log(grohl.Data{"at": "wait"})
		<-attachWait
		job = h.state.GetJob(req.JobID)
	}
	w := bufio.NewWriter(conn)
	writeError := func(err string) {
		w.WriteByte(host.AttachError)
		binary.Write(w, binary.BigEndian, uint32(len(err)))
		w.WriteString(err)
		w.Flush()
	}
	if job.Status == host.StatusFailed {
		close(attachWait)
		writeError(*job.Error)
		return
	}

	writeMtx := &sync.Mutex{}
	writeMtx.Lock()

	success := make(chan struct{})
	attached := make(chan struct{})
	failed := make(chan struct{})
	opts := &AttachRequest{
		Job:      job,
		Logs:     req.Flags&host.AttachFlagLogs != 0,
		Stream:   req.Flags&host.AttachFlagStream != 0,
		Height:   req.Height,
		Width:    req.Width,
		Attached: attached,
	}
	var stdinW *io.PipeWriter
	if req.Flags&host.AttachFlagStdin != 0 {
		opts.Stdin, stdinW = io.Pipe()
	}
	if req.Flags&host.AttachFlagStdout != 0 {
		opts.Stdout = newFrameWriter(1, w, writeMtx)
	}
	if req.Flags&host.AttachFlagStderr != 0 {
		opts.Stderr = newFrameWriter(2, w, writeMtx)
	}

	go func() {
		defer func() {
			if stdinW != nil {
				stdinW.Close()
			}
		}()

		select {
		case <-attached:
			g.Log(grohl.Data{"at": "success"})
			conn.Write([]byte{host.AttachSuccess})
			writeMtx.Unlock()
			close(attached)
			close(success)
		case <-failed:
			g.Log(grohl.Data{"at": "failed"})
			return
		}
		close(attachWait)
		r := bufio.NewReader(conn)
		var buf [4]byte

		for {
			frameType, err := r.ReadByte()
			if err != nil {
				// TODO: signal close to attach and close all connections
				return
			}
			switch frameType {
			case host.AttachData:
				stream, err := r.ReadByte()
				if err != nil || stream != 0 || stdinW == nil {
					return
				}
				if _, err := io.ReadFull(r, buf[:]); err != nil {
					return
				}
				length := int64(binary.BigEndian.Uint32(buf[:]))
				if length == 0 {
					stdinW.Close()
					stdinW = nil
					continue
				}
				if _, err := io.CopyN(stdinW, r, length); err != nil {
					return
				}
			case host.AttachSignal:
				if _, err := io.ReadFull(r, buf[:]); err != nil {
					return
				}
				signal := int(binary.BigEndian.Uint32(buf[:]))
				g.Log(grohl.Data{"at": "signal", "signal": signal})
				if err := h.backend.Signal(req.JobID, signal); err != nil {
					g.Log(grohl.Data{"at": "signal", "status": "error", "err": err})
					return
				}
			case host.AttachResize:
				if !job.Job.Config.TTY {
					return
				}
				if _, err := io.ReadFull(r, buf[:]); err != nil {
					return
				}
				height := binary.BigEndian.Uint16(buf[:])
				width := binary.BigEndian.Uint16(buf[2:])
				g.Log(grohl.Data{"at": "tty_resize", "height": height, "width": width})
				if err := h.backend.ResizeTTY(req.JobID, height, width); err != nil {
					g.Log(grohl.Data{"at": "tty_resize", "status": "error", "err": err})
					return
				}
			default:
				return
			}
		}
	}()

	g.Log(grohl.Data{"at": "attach"})
	if err := h.backend.Attach(opts); err != nil && err != io.EOF {
		// TODO: send AttachExit if the job has exited
		select {
		case <-success:
			if exit, ok := err.(ExitError); ok {
				writeMtx.Lock()
				w.WriteByte(host.AttachExit)
				binary.Write(w, binary.BigEndian, uint32(exit))
				w.Flush()
				writeMtx.Unlock()
				if exit == 0 {
					err = nil
				}
			}
		default:
			close(failed)
			writeMtx.Lock()
			writeError(err.Error())
			writeMtx.Unlock()
		}
		if err != nil {
			g.Log(grohl.Data{"at": "attach", "status": "error", "err": err.Error()})
		}
	}
	g.Log(grohl.Data{"at": "finish"})
}

type ExitError int

func (e ExitError) Error() string {
	return fmt.Sprintf("exit status %d", e)
}

type frameWriter struct {
	mtx *sync.Mutex
	buf [6]byte
	w   *bufio.Writer
}

func newFrameWriter(stream byte, w *bufio.Writer, mtx *sync.Mutex) io.WriteCloser {
	f := &frameWriter{w: w, mtx: mtx}
	f.buf[0] = host.AttachData
	f.buf[1] = stream
	return f
}

func (w *frameWriter) Write(p []byte) (int, error) {
	w.mtx.Lock()
	defer w.mtx.Unlock()
	binary.BigEndian.PutUint32(w.buf[2:], uint32(len(p)))
	w.w.Write(w.buf[:])
	n, _ := w.w.Write(p)
	return n, w.w.Flush()
}

func (w *frameWriter) Close() error {
	w.mtx.Lock()
	defer w.mtx.Unlock()
	binary.BigEndian.PutUint32(w.buf[2:], 0)
	w.w.Write(w.buf[:])
	return w.w.Flush()
}
<|endoftext|>


<commit_before>package gohost

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"testing"
	"time"

	"github.com/eleniums/gohost/examples/test"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pb "github.com/eleniums/gohost/examples/test/proto"
	assert "github.com/stretchr/testify/require"
)

func Test_Hoster_ListenAndServe_GRPCEndpoint(t *testing.T) {
	// arrange
	service := test.NewService()
	grpcAddr := "127.0.0.1:50051"

	hoster := NewHoster(service, grpcAddr)

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the gRPC endpoint
	conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
	assert.NoError(t, err)
	client := pb.NewTestServiceClient(conn)
	grpcReq := pb.SendRequest{
		Value: "test",
	}
	grpcResp, err := client.Send(context.Background(), &grpcReq)

	// assert
	assert.NoError(t, err)
	assert.NotNil(t, grpcResp)
	assert.True(t, grpcResp.Success)
}

func Test_Hoster_ListenAndServe_HTTPEndpoint(t *testing.T) {
	// arrange
	service := test.NewService()
	httpAddr := "127.0.0.1:9090"
	grpcAddr := "127.0.0.1:50051"

	hoster := NewHoster(service, grpcAddr)
	hoster.HTTPAddr = httpAddr

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the HTTP endpoint
	httpClient := http.Client{
		Timeout: time.Millisecond * 500,
	}
	httpReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%v/v1/send?value=test", httpAddr), nil)
	assert.NoError(t, err)
	doResp, err := httpClient.Do(httpReq)
	assert.NoError(t, err)
	body, err := ioutil.ReadAll(doResp.Body)
	assert.NoError(t, err)
	httpResp := pb.TestResponse{}
	err = json.Unmarshal(body, &httpResp)

	// assert
	assert.NoError(t, err)
	assert.NotNil(t, httpResp)
	assert.True(t, httpResp.Success)
}
<commit_msg>Added tests for max message sizes. Needs some work.<commit_after>package gohost

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"testing"
	"time"

	"github.com/eleniums/gohost/examples/test"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pb "github.com/eleniums/gohost/examples/test/proto"
	assert "github.com/stretchr/testify/require"
)

func Test_Hoster_ListenAndServe_GRPCEndpoint(t *testing.T) {
	// arrange
	service := test.NewService()
	grpcAddr := "127.0.0.1:50051"

	expectedValue := "test"

	hoster := NewHoster(service, grpcAddr)

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the gRPC endpoint
	conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
	assert.NoError(t, err)
	client := pb.NewTestServiceClient(conn)
	grpcReq := pb.SendRequest{
		Value: expectedValue,
	}
	grpcResp, err := client.Echo(context.Background(), &grpcReq)

	// assert
	assert.NoError(t, err)
	assert.NotNil(t, grpcResp)
	assert.Equal(t, expectedValue, grpcResp.Echo)
}

func Test_Hoster_ListenAndServe_HTTPEndpoint(t *testing.T) {
	// arrange
	service := test.NewService()
	httpAddr := "127.0.0.1:9090"
	grpcAddr := "127.0.0.1:50052"

	expectedValue := "test"

	hoster := NewHoster(service, grpcAddr)
	hoster.HTTPAddr = httpAddr

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the HTTP endpoint
	httpClient := http.Client{
		Timeout: time.Millisecond * 500,
	}
	httpReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%v/v1/echo?value="+expectedValue, httpAddr), nil)
	assert.NoError(t, err)
	doResp, err := httpClient.Do(httpReq)
	assert.NoError(t, err)
	body, err := ioutil.ReadAll(doResp.Body)
	assert.NoError(t, err)
	httpResp := pb.EchoResponse{}
	err = json.Unmarshal(body, &httpResp)

	// assert
	assert.NoError(t, err)
	assert.NotNil(t, httpResp)
	assert.Equal(t, expectedValue, httpResp.Echo)
}

func Test_Hoster_ListenAndServe_MaxRecvMsgSize_GRPC_Pass(t *testing.T) {
	// arrange
	service := test.NewService()
	grpcAddr := "127.0.0.1:50053"

	largeValue := string(make([]byte, 10000000))

	hoster := NewHoster(service, grpcAddr)
	hoster.MaxRecvMsgSize = math.MaxInt32

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the gRPC endpoint
	conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
	assert.NoError(t, err)
	client := pb.NewTestServiceClient(conn)
	grpcReq := pb.SendRequest{
		Value: largeValue,
	}
	grpcResp, err := client.Send(context.Background(), &grpcReq, grpc.MaxCallSendMsgSize(math.MaxInt32))

	// assert
	assert.NoError(t, err)
	assert.NotNil(t, grpcResp)
	assert.True(t, grpcResp.Success)
}

func Test_Hoster_ListenAndServe_MaxRecvMsgSize_GRPC_Fail(t *testing.T) {
	// arrange
	service := test.NewService()
	grpcAddr := "127.0.0.1:50054"

	largeValue := string(make([]byte, 10000000))

	hoster := NewHoster(service, grpcAddr)
	hoster.MaxRecvMsgSize = 1

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the gRPC endpoint
	conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
	assert.NoError(t, err)
	client := pb.NewTestServiceClient(conn)
	grpcReq := pb.SendRequest{
		Value: largeValue,
	}
	grpcResp, err := client.Send(context.Background(), &grpcReq, grpc.MaxCallSendMsgSize(math.MaxInt32))

	// assert
	assert.Error(t, err)
	assert.Nil(t, grpcResp)
}

func Test_Hoster_ListenAndServe_MaxRecvMsgSize_HTTP_Pass(t *testing.T) {
	// arrange
	service := test.NewService()
	httpAddr := "127.0.0.1:9090"
	grpcAddr := "127.0.0.1:50055"

	largeValue := string(make([]byte, 10000000))

	hoster := NewHoster(service, grpcAddr)
	hoster.HTTPAddr = httpAddr
	hoster.MaxRecvMsgSize = math.MaxInt32

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the HTTP endpoint
	httpClient := http.Client{
		Timeout: time.Millisecond * 500,
	}
	httpReq := pb.SendRequest{
		Value: largeValue,
	}
	payload, err := json.Marshal(&httpReq)
	assert.NoError(t, err)
	postReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%v/v1/send", httpAddr), bytes.NewBuffer(payload))
	assert.NoError(t, err)
	doResp, err := httpClient.Do(postReq)
	assert.NoError(t, err)
	body, err := ioutil.ReadAll(doResp.Body)
	assert.NoError(t, err)
	httpResp := pb.TestResponse{}
	err = json.Unmarshal(body, &httpResp)

	// assert
	assert.NoError(t, err)
	assert.NotNil(t, httpResp)
	assert.True(t, httpResp.Success)
}

func Test_Hoster_ListenAndServe_MaxRecvMsgSize_HTTP_Fail(t *testing.T) {
	// arrange
	service := test.NewService()
	httpAddr := "127.0.0.1:9090"
	grpcAddr := "127.0.0.1:50056"

	largeValue := string(make([]byte, 10000000))

	hoster := NewHoster(service, grpcAddr)
	hoster.HTTPAddr = httpAddr
	hoster.MaxRecvMsgSize = 1

	// act - start the service
	go hoster.ListenAndServe()

	// make sure service has time to start
	time.Sleep(time.Millisecond * 100)

	// call the service at the HTTP endpoint
	httpClient := http.Client{
		Timeout: time.Millisecond * 500,
	}
	httpReq := pb.SendRequest{
		Value: largeValue,
	}
	payload, err := json.Marshal(&httpReq)
	assert.NoError(t, err)
	postReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%v/v1/send", httpAddr), bytes.NewBuffer(payload))
	assert.NoError(t, err)
	doResp, err := httpClient.Do(postReq)
	assert.NoError(t, err)
	body, err := ioutil.ReadAll(doResp.Body)
	assert.NoError(t, err)
	httpResp := pb.TestResponse{}
	err = json.Unmarshal(body, &httpResp)

	// assert
	assert.Error(t, err)
	assert.NotNil(t, httpResp)
	assert.True(t, httpResp.Success)
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/aisk/logp"
	"github.com/aisk/wizard"
	"github.com/cloudfoundry-attic/jibber_jabber"
	cookiejar "github.com/juju/persistent-cookiejar"
	"github.com/leancloud/lean-cli/api/regions"
	"github.com/leancloud/lean-cli/apps"
	"github.com/leancloud/lean-cli/utils"
	"github.com/leancloud/lean-cli/version"
	"github.com/levigross/grequests"
)

var dashboardBaseUrls = map[regions.Region]string{
	regions.ChinaNorth: "https://cn-n1-console-api.leancloud.cn",
	regions.USWest:     "https://us-w1-console-api.leancloud.app",
	regions.ChinaEast:  "https://cn-e1-console-api.leancloud.cn",
}

var (
	// Get2FACode is the function to get the user's two-factor-authentication code.
	// You can override it with your custom function.
	Get2FACode = func() (int, error) {
		result := new(string)
		wizard.Ask([]wizard.Question{
			{
				Content: "Please input 2-factor auth code",
				Input: &wizard.Input{
					Result: result,
					Hidden: false,
				},
			},
		})
		code, err := strconv.Atoi(*result)
		if err != nil {
			return 0, errors.New("2-factor auth code should be numerical")
		}
		return code, nil
	}
)

type Client struct {
	CookieJar   *cookiejar.Jar
	Region      regions.Region
	AppID       string
	AccessToken string
}

func NewClientByRegion(region regions.Region) *Client {
	if version.Distribution == "lean" {
		return &Client{
			CookieJar: newCookieJar(),
			Region:    region,
		}
	}

	return &Client{
		AccessToken: accessTokenCache[region],
		Region:      region,
	}
}

func NewClientByApp(appID string) *Client {
	if version.Distribution == "lean" {
		return &Client{
			CookieJar: newCookieJar(),
			AppID:     appID,
		}
	}

	region, err := apps.GetAppRegion(appID)
	if err != nil {
		return &Client{
			AppID: appID,
		}
	}

	return &Client{
		AccessToken: accessTokenCache[region],
		AppID:       appID,
	}
}

func (client *Client) GetBaseURL() string {
	envBaseURL := os.Getenv("LEANCLOUD_DASHBOARD")

	if envBaseURL != "" {
		return envBaseURL
	}

	region := client.Region

	if client.AppID != "" {
		var err error
		region, err = apps.GetAppRegion(client.AppID)

		if err != nil {
			panic(err) // This error should be catch at top level
		}
	}

	if url, ok := dashboardBaseUrls[region]; ok {
		return url
	}
	panic("invalid region")
}

func (client *Client) options() (*grequests.RequestOptions, error) {
	u, err := url.Parse(client.GetBaseURL())
	if err != nil {
		panic(err)
	}

	if version.Distribution == "lean" {
		cookies := client.CookieJar.Cookies(u)
		xsrf := ""
		for _, cookie := range cookies {
			if cookie.Name == "XSRF-TOKEN" {
				xsrf = cookie.Value
				break
			}
		}

		return &grequests.RequestOptions{
			Headers: map[string]string{
				"X-XSRF-TOKEN":    xsrf,
				"Accept-Language": getSystemLanguage(),
			},
			CookieJar:    client.CookieJar,
			UseCookieJar: true,
			UserAgent:    "LeanCloud-CLI/" + version.Version,
		}, nil
	}

	return &grequests.RequestOptions{
		Headers: map[string]string{
			"Accept-Language": getSystemLanguage(),
			"Authorization":   fmt.Sprint("Bearer ", client.AccessToken),
		},
		UserAgent: "TDS-CLI/" + version.Version,
	}, nil
}

func doRequest(client *Client, method string, path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	if params != nil {
		options.JSON = params
	}
	var fn func(string, *grequests.RequestOptions) (*grequests.Response, error)
	switch method {
	case "GET":
		fn = grequests.Get
	case "POST":
		fn = grequests.Post
	case "PUT":
		fn = grequests.Put
	case "DELETE":
		fn = grequests.Delete
	case "PATCH":
		fn = grequests.Patch
	default:
		panic("invalid method: " + method)
	}
	resp, err := fn(client.GetBaseURL()+path, options)
	if err != nil {
		return nil, err
	}

	if version.Distribution == "lean" {
		resp, err = client.checkAndDo2FA(resp)
		if err != nil {
			return nil, err
		}
	}

	if !resp.Ok {
		if strings.HasPrefix(strings.TrimSpace(resp.Header.Get("Content-Type")), "application/json") {
			return nil, NewErrorFromResponse(resp)
		}
		return nil, fmt.Errorf("HTTP Error: %d, %s %s", resp.StatusCode, method, path)
	}

	if version.Distribution == "lean" {
		if err = client.CookieJar.Save(); err != nil {
			return nil, err
		}
	}

	return resp, nil
}

// check if the requests need two-factor-authentication and then do it.
func (client *Client) checkAndDo2FA(resp *grequests.Response) (*grequests.Response, error) {
	if resp.StatusCode != 401 || strings.Contains(resp.String(), "User doesn't sign in.") {
		// don't need 2FA
		return resp, nil
	}
	var result struct {
		Token string `json:"token"`
	}
	err := resp.JSON(&result)
	if err != nil {
		return nil, err
	}
	token := result.Token
	if token == "" {
		return resp, nil
	}
	code, err := Get2FACode()
	if err != nil {
		return nil, err
	}

	jar, err := cookiejar.New(&cookiejar.Options{
		Filename: filepath.Join(utils.ConfigDir(), "leancloud", "cookies"),
	})
	if err != nil {
		return nil, err
	}

	resp, err = grequests.Post(client.GetBaseURL()+"/1.1/do2fa", &grequests.RequestOptions{
		JSON: map[string]interface{}{
			"token": token,
			"code":  code,
		},
		CookieJar: jar,
	})
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		if strings.HasPrefix(strings.TrimSpace(resp.Header.Get("Content-Type")), "application/json") {
			return nil, NewErrorFromResponse(resp)
		}
		return nil, fmt.Errorf("HTTP Error: %d, %s %s", resp.StatusCode, "POST", "/do2fa")
	}

	if err := jar.Save(); err != nil {
		return nil, err
	}

	return resp, nil
}

func (client *Client) get(path string, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "GET", path, nil, options)
}

func (client *Client) post(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "POST", path, params, options)
}

func (client *Client) patch(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "PATCH", path, params, options)
}

func (client *Client) put(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "PUT", path, params, options)
}

func (client *Client) delete(path string, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "DELETE", path, nil, options)
}

func newCookieJar() *cookiejar.Jar {
	jarFileDir := filepath.Join(utils.ConfigDir(), "leancloud")

	os.MkdirAll(jarFileDir, 0775)

	jar, err := cookiejar.New(&cookiejar.Options{
		Filename: filepath.Join(jarFileDir, "cookies"),
	})
	if err != nil {
		panic(err)
	}
	return jar
}

func getSystemLanguage() string {
	language, err := jibber_jabber.DetectLanguage()

	if err != nil {
		logp.Info("unsupported locale setting & set to default en_US.UTF-8: ", err)
		language = "en"
	}

	return language
}
<commit_msg>chore: replace Bearer with token<commit_after>package api

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/aisk/logp"
	"github.com/aisk/wizard"
	"github.com/cloudfoundry-attic/jibber_jabber"
	cookiejar "github.com/juju/persistent-cookiejar"
	"github.com/leancloud/lean-cli/api/regions"
	"github.com/leancloud/lean-cli/apps"
	"github.com/leancloud/lean-cli/utils"
	"github.com/leancloud/lean-cli/version"
	"github.com/levigross/grequests"
)

var dashboardBaseUrls = map[regions.Region]string{
	regions.ChinaNorth: "https://cn-n1-console-api.leancloud.cn",
	regions.USWest:     "https://us-w1-console-api.leancloud.app",
	regions.ChinaEast:  "https://cn-e1-console-api.leancloud.cn",
}

var (
	// Get2FACode is the function to get the user's two-factor-authentication code.
	// You can override it with your custom function.
	Get2FACode = func() (int, error) {
		result := new(string)
		wizard.Ask([]wizard.Question{
			{
				Content: "Please input 2-factor auth code",
				Input: &wizard.Input{
					Result: result,
					Hidden: false,
				},
			},
		})
		code, err := strconv.Atoi(*result)
		if err != nil {
			return 0, errors.New("2-factor auth code should be numerical")
		}
		return code, nil
	}
)

type Client struct {
	CookieJar   *cookiejar.Jar
	Region      regions.Region
	AppID       string
	AccessToken string
}

func NewClientByRegion(region regions.Region) *Client {
	if version.Distribution == "lean" {
		return &Client{
			CookieJar: newCookieJar(),
			Region:    region,
		}
	}

	return &Client{
		AccessToken: accessTokenCache[region],
		Region:      region,
	}
}

func NewClientByApp(appID string) *Client {
	if version.Distribution == "lean" {
		return &Client{
			CookieJar: newCookieJar(),
			AppID:     appID,
		}
	}

	region, err := apps.GetAppRegion(appID)
	if err != nil {
		return &Client{
			AppID: appID,
		}
	}

	return &Client{
		AccessToken: accessTokenCache[region],
		AppID:       appID,
	}
}

func (client *Client) GetBaseURL() string {
	envBaseURL := os.Getenv("LEANCLOUD_DASHBOARD")

	if envBaseURL != "" {
		return envBaseURL
	}

	region := client.Region

	if client.AppID != "" {
		var err error
		region, err = apps.GetAppRegion(client.AppID)

		if err != nil {
			panic(err) // This error should be catch at top level
		}
	}

	if url, ok := dashboardBaseUrls[region]; ok {
		return url
	}
	panic("invalid region")
}

func (client *Client) options() (*grequests.RequestOptions, error) {
	u, err := url.Parse(client.GetBaseURL())
	if err != nil {
		panic(err)
	}

	if version.Distribution == "lean" {
		cookies := client.CookieJar.Cookies(u)
		xsrf := ""
		for _, cookie := range cookies {
			if cookie.Name == "XSRF-TOKEN" {
				xsrf = cookie.Value
				break
			}
		}

		return &grequests.RequestOptions{
			Headers: map[string]string{
				"X-XSRF-TOKEN":    xsrf,
				"Accept-Language": getSystemLanguage(),
			},
			CookieJar:    client.CookieJar,
			UseCookieJar: true,
			UserAgent:    "LeanCloud-CLI/" + version.Version,
		}, nil
	}

	return &grequests.RequestOptions{
		Headers: map[string]string{
			"Accept-Language": getSystemLanguage(),
			"Authorization":   fmt.Sprint("token ", client.AccessToken),
		},
		UserAgent: "TDS-CLI/" + version.Version,
	}, nil
}

func doRequest(client *Client, method string, path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	if params != nil {
		options.JSON = params
	}
	var fn func(string, *grequests.RequestOptions) (*grequests.Response, error)
	switch method {
	case "GET":
		fn = grequests.Get
	case "POST":
		fn = grequests.Post
	case "PUT":
		fn = grequests.Put
	case "DELETE":
		fn = grequests.Delete
	case "PATCH":
		fn = grequests.Patch
	default:
		panic("invalid method: " + method)
	}
	resp, err := fn(client.GetBaseURL()+path, options)
	if err != nil {
		return nil, err
	}

	if version.Distribution == "lean" {
		resp, err = client.checkAndDo2FA(resp)
		if err != nil {
			return nil, err
		}
	}

	if !resp.Ok {
		if strings.HasPrefix(strings.TrimSpace(resp.Header.Get("Content-Type")), "application/json") {
			return nil, NewErrorFromResponse(resp)
		}
		return nil, fmt.Errorf("HTTP Error: %d, %s %s", resp.StatusCode, method, path)
	}

	if version.Distribution == "lean" {
		if err = client.CookieJar.Save(); err != nil {
			return nil, err
		}
	}

	return resp, nil
}

// check if the requests need two-factor-authentication and then do it.
func (client *Client) checkAndDo2FA(resp *grequests.Response) (*grequests.Response, error) {
	if resp.StatusCode != 401 || strings.Contains(resp.String(), "User doesn't sign in.") {
		// don't need 2FA
		return resp, nil
	}
	var result struct {
		Token string `json:"token"`
	}
	err := resp.JSON(&result)
	if err != nil {
		return nil, err
	}
	token := result.Token
	if token == "" {
		return resp, nil
	}
	code, err := Get2FACode()
	if err != nil {
		return nil, err
	}

	jar, err := cookiejar.New(&cookiejar.Options{
		Filename: filepath.Join(utils.ConfigDir(), "leancloud", "cookies"),
	})
	if err != nil {
		return nil, err
	}

	resp, err = grequests.Post(client.GetBaseURL()+"/1.1/do2fa", &grequests.RequestOptions{
		JSON: map[string]interface{}{
			"token": token,
			"code":  code,
		},
		CookieJar: jar,
	})
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		if strings.HasPrefix(strings.TrimSpace(resp.Header.Get("Content-Type")), "application/json") {
			return nil, NewErrorFromResponse(resp)
		}
		return nil, fmt.Errorf("HTTP Error: %d, %s %s", resp.StatusCode, "POST", "/do2fa")
	}

	if err := jar.Save(); err != nil {
		return nil, err
	}

	return resp, nil
}

func (client *Client) get(path string, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "GET", path, nil, options)
}

func (client *Client) post(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "POST", path, params, options)
}

func (client *Client) patch(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "PATCH", path, params, options)
}

func (client *Client) put(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "PUT", path, params, options)
}

func (client *Client) delete(path string, options *grequests.RequestOptions) (*grequests.Response, error) {
	return doRequest(client, "DELETE", path, nil, options)
}

func newCookieJar() *cookiejar.Jar {
	jarFileDir := filepath.Join(utils.ConfigDir(), "leancloud")

	os.MkdirAll(jarFileDir, 0775)

	jar, err := cookiejar.New(&cookiejar.Options{
		Filename: filepath.Join(jarFileDir, "cookies"),
	})
	if err != nil {
		panic(err)
	}
	return jar
}

func getSystemLanguage() string {
	language, err := jibber_jabber.DetectLanguage()

	if err != nil {
		logp.Info("unsupported locale setting & set to default en_US.UTF-8: ", err)
		language = "en"
	}

	return language
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package v4

import (
	"archive/zip"
	"net/http"
	"net/url"

	"github.com/juju/errgo"
	"gopkg.in/juju/charm.v3"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/juju/charmstore/internal/charmstore"
	"github.com/juju/charmstore/internal/mongodoc"
	"github.com/juju/charmstore/internal/router"
	"github.com/juju/charmstore/params"
)

type handler struct {
	*router.Router
	store *charmstore.Store
}

// New returns a new instance of the v4 API handler.
func New(store *charmstore.Store) http.Handler {
	h := &handler{
		store: store,
	}
	h.Router = router.New(&router.Handlers{
		Global: map[string]http.Handler{
			"stats/counter/":     router.HandleJSON(h.serveStatsCounter),
			"stats/":             router.NotFoundHandler(),
			"search":             http.HandlerFunc(h.serveSearch),
			"search/interesting": http.HandlerFunc(h.serveSearchInteresting),
			"debug":              http.HandlerFunc(h.serveDebug),
		},
		Id: map[string]router.IdHandler{
			"resources": h.serveResources,
			"archive":   h.serveArchive,
			"archive/":  h.serveArchiveFile,
			"expand-id": h.serveExpandId,
		},
		Meta: map[string]router.BulkIncludeHandler{
			"charm-metadata":      h.entityHandler(h.metaCharmMetadata, "charmmeta"),
			"bundle-metadata":     h.entityHandler(h.metaBundleMetadata, "bundledata"),
			"charm-config":        h.entityHandler(h.metaCharmConfig, "charmconfig"),
			"charm-actions":       h.entityHandler(h.metaCharmActions, "charmactions"),
			"archive-size":        h.entityHandler(h.metaArchiveSize, "size"),
			"manifest":            h.entityHandler(h.metaManifest, "blobname"),
			"archive-upload-time": h.entityHandler(h.metaArchiveUploadTime, "uploadtime"),

			// endpoints not yet implemented - use SingleIncludeHandler for the time being.
			"color":              router.SingleIncludeHandler(h.metaColor),
			"bundles-containing": router.SingleIncludeHandler(h.metaBundlesContaining),
			"extra-info":         router.SingleIncludeHandler(h.metaExtraInfo),
			"extra-info/":        router.SingleIncludeHandler(h.metaExtraInfoWithKey),
			"charm-related":      router.SingleIncludeHandler(h.metaCharmRelated),
		},
	}, h.resolveURL)
	return h
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.Router.ServeHTTP(w, req)
}

// ResolveURL resolves the series and revision of the given URL
// if either is unspecified by filling them out with information retrieved
// from the store.
func ResolveURL(store *charmstore.Store, url *charm.Reference) error {
	if url.Series != "" && url.Revision != -1 {
		return nil
	}
	urls, err := store.ExpandURL(url)
	if err != nil {
		return errgo.Notef(err, "cannot expand URL")
	}
	if len(urls) == 0 {
		return errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %q", url)
	}
	*url = *selectPreferredURL(urls)
	return nil
}

func (h *handler) resolveURL(url *charm.Reference) error {
	return ResolveURL(h.store, url)
}

type entityHandlerFunc func(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error)

// entityHandler returns a handler that calls f with a *mongodoc.Entity that
// contains at least the given fields.
func (h *handler) entityHandler(f entityHandlerFunc, fields ...string) router.BulkIncludeHandler {
	handle := func(doc interface{}, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
		edoc := doc.(*mongodoc.Entity)
		val, err := f(edoc, id, path, method, flags)
		return val, errgo.Mask(err, errgo.Any)
	}
	type entityHandlerKey struct{}
	return router.FieldIncludeHandler(
		entityHandlerKey{},
		h.entityQuery,
		fields,
		handle,
	)
}

func (h *handler) entityQuery(id *charm.Reference, selector map[string]int) (interface{}, error) {
	var val mongodoc.Entity
	err := h.store.DB.Entities().
		Find(bson.D{{"_id", id}}).
		Select(selector).
		One(&val)
	if err == mgo.ErrNotFound {
		return nil, params.ErrNotFound
	}
	if err != nil {
		return nil, errgo.Mask(err)
	}
	return &val, nil
}

var ltsReleases = map[string]bool{
	"lucid":   true,
	"precise": true,
	"trusty":  true,
}

func selectPreferredURL(urls []*charm.Reference) *charm.Reference {
	best := urls[0]
	for _, url := range urls {
		if preferredURL(url, best) {
			best = url
		}
	}
	return best
}

// preferredURL reports whether url0 is preferred over url1.
func preferredURL(url0, url1 *charm.Reference) bool {
	if url0.Series == url1.Series {
		return url0.Revision > url1.Revision
	}
	if url0.Series == "bundle" || url1.Series == "bundle" {
		// One of the URLs refers to a bundle. Choose
		// a charm by preference.
		return url0.Series != "bundle"
	}
	if ltsReleases[url0.Series] == ltsReleases[url1.Series] {
		return url0.Series > url1.Series
	}
	return ltsReleases[url0.Series]
}

var errNotImplemented = errgo.Newf("method not implemented")

// GET search[?text=text][&autocomplete=1][&filter=value…][&limit=limit][&include=meta]
// http://tinyurl.com/qzobc69
func (h *handler) serveSearch(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET search/interesting[?limit=limit][&include=meta]
// http://tinyurl.com/ntmdrg8
func (h *handler) serveSearchInteresting(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET /debug
// http://tinyurl.com/m63xhz8
func (h *handler) serveDebug(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// POST id/resources/name.stream
// http://tinyurl.com/pnmwvy4
//
// GET  id/resources/name.stream[-revision]/arch/filename
// http://tinyurl.com/pydbn3u
//
// PUT id/resources/[~user/]series/name.stream-revision/arch?sha256=hash
// http://tinyurl.com/k8l8kdg
func (h *handler) serveResources(charmId *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	return errNotImplemented
}

// GET id/expand-id
// https://docs.google.com/a/canonical.com/document/d/1TgRA7jW_mmXoKH3JiwBbtPvQu7WiM6XMrz1wSrhTMXw/edit#bookmark=id.4xdnvxphb2si
func (h *handler) serveExpandId(charmId *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	return errNotImplemented
}

func badRequestf(underlying error, f string, a ...interface{}) error {
	err := errgo.WithCausef(underlying, params.ErrBadRequest, f, a...)
	err.(*errgo.Err).SetLocation(1)
	return err
}

// GET id/meta/charm-metadata
// http://tinyurl.com/poeoulw
func (h *handler) metaCharmMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmMeta, nil
}

// GET id/meta/bundle-metadata
// http://tinyurl.com/ozshbtb
func (h *handler) metaBundleMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.BundleData, nil
}

// GET id/meta/manifest
// http://tinyurl.com/p3xdcto
func (h *handler) metaManifest(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	r, size, err := h.store.BlobStore.Open(entity.BlobName)
	if err != nil {
		return nil, errgo.Notef(err, "cannot open archive data for %s", id)
	}
	defer r.Close()
	zipReader, err := zip.NewReader(&readerAtSeeker{r}, size)
	if err != nil {
		return nil, errgo.Notef(err, "cannot read archive data for %s", id)
	}
	// Collect the files.
	manifest := make([]params.ManifestFile, 0, len(zipReader.File))
	for _, file := range zipReader.File {
		fileInfo := file.FileInfo()
		if fileInfo.IsDir() {
			continue
		}
		manifest = append(manifest, params.ManifestFile{
			Name: file.Name,
			Size: fileInfo.Size(),
		})
	}
	return manifest, nil
}

// GET id/meta/charm-actions
// http://tinyurl.com/kfd2h34
func (h *handler) metaCharmActions(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmActions, nil
}

// GET id/meta/charm-config
// http://tinyurl.com/oxxyujx
func (h *handler) metaCharmConfig(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmConfig, nil
}

// GET id/meta/color
// http://tinyurl.com/o2t3j4p
func (h *handler) metaColor(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-size
// http://tinyurl.com/m8b9geq
func (h *handler) metaArchiveSize(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveSizeResponse{
		Size: entity.Size,
	}, nil
}

// GET id/meta/stats/
// http://tinyurl.com/lvyp2l5
func (h *handler) metaStats(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/bundles-containing[?include=meta[&include=meta…]]
// http://tinyurl.com/oqc386r
func (h *handler) metaBundlesContaining(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info
// http://tinyurl.com/keos7wd
func (h *handler) metaExtraInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info/key
// http://tinyurl.com/polrbn7
func (h *handler) metaExtraInfoWithKey(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/charm-related[?include=meta[&include=meta…]]
// http://tinyurl.com/q7vdmzl
func (h *handler) metaCharmRelated(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-upload-time
// http://tinyurl.com/nmujuqk
func (h *handler) metaArchiveUploadTime(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveUploadTimeResponse{
		UploadTime: entity.UploadTime.UTC(),
	}, nil
}
<commit_msg>- added revision info api<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package v4

import (
	"archive/zip"
	"net/http"
	"net/url"

	"github.com/juju/errgo"
	"gopkg.in/juju/charm.v3"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/juju/charmstore/internal/charmstore"
	"github.com/juju/charmstore/internal/mongodoc"
	"github.com/juju/charmstore/internal/router"
	"github.com/juju/charmstore/params"
)

type handler struct {
	*router.Router
	store *charmstore.Store
}

// New returns a new instance of the v4 API handler.
func New(store *charmstore.Store) http.Handler {
	h := &handler{
		store: store,
	}
	h.Router = router.New(&router.Handlers{
		Global: map[string]http.Handler{
			"stats/counter/":     router.HandleJSON(h.serveStatsCounter),
			"stats/":             router.NotFoundHandler(),
			"search":             http.HandlerFunc(h.serveSearch),
			"search/interesting": http.HandlerFunc(h.serveSearchInteresting),
			"debug":              http.HandlerFunc(h.serveDebug),
		},
		Id: map[string]router.IdHandler{
			"resources": h.serveResources,
			"archive":   h.serveArchive,
			"archive/":  h.serveArchiveFile,
			"expand-id": h.serveExpandId,
		},
		Meta: map[string]router.BulkIncludeHandler{
			"charm-metadata":      h.entityHandler(h.metaCharmMetadata, "charmmeta"),
			"bundle-metadata":     h.entityHandler(h.metaBundleMetadata, "bundledata"),
			"charm-config":        h.entityHandler(h.metaCharmConfig, "charmconfig"),
			"charm-actions":       h.entityHandler(h.metaCharmActions, "charmactions"),
			"archive-size":        h.entityHandler(h.metaArchiveSize, "size"),
			"manifest":            h.entityHandler(h.metaManifest, "blobname"),
			"archive-upload-time": h.entityHandler(h.metaArchiveUploadTime, "uploadtime"),

			// endpoints not yet implemented - use SingleIncludeHandler for the time being.
			"color":              router.SingleIncludeHandler(h.metaColor),
			"bundles-containing": router.SingleIncludeHandler(h.metaBundlesContaining),
			"revision-info":      router.SingleIncludeHandler(h.metaRevisionInfo),
			"extra-info":         router.SingleIncludeHandler(h.metaExtraInfo),
			"extra-info/":        router.SingleIncludeHandler(h.metaExtraInfoWithKey),
			"charm-related":      router.SingleIncludeHandler(h.metaCharmRelated),
		},
	}, h.resolveURL)
	return h
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.Router.ServeHTTP(w, req)
}

// ResolveURL resolves the series and revision of the given URL
// if either is unspecified by filling them out with information retrieved
// from the store.
func ResolveURL(store *charmstore.Store, url *charm.Reference) error {
	if url.Series != "" && url.Revision != -1 {
		return nil
	}
	urls, err := store.ExpandURL(url)
	if err != nil {
		return errgo.Notef(err, "cannot expand URL")
	}
	if len(urls) == 0 {
		return errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %q", url)
	}
	*url = *selectPreferredURL(urls)
	return nil
}

func (h *handler) resolveURL(url *charm.Reference) error {
	return ResolveURL(h.store, url)
}

type entityHandlerFunc func(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error)

// entityHandler returns a handler that calls f with a *mongodoc.Entity that
// contains at least the given fields.
func (h *handler) entityHandler(f entityHandlerFunc, fields ...string) router.BulkIncludeHandler {
	handle := func(doc interface{}, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
		edoc := doc.(*mongodoc.Entity)
		val, err := f(edoc, id, path, method, flags)
		return val, errgo.Mask(err, errgo.Any)
	}
	type entityHandlerKey struct{}
	return router.FieldIncludeHandler(
		entityHandlerKey{},
		h.entityQuery,
		fields,
		handle,
	)
}

func (h *handler) entityQuery(id *charm.Reference, selector map[string]int) (interface{}, error) {
	var val mongodoc.Entity
	err := h.store.DB.Entities().
		Find(bson.D{{"_id", id}}).
		Select(selector).
		One(&val)
	if err == mgo.ErrNotFound {
		return nil, params.ErrNotFound
	}
	if err != nil {
		return nil, errgo.Mask(err)
	}
	return &val, nil
}

var ltsReleases = map[string]bool{
	"lucid":   true,
	"precise": true,
	"trusty":  true,
}

func selectPreferredURL(urls []*charm.Reference) *charm.Reference {
	best := urls[0]
	for _, url := range urls {
		if preferredURL(url, best) {
			best = url
		}
	}
	return best
}

// preferredURL reports whether url0 is preferred over url1.
func preferredURL(url0, url1 *charm.Reference) bool {
	if url0.Series == url1.Series {
		return url0.Revision > url1.Revision
	}
	if url0.Series == "bundle" || url1.Series == "bundle" {
		// One of the URLs refers to a bundle. Choose
		// a charm by preference.
		return url0.Series != "bundle"
	}
	if ltsReleases[url0.Series] == ltsReleases[url1.Series] {
		return url0.Series > url1.Series
	}
	return ltsReleases[url0.Series]
}

var errNotImplemented = errgo.Newf("method not implemented")

// GET search[?text=text][&autocomplete=1][&filter=value…][&limit=limit][&include=meta]
// http://tinyurl.com/qzobc69
func (h *handler) serveSearch(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET search/interesting[?limit=limit][&include=meta]
// http://tinyurl.com/ntmdrg8
func (h *handler) serveSearchInteresting(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET /debug
// http://tinyurl.com/m63xhz8
func (h *handler) serveDebug(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// POST id/resources/name.stream
// http://tinyurl.com/pnmwvy4
//
// GET  id/resources/name.stream[-revision]/arch/filename
// http://tinyurl.com/pydbn3u
//
// PUT id/resources/[~user/]series/name.stream-revision/arch?sha256=hash
// http://tinyurl.com/k8l8kdg
func (h *handler) serveResources(charmId *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	return errNotImplemented
}

// GET id/expand-id
// https://docs.google.com/a/canonical.com/document/d/1TgRA7jW_mmXoKH3JiwBbtPvQu7WiM6XMrz1wSrhTMXw/edit#bookmark=id.4xdnvxphb2si
func (h *handler) serveExpandId(charmId *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	return errNotImplemented
}

func badRequestf(underlying error, f string, a ...interface{}) error {
	err := errgo.WithCausef(underlying, params.ErrBadRequest, f, a...)
	err.(*errgo.Err).SetLocation(1)
	return err
}

// GET id/meta/charm-metadata
// http://tinyurl.com/poeoulw
func (h *handler) metaCharmMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmMeta, nil
}

// GET id/meta/bundle-metadata
// http://tinyurl.com/ozshbtb
func (h *handler) metaBundleMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.BundleData, nil
}

// GET id/meta/manifest
// http://tinyurl.com/p3xdcto
func (h *handler) metaManifest(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	r, size, err := h.store.BlobStore.Open(entity.BlobName)
	if err != nil {
		return nil, errgo.Notef(err, "cannot open archive data for %s", id)
	}
	defer r.Close()
	zipReader, err := zip.NewReader(&readerAtSeeker{r}, size)
	if err != nil {
		return nil, errgo.Notef(err, "cannot read archive data for %s", id)
	}
	// Collect the files.
	manifest := make([]params.ManifestFile, 0, len(zipReader.File))
	for _, file := range zipReader.File {
		fileInfo := file.FileInfo()
		if fileInfo.IsDir() {
			continue
		}
		manifest = append(manifest, params.ManifestFile{
			Name: file.Name,
			Size: fileInfo.Size(),
		})
	}
	return manifest, nil
}

// GET id/meta/charm-actions
// http://tinyurl.com/kfd2h34
func (h *handler) metaCharmActions(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmActions, nil
}

// GET id/meta/charm-config
// http://tinyurl.com/oxxyujx
func (h *handler) metaCharmConfig(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmConfig, nil
}

// GET id/meta/color
// http://tinyurl.com/o2t3j4p
func (h *handler) metaColor(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-size
// http://tinyurl.com/m8b9geq
func (h *handler) metaArchiveSize(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveSizeResponse{
		Size: entity.Size,
	}, nil
}

// GET id/meta/stats/
// http://tinyurl.com/lvyp2l5
func (h *handler) metaStats(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/bundles-containing[?include=meta[&include=meta…]]
// http://tinyurl.com/oqc386r
func (h *handler) metaBundlesContaining(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/revision-info
// http://tinyurl.com/q6xos7f
func (h *handler) metaRevisionInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info
// http://tinyurl.com/keos7wd
func (h *handler) metaExtraInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info/key
// http://tinyurl.com/polrbn7
func (h *handler) metaExtraInfoWithKey(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/charm-related[?include=meta[&include=meta…]]
// http://tinyurl.com/q7vdmzl
func (h *handler) metaCharmRelated(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-upload-time
// http://tinyurl.com/nmujuqk
func (h *handler) metaArchiveUploadTime(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveUploadTimeResponse{
		UploadTime: entity.UploadTime.UTC(),
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 syncutil

import (
	"flag"
	"sync"
)

var fCheckInvariants = flag.Bool("syncutil.check_invariants", false, "Crash when registered invariants are violated.")

// A sync.Locker that, when enabled, runs a check for registered invariants at
// times when invariants should hold. This can aid debugging subtle code by
// crashing early as soon as something unexpected happens.
//
// Must be created with NewInvariantMutex. See that function for more details.
//
// A typical use looks like this:
//
//     type myStruct struct {
//       mu syncutil.InvariantMutex
//
//       // INVARIANT: nextGeneration == currentGeneration + 1
//       currentGeneration int // GUARDED_BY(mu)
//       nextGeneration    int // GUARDED_BY(mu)
//     }
//
//     // The constructor function for myStruct sets up the mutex to
//     // call the checkInvariants method.
//     func newMyStruct() *myStruct {
//       s := &myStruct{
//         currentGeneration: 1,
//         nextGeneration:    2,
//       }
//
//       s.mu = syncutil.NewInvariantMutex(s.checkInvariants)
//       return s
//     }
//
//     func (s *myStruct) checkInvariants() {
//       if s.nextGeneration != s.currentGeneration+1 {
//         panic(
//           fmt.Sprintf("%v != %v + 1", s.nextGeneration, s.currentGeneration))
//       }
//     }
//
//     // When the flag is set, invariants will be checked at entry to and exit
//     // from this function.
//     func (s *myStruct) setGeneration(n int) {
//       s.mu.Lock()
//       defer s.mu.Unlock()
//
//       s.currentGeneration = n
//       s.nextGeneration = n + 1
//     }
//
type InvariantMutex struct {
	mu    sync.Mutex
	check func()
}

func (i *InvariantMutex) Lock() {
	i.mu.Lock()
	i.checkIfEnabled()
}

func (i *InvariantMutex) Unlock() {
	i.checkIfEnabled()
	i.mu.Unlock()
}

func (i *InvariantMutex) checkIfEnabled() {
	if *fCheckInvariants {
		i.check()
	}
}

// Create a lock which, when the flag --syncutil.check_invariants is set, will
// call the supplied function at moments when invariants protected by the lock
// should hold (e.g. just after acquiring the lock). The function should crash
// if an invariant is violated. It should not have side effects, as there are
// no guarantees that it will run.
//
// The invariants must hold at the time that NewInvariantMutex is called.
func NewInvariantMutex(check func()) InvariantMutex {
	if check == nil {
		panic("check must be non-nil.")
	}

	// Check now, if enabled.
	if *fCheckInvariants {
		check()
	}

	return InvariantMutex{
		check: check,
	}
}
<commit_msg>Killed --syncutil.check_invariants. Call EnableInvariantChecking.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 syncutil

import (
	"sync"
	"sync/atomic"
)

var gEnable uintptr

// Enable checking of invariants when locking and unlocking InvariantMutex.
func EnableInvariantChecking() {
	atomic.StoreUintptr(&gEnable, 1)
}

// A sync.Locker that, when enabled, runs a check for registered invariants at
// times when invariants should hold. This can aid debugging subtle code by
// crashing early as soon as something unexpected happens.
//
// Must be created with NewInvariantMutex. See that function for more details.
//
// A typical use looks like this:
//
//     type myStruct struct {
//       mu syncutil.InvariantMutex
//
//       // INVARIANT: nextGeneration == currentGeneration + 1
//       currentGeneration int // GUARDED_BY(mu)
//       nextGeneration    int // GUARDED_BY(mu)
//     }
//
//     // The constructor function for myStruct sets up the mutex to
//     // call the checkInvariants method.
//     func newMyStruct() *myStruct {
//       s := &myStruct{
//         currentGeneration: 1,
//         nextGeneration:    2,
//       }
//
//       s.mu = syncutil.NewInvariantMutex(s.checkInvariants)
//       return s
//     }
//
//     func (s *myStruct) checkInvariants() {
//       if s.nextGeneration != s.currentGeneration+1 {
//         panic(
//           fmt.Sprintf("%v != %v + 1", s.nextGeneration, s.currentGeneration))
//       }
//     }
//
//     // When EnableInvariantChecking has been called, invariants will be
//     // checked at entry to and exit from this function.
//     func (s *myStruct) setGeneration(n int) {
//       s.mu.Lock()
//       defer s.mu.Unlock()
//
//       s.currentGeneration = n
//       s.nextGeneration = n + 1
//     }
//
type InvariantMutex struct {
	mu    sync.Mutex
	check func()
}

func (i *InvariantMutex) Lock() {
	i.mu.Lock()
	i.checkIfEnabled()
}

func (i *InvariantMutex) Unlock() {
	i.checkIfEnabled()
	i.mu.Unlock()
}

func (i *InvariantMutex) checkIfEnabled() {
	if atomic.LoadUintptr(&gEnable) != 0 {
		i.check()
	}
}

// Create a lock which, when EnableInvariantChecking has been called, will call
// the supplied function at moments when invariants protected by the lock
// should hold (e.g. just after acquiring the lock). The function should crash
// if an invariant is violated. It should not have side effects, as there are
// no guarantees that it will run.
//
// The invariants must hold at the time that NewInvariantMutex is called.
func NewInvariantMutex(check func()) InvariantMutex {
	if check == nil {
		panic("check must be non-nil.")
	}

	// Check now, if enabled.
	if atomic.LoadUintptr(&gEnable) != 0 {
		check()
	}

	return InvariantMutex{
		check: check,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/kidoman/embd"
	"github.com/kidoman/embd/convertors/mcp3008"
	"github.com/kidoman/embd/host/generic"
	"time"
)

const (
	spi_speed = 1000000
	spi_delay = 0
	spi_bits  = 8
)

func main() {
	fmt.Println("Attempting to engage in Penguin SPI-ing")
	fmt.Printf("Speed: %d\n\tDelay: %d\n\tBPW: %d\n", spi_speed, spi_delay, spi_bits)

	if err := embd.InitSPI(); err != nil {
		fmt.Println("Having a wee panic")
		panic(err)
	}
	defer embd.CloseSPI()

	var channel byte = 0

	// Spi minor appears to be spi dev
	// Notes: chanel cf chip select
	// Todo, get spi1,2,and 2.1
	spiBus := embd.NewSPIBus(embd.SPIMode0, channel, spi_speed, spi_bits, spi_delay)
	defer spiBus.Close()

	adc := mcp3008.New(mcp3008.SingleMode, spiBus)

	for i := 0; i < 20; i++ {
		time.Sleep(1 * time.Second)

		for ch := 0; ch < 16; ch++ {
			val, err := adc.AnalogValueAt(0)
			if err != nil {
				panic(err)
			}
			fmt.Printf("analog channel %v value is: %v\n", ch, val)
		}
	}
}
<commit_msg>fixed spi example<commit_after>package main

import (
	"fmt"
	"github.com/kidoman/embd"
	"github.com/kidoman/embd/convertors/mcp3008"
	_ "github.com/kidoman/embd/host/bbb"
	"time"
)

const (
	spi_speed = 1000000
	spi_delay = 0
	spi_bits  = 8
)

func main() {
	fmt.Println("Attempting to engage in Penguin SPI-ing")
	fmt.Printf("Speed: %d\n\tDelay: %d\n\tBPW: %d\n", spi_speed, spi_delay, spi_bits)

	if err := embd.InitSPI(); err != nil {
		fmt.Println("Having a wee panic")
		panic(err)
	}
	defer embd.CloseSPI()

	var channel byte = 0

	// Spi minor appears to be spi dev
	// Notes: chanel cf chip select
	// Todo, get spi1,2,and 2.1
	spiBus := embd.NewSPIBus(embd.SPIMode0, channel, spi_speed, spi_bits, spi_delay)
	defer spiBus.Close()

	adc := mcp3008.New(mcp3008.SingleMode, spiBus)

	for i := 0; i < 20; i++ {
		time.Sleep(1 * time.Second)

		for ch := 0; ch < 16; ch++ {
			val, err := adc.AnalogValueAt(0)
			if err != nil {
				panic(err)
			}
			fmt.Printf("analog channel %v value is: %v\n", ch, val)
		}
	}
}
<|endoftext|>


<commit_before>package goformation

import (
	"encoding/json"
	"io/ioutil"
	"strings"

	"github.com/awslabs/goformation/cloudformation"
	"github.com/awslabs/goformation/intrinsics"
)

//go:generate generate/generate.sh

// Open and parse a AWS CloudFormation template from file.
// Works with either JSON or YAML formatted templates.
func Open(filename string) (*cloudformation.Template, error) {
	return OpenWithOptions(filename, nil)
}

// OpenWithOptions opens and parse a AWS CloudFormation template from file.
// Works with either JSON or YAML formatted templates.
// Parsing can be tweaked via the specified options.
func OpenWithOptions(filename string, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) {

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	if strings.HasSuffix(filename, ".yaml") || strings.HasSuffix(filename, ".yml") {
		return ParseYAMLWithOptions(data, options)
	}

	return ParseJSONWithOptions(data, options)

}

// ParseYAML an AWS CloudFormation template (expects a []byte of valid YAML)
func ParseYAML(data []byte) (*cloudformation.Template, error) {
	return ParseYAMLWithOptions(data, nil)
}

// ParseYAMLWithOptions an AWS CloudFormation template (expects a []byte of valid YAML)
// Parsing can be tweaked via the specified options.
func ParseYAMLWithOptions(data []byte, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) {
	// Process all AWS CloudFormation intrinsic functions (e.g. Fn::Join)
	intrinsified, err := intrinsics.ProcessYAML(data, options)
	if err != nil {
		return nil, err
	}

	return unmarshal(intrinsified)

}

// ParseJSON an AWS CloudFormation template (expects a []byte of valid JSON)
func ParseJSON(data []byte) (*cloudformation.Template, error) {
	return ParseJSONWithOptions(data, nil)
}

// ParseJSONWithOptions an AWS CloudFormation template (expects a []byte of valid JSON)
// Parsing can be tweaked via the specified options.
func ParseJSONWithOptions(data []byte, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) {

	// Process all AWS CloudFormation intrinsic functions (e.g. Fn::Join)
	intrinsified, err := intrinsics.ProcessJSON(data, options)
	if err != nil {
		return nil, err
	}

	return unmarshal(intrinsified)

}

func unmarshal(data []byte) (*cloudformation.Template, error) {

	template := &cloudformation.Template{}
	if err := json.Unmarshal(data, template); err != nil {
		return nil, err
	}

	return template, nil

}
<commit_msg>feat(parser): Default to parsing as YAML unless the filename ends in .json (#176)<commit_after>package goformation

import (
	"encoding/json"
	"io/ioutil"
	"strings"

	"github.com/awslabs/goformation/cloudformation"
	"github.com/awslabs/goformation/intrinsics"
)

//go:generate generate/generate.sh

// Open and parse a AWS CloudFormation template from file.
// Works with either JSON or YAML formatted templates.
func Open(filename string) (*cloudformation.Template, error) {
	return OpenWithOptions(filename, nil)
}

// OpenWithOptions opens and parse a AWS CloudFormation template from file.
// Works with either JSON or YAML formatted templates.
// Parsing can be tweaked via the specified options.
func OpenWithOptions(filename string, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) {

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	if strings.HasSuffix(filename, ".json") {
		// This is definitely JSON
		return ParseJSONWithOptions(data, options)
	}

	return ParseYAMLWithOptions(data, options)
}

// ParseYAML an AWS CloudFormation template (expects a []byte of valid YAML)
func ParseYAML(data []byte) (*cloudformation.Template, error) {
	return ParseYAMLWithOptions(data, nil)
}

// ParseYAMLWithOptions an AWS CloudFormation template (expects a []byte of valid YAML)
// Parsing can be tweaked via the specified options.
func ParseYAMLWithOptions(data []byte, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) {
	// Process all AWS CloudFormation intrinsic functions (e.g. Fn::Join)
	intrinsified, err := intrinsics.ProcessYAML(data, options)
	if err != nil {
		return nil, err
	}

	return unmarshal(intrinsified)

}

// ParseJSON an AWS CloudFormation template (expects a []byte of valid JSON)
func ParseJSON(data []byte) (*cloudformation.Template, error) {
	return ParseJSONWithOptions(data, nil)
}

// ParseJSONWithOptions an AWS CloudFormation template (expects a []byte of valid JSON)
// Parsing can be tweaked via the specified options.
func ParseJSONWithOptions(data []byte, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) {

	// Process all AWS CloudFormation intrinsic functions (e.g. Fn::Join)
	intrinsified, err := intrinsics.ProcessJSON(data, options)
	if err != nil {
		return nil, err
	}

	return unmarshal(intrinsified)

}

func unmarshal(data []byte) (*cloudformation.Template, error) {

	template := &cloudformation.Template{}
	if err := json.Unmarshal(data, template); err != nil {
		return nil, err
	}

	return template, nil

}
<|endoftext|>