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

/* Handles metadata requests. */

package main

import (
	"context"
	"errors"
	"sort"
	"strings"

	"gopkg.in/btrdb.v4"

	"github.com/SoftwareDefinedBuildings/mr-plotter/accounts"
	etcd "github.com/coreos/etcd/clientv3"
	"github.com/pborman/uuid"
)

var defaulttagset = map[string]struct{}{accounts.PublicTag: struct{}{}}

const btrdbSeparator = '/'
const plotterSeparator = '/'

func streamtoleafname(ctx context.Context, s *btrdb.Stream) (string, error) {
	tags, err := s.Tags(ctx)
	if err != nil {
		return "", err
	}

	name, ok := tags["name"]
	if ok {
		return name, nil
	}

	return "$" + s.UUID().String(), nil
}

func leafnametostream(ctx context.Context, bc *btrdb.BTrDB, collection string, leafname string) (*btrdb.Stream, error) {
	if len(leafname) != 0 && leafname[0] == '$' {
		uuidstr := leafname[1:]
		uu := uuid.Parse(uuidstr)
		s := bc.StreamFromUUID(uu)
		ex, err := s.Exists(ctx)
		if err != nil {
			return nil, err
		}
		if !ex {
			return nil, nil
		}
		return s, nil
	}
	matching, err := bc.LookupStreams(ctx, collection, false, map[string]*string{"name": &leafname}, nil)
	if err != nil {
		return nil, err
	}
	if len(matching) == 0 {
		return nil, nil
	}
	return matching[0], nil
}

func getprefixes(ctx context.Context, ec *etcd.Client, ls *LoginSession) (map[string]struct{}, error) {
	var tagset map[string]struct{}
	if ls == nil {
		tagset = defaulttagset
	} else {
		tagset = ls.Tags
	}

	prefixes := make(map[string]struct{})
	for tagname := range tagset {
		tagdef, err := accounts.RetrieveTagDef(ctx, ec, tagname)
		if err != nil {
			return nil, err
		}

		// Tags that are not defined do not grant any permissions
		if tagdef != nil {
			for pfx := range tagdef.PathPrefix {
				prefixes[pfx] = struct{}{}
			}
		}
	}

	return prefixes, nil
}

/* Returns a sorted slice of top level elements in the stream tree. */
func treetopPaths(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession) ([]string, error) {
	collections, err := bc.ListAllCollections(ctx)
	if err != nil {
		return nil, err
	}

	prefixes, err := getprefixes(ctx, ec, ls)
	if err != nil {
		return nil, err
	}

	toplevelset := make(map[string]struct{})
	for _, coll := range collections {
		var toplevel string

		/* Skip this collection if the user doesn't have permission. */
		haspermission := false
		for pfx := range prefixes {
			if strings.HasPrefix(coll, pfx) {
				haspermission = true
				break
			}
		}
		if !haspermission {
			continue
		}

		/* Extract the top-level element from the collection name. */
		sepindex := strings.Index(coll, string(btrdbSeparator))
		if sepindex == -1 {
			toplevel = coll
		} else {
			toplevel = coll[:sepindex]
		}
		toplevelset[toplevel] = struct{}{}
	}

	/* Transfer top-level elements into a slice. */
	treetop := make([]string, len(toplevelset))
	i := 0
	for toplevel := range toplevelset {
		treetop[i] = toplevel
		i++
	}

	sort.Strings(treetop)

	return treetop, nil
}

func treebranchPaths(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, toplevel string) ([]string, error) {
	collprefix := toplevel + string(btrdbSeparator)
	collections, err := bc.ListCollections(ctx, collprefix)
	if err != nil {
		return nil, err
	}

	prefixes, err := getprefixes(ctx, ec, ls)
	if err != nil {
		return nil, err
	}

	branches := make([]string, 0, len(collections))
	for _, coll := range collections {
		/* Skip this collection if the user doesn't have permission. */
		haspermission := false
		for pfx := range prefixes {
			if strings.HasPrefix(coll, pfx) {
				haspermission = true
				break
			}
		}
		if !haspermission {
			continue
		}

		dotidx := strings.IndexByte(coll, btrdbSeparator)
		if dotidx == -1 {
			dotidx = len(coll)
		}
		pathcoll := strings.Replace(coll[dotidx:], string(btrdbSeparator), string(plotterSeparator), -1)

		branches = append(branches, pathcoll)
	}

	sort.Strings(branches)

	return branches, nil
}

func treeleafPaths(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, branchpath string) ([]string, error) {
	coll := strings.Replace(branchpath, string(plotterSeparator), string(btrdbSeparator), -1)

	/* Get the streams in the collection. */
	streams, err := bc.LookupStreams(ctx, coll, false, nil, nil)
	if err != nil {
		return nil, err
	}

	leaves := make([]string, 0, len(streams))
	for _, stream := range streams {
		/* Formulate the path for this stream. */
		pathfin, err := streamtoleafname(ctx, stream)
		if err != nil {
			return nil, err
		}
		path := string(plotterSeparator) + pathfin

		/* Add path to return slice. */
		leaves = append(leaves, path)
	}

	sort.Strings(leaves)
	return leaves, nil
}

func treeleafMetadata(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, path string) (map[string]interface{}, error) {
	div := strings.LastIndex(path, string(plotterSeparator))
	if div == -1 {
		return nil, errors.New("Invalid path")
	}
	leafname := path[div+1:]
	collection := strings.Replace(path[:div], string(plotterSeparator), string(btrdbSeparator), -1)
	s, err := leafnametostream(ctx, bc, collection, leafname)
	if err != nil {
		return nil, err
	}
	if s == nil {
		return nil, errors.New("Stream does not exist")
	}

	uu := s.UUID()
	return uuidMetadata(ctx, ec, bc, ls, uu)
}

func uuidMetadata(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, uu uuid.UUID) (map[string]interface{}, error) {
	s := bc.StreamFromUUID(uu)
	ex, err := s.Exists(ctx)
	if err != nil {
		return nil, err
	}
	if !ex {
		return nil, errors.New("Stream does not exist")
	}
	if !hasPermission(ctx, ls, uu) {
		return nil, errors.New("Need permission")
	}

	ann, _, err := s.Annotations(ctx)
	if err != nil {
		return nil, err
	}

	collection, err := s.Collection(ctx)
	if err != nil {
		return nil, err
	}
	pathfin, err := streamtoleafname(ctx, s)
	if err != nil {
		return nil, err
	}

	var doc = map[string]interface{}{}
	for k, v := range ann {
		doc[k] = v
	}
	um, ok := doc["UnitofMeasure"]
	if !ok {
		doc["UnitofMeasure"] = "Unknown"
	}
	if _, ok := um.(string); !ok {
		doc["UnitofMeasure"] = "Unknown"
	}
	doc["Path"] = strings.Replace(collection, string(btrdbSeparator), string(plotterSeparator), -1) + string(plotterSeparator) + pathfin
	doc["uuid"] = uu.String()

	return doc, nil
}
<commit_msg>Avoid empty toplevel elements in stream tree<commit_after>/*
 * Copyright (C) 2017 Sam Kumar, Michael Andersen, and the University
 * of California, Berkeley.
 *
 * This file is part of Mr. Plotter (the Multi-Resolution Plotter).
 *
 * Mr. Plotter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Mr. Plotter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Mr. Plotter.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Handles metadata requests. */

package main

import (
	"context"
	"errors"
	"sort"
	"strings"

	"gopkg.in/btrdb.v4"

	"github.com/SoftwareDefinedBuildings/mr-plotter/accounts"
	etcd "github.com/coreos/etcd/clientv3"
	"github.com/pborman/uuid"
)

var defaulttagset = map[string]struct{}{accounts.PublicTag: struct{}{}}

const btrdbSeparator = '/'
const plotterSeparator = '/'

func streamtoleafname(ctx context.Context, s *btrdb.Stream) (string, error) {
	tags, err := s.Tags(ctx)
	if err != nil {
		return "", err
	}

	name, ok := tags["name"]
	if ok {
		return name, nil
	}

	return "$" + s.UUID().String(), nil
}

func leafnametostream(ctx context.Context, bc *btrdb.BTrDB, collection string, leafname string) (*btrdb.Stream, error) {
	if len(leafname) != 0 && leafname[0] == '$' {
		uuidstr := leafname[1:]
		uu := uuid.Parse(uuidstr)
		s := bc.StreamFromUUID(uu)
		ex, err := s.Exists(ctx)
		if err != nil {
			return nil, err
		}
		if !ex {
			return nil, nil
		}
		return s, nil
	}
	matching, err := bc.LookupStreams(ctx, collection, false, map[string]*string{"name": &leafname}, nil)
	if err != nil {
		return nil, err
	}
	if len(matching) == 0 {
		return nil, nil
	}
	return matching[0], nil
}

func getprefixes(ctx context.Context, ec *etcd.Client, ls *LoginSession) (map[string]struct{}, error) {
	var tagset map[string]struct{}
	if ls == nil {
		tagset = defaulttagset
	} else {
		tagset = ls.Tags
	}

	prefixes := make(map[string]struct{})
	for tagname := range tagset {
		tagdef, err := accounts.RetrieveTagDef(ctx, ec, tagname)
		if err != nil {
			return nil, err
		}

		// Tags that are not defined do not grant any permissions
		if tagdef != nil {
			for pfx := range tagdef.PathPrefix {
				prefixes[pfx] = struct{}{}
			}
		}
	}

	return prefixes, nil
}

/* Returns a sorted slice of top level elements in the stream tree. */
func treetopPaths(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession) ([]string, error) {
	collections, err := bc.ListAllCollections(ctx)
	if err != nil {
		return nil, err
	}

	prefixes, err := getprefixes(ctx, ec, ls)
	if err != nil {
		return nil, err
	}

	toplevelset := make(map[string]struct{})
	for _, coll := range collections {
		var toplevel string

		/* Skip this collection if the user doesn't have permission. */
		haspermission := false
		for pfx := range prefixes {
			if strings.HasPrefix(coll, pfx) {
				haspermission = true
				break
			}
		}
		if !haspermission {
			continue
		}

		/* Extract the top-level element from the collection name. */
		sepindex := strings.Index(coll, string(btrdbSeparator))
		/* If the element starts with the separator, then we would get an empty
		 * toplevel element. To avoid this, split on the next separator. */
		if sepindex == 0 {
			sepindex = strings.Index(coll[1:], string(btrdbSeparator))
			if sepindex != -1 {
				sepindex++
			}
		}
		if sepindex == -1 {
			toplevel = coll
		} else {
			toplevel = coll[:sepindex]
		}
		toplevelset[toplevel] = struct{}{}
	}

	/* Transfer top-level elements into a slice. */
	treetop := make([]string, len(toplevelset))
	i := 0
	for toplevel := range toplevelset {
		treetop[i] = toplevel
		i++
	}

	sort.Strings(treetop)

	return treetop, nil
}

func treebranchPaths(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, toplevel string) ([]string, error) {
	collprefix := toplevel + string(btrdbSeparator)
	collections, err := bc.ListCollections(ctx, collprefix)
	if err != nil {
		return nil, err
	}

	prefixes, err := getprefixes(ctx, ec, ls)
	if err != nil {
		return nil, err
	}

	branches := make([]string, 0, len(collections))
	for _, coll := range collections {
		/* Skip this collection if the user doesn't have permission. */
		haspermission := false
		for pfx := range prefixes {
			if strings.HasPrefix(coll, pfx) {
				haspermission = true
				break
			}
		}
		if !haspermission {
			continue
		}

		dotidx := strings.IndexByte(coll, btrdbSeparator)
		if dotidx == -1 {
			dotidx = len(coll)
		}
		pathcoll := strings.Replace(coll[dotidx:], string(btrdbSeparator), string(plotterSeparator), -1)

		branches = append(branches, pathcoll)
	}

	sort.Strings(branches)

	return branches, nil
}

func treeleafPaths(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, branchpath string) ([]string, error) {
	coll := strings.Replace(branchpath, string(plotterSeparator), string(btrdbSeparator), -1)

	/* Get the streams in the collection. */
	streams, err := bc.LookupStreams(ctx, coll, false, nil, nil)
	if err != nil {
		return nil, err
	}

	leaves := make([]string, 0, len(streams))
	for _, stream := range streams {
		/* Formulate the path for this stream. */
		pathfin, err := streamtoleafname(ctx, stream)
		if err != nil {
			return nil, err
		}
		path := string(plotterSeparator) + pathfin

		/* Add path to return slice. */
		leaves = append(leaves, path)
	}

	sort.Strings(leaves)
	return leaves, nil
}

func treeleafMetadata(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, path string) (map[string]interface{}, error) {
	div := strings.LastIndex(path, string(plotterSeparator))
	if div == -1 {
		return nil, errors.New("Invalid path")
	}
	leafname := path[div+1:]
	collection := strings.Replace(path[:div], string(plotterSeparator), string(btrdbSeparator), -1)
	s, err := leafnametostream(ctx, bc, collection, leafname)
	if err != nil {
		return nil, err
	}
	if s == nil {
		return nil, errors.New("Stream does not exist")
	}

	uu := s.UUID()
	return uuidMetadata(ctx, ec, bc, ls, uu)
}

func uuidMetadata(ctx context.Context, ec *etcd.Client, bc *btrdb.BTrDB, ls *LoginSession, uu uuid.UUID) (map[string]interface{}, error) {
	s := bc.StreamFromUUID(uu)
	ex, err := s.Exists(ctx)
	if err != nil {
		return nil, err
	}
	if !ex {
		return nil, errors.New("Stream does not exist")
	}
	if !hasPermission(ctx, ls, uu) {
		return nil, errors.New("Need permission")
	}

	ann, _, err := s.Annotations(ctx)
	if err != nil {
		return nil, err
	}

	collection, err := s.Collection(ctx)
	if err != nil {
		return nil, err
	}
	pathfin, err := streamtoleafname(ctx, s)
	if err != nil {
		return nil, err
	}

	var doc = map[string]interface{}{}
	for k, v := range ann {
		doc[k] = v
	}
	um, ok := doc["UnitofMeasure"]
	if !ok {
		doc["UnitofMeasure"] = "Unknown"
	}
	if _, ok := um.(string); !ok {
		doc["UnitofMeasure"] = "Unknown"
	}
	doc["Path"] = strings.Replace(collection, string(btrdbSeparator), string(plotterSeparator), -1) + string(plotterSeparator) + pathfin
	doc["uuid"] = uu.String()

	return doc, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"strings"

	"github.com/gorilla/mux"
)

var catalogue []Sock

var dev bool
var port string
var tagList []string = []string{"blue", "brown", "green", "smelly", "large", "short", "magic", "toes", "formal"}

func main() {

	flag.BoolVar(&dev, "dev", false, "Run in development mode")
	flag.StringVar(&port, "port", "8081", "Port on which to run")
	flag.Parse()

	var file string
	if dev {
		file = "./socks.json"
	} else {
		file = "/config/socks.json"
	}
	loadCatalogue(file)

	router := mux.NewRouter().StrictSlash(false)
	router.HandleFunc("/catalogue", catalogueHandler)
	router.HandleFunc("/catalogue/size", sizeHandler)
	router.HandleFunc("/catalogue/{catId}", itemHandler)
	router.HandleFunc("/tags", tagHandler)
	router.PathPrefix("/images/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./images/"))))
	fmt.Printf("Catalogue service running on port %s\n", port)
	http.ListenAndServe(":" + port, router)
}

func catalogueHandler(w http.ResponseWriter, r *http.Request) {

	page := r.FormValue("page")
	size := r.FormValue("size")
	sortField := r.FormValue("sort")
	tagField := r.FormValue("tags")

	pageCount := 1
	if len(page) > 0 {
		pageCount, _ = strconv.Atoi(page)
	}
	perPage := 10
	if len(size) > 0 {
		perPage, _ = strconv.Atoi(size)
	}
	sortOn := "id"
	if len(sortField) > 0 {
		sortOn = strings.ToLower(sortField)
	}

	var sorted []Sock = localizeUrl(filter(catalogue, tagField),  "http://" + r.Host)

	switch sortOn {
		case "id":
			sort.Sort(IdSorter(sorted))
		case "name":
			sort.Sort(NameSorter(sorted))
		case "description":
			sort.Sort(DescriptionSorter(sorted))
		case "price":
			sort.Sort(PriceSorter(sorted))
		case "count":
			sort.Sort(CountSorter(sorted))
		case "tag":
			sort.Sort(TagSorter(sorted))
	}
	end := (pageCount * perPage)
	if (end > len(sorted)) {
	    end = len(sorted)
	}
	start := end - perPage
	if (start < 0) {
	    start = 0
	}
	var data []byte
	var err error

	fmt.Printf("Fetching items from %d to %d. Sorted by %s\n", start, end, sortOn)

	data, err = json.Marshal(sorted[start:end])
	if err != nil {
		panic(err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func itemHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
    catId := vars["catId"]

	for _, sock := range catalogue {
		if sock.Id == catId {
			sock.ImageURL = "http://" + r.Host + sock.ImageURL
			data, _ := json.Marshal(sock)
			w.Header().Set("Content-Type", "application/json")
			w.Write(data)
			return
		}
	}
	w.WriteHeader(404)
}

func tagHandler(w http.ResponseWriter, r *http.Request) {
	body, err := json.Marshal(tagList)
	if err != nil { panic(err) }
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func sizeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte("{\"size\":" + strconv.Itoa(len(catalogue)) + "}"))
}
func loadCatalogue(file string) {
	f, err := ioutil.ReadFile(file)
    if err != nil {
        panic(err)
    }

    json.Unmarshal(f, &catalogue)
    fmt.Printf("Loaded %d items into catalogue.\n", len(catalogue))
}

func localizeUrl(socks []Sock, host string) []Sock {
	var r []Sock
	for _, s := range socks {
		s.ImageURL = host + s.ImageURL
		r = append(r, s)
	}
	return r
}

func filter(socks []Sock, tagString string) []Sock {
	if len(tagString) < 1 {
		return socks[:]
	}
	var r []Sock
	tags := strings.Split(tagString, ",")
	for _, s := range socks {
        var count []string
        for _, m := range tags {
            TAGLABEL:
            for _, t := range s.Tags {
                fmt.Printf("sock tag: %s, current tag:%s\n", t, m)
				if t == m && !contains(count, t) {
                    fmt.Printf("appending: %s\n", m)
                    count = append(count, t)
                    break TAGLABEL
				}
			}
		}
        fmt.Printf("len(count) %d == len(tags) %d\n", len(count), len(tags))
		if (len(count) == len(tags)) {
			r = append(r, s)
		}
	}
	return r
}

func contains(s []string, e string) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

type Sock struct {
	Id string `json:id`
	Name string `json:"name"`
	Description string `json:"description"`
	ImageURL string `json:"imageUrl"`
	Price int `json:price`
	Count int `json:"count"`
	Tags []string `json:"tag"`
}

type IdSorter []Sock

func (a IdSorter) Len() int           { return len(a) }
func (a IdSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a IdSorter) Less(i, j int) bool { return a[i].Id < a[j].Id }

type NameSorter []Sock

func (a NameSorter) Len() int           { return len(a) }
func (a NameSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }

type DescriptionSorter []Sock

func (a DescriptionSorter) Len() int           { return len(a) }
func (a DescriptionSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a DescriptionSorter) Less(i, j int) bool { return a[i].Description < a[j].Description }

type PriceSorter []Sock

func (a PriceSorter) Len() int           { return len(a) }
func (a PriceSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a PriceSorter) Less(i, j int) bool { return a[i].Price < a[j].Price }

type CountSorter []Sock

func (a CountSorter) Len() int           { return len(a) }
func (a CountSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a CountSorter) Less(i, j int) bool { return a[i].Count < a[j].Count }

type TagSorter []Sock

func (a TagSorter) Len() int           { return len(a) }
func (a TagSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a TagSorter) Less(i, j int) bool { return len(a[i].Tags) < len(a[j].Tags) }<commit_msg>Remove debug messages.<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"strings"

	"github.com/gorilla/mux"
)

var catalogue []Sock

var dev bool
var port string
var tagList []string = []string{"blue", "brown", "green", "smelly", "large", "short", "magic", "toes", "formal"}

func main() {

	flag.BoolVar(&dev, "dev", false, "Run in development mode")
	flag.StringVar(&port, "port", "8081", "Port on which to run")
	flag.Parse()

	var file string
	if dev {
		file = "./socks.json"
	} else {
		file = "/config/socks.json"
	}
	loadCatalogue(file)

	router := mux.NewRouter().StrictSlash(false)
	router.HandleFunc("/catalogue", catalogueHandler)
	router.HandleFunc("/catalogue/size", sizeHandler)
	router.HandleFunc("/catalogue/{catId}", itemHandler)
	router.HandleFunc("/tags", tagHandler)
	router.PathPrefix("/images/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./images/"))))
	fmt.Printf("Catalogue service running on port %s\n", port)
	http.ListenAndServe(":" + port, router)
}

func catalogueHandler(w http.ResponseWriter, r *http.Request) {

	page := r.FormValue("page")
	size := r.FormValue("size")
	sortField := r.FormValue("sort")
	tagField := r.FormValue("tags")

	pageCount := 1
	if len(page) > 0 {
		pageCount, _ = strconv.Atoi(page)
	}
	perPage := 10
	if len(size) > 0 {
		perPage, _ = strconv.Atoi(size)
	}
	sortOn := "id"
	if len(sortField) > 0 {
		sortOn = strings.ToLower(sortField)
	}

	var sorted []Sock = localizeUrl(filter(catalogue, tagField),  "http://" + r.Host)

	switch sortOn {
		case "id":
			sort.Sort(IdSorter(sorted))
		case "name":
			sort.Sort(NameSorter(sorted))
		case "description":
			sort.Sort(DescriptionSorter(sorted))
		case "price":
			sort.Sort(PriceSorter(sorted))
		case "count":
			sort.Sort(CountSorter(sorted))
		case "tag":
			sort.Sort(TagSorter(sorted))
	}
	end := (pageCount * perPage)
	if (end > len(sorted)) {
	    end = len(sorted)
	}
	start := end - perPage
	if (start < 0) {
	    start = 0
	}
	var data []byte
	var err error

	fmt.Printf("Fetching items from %d to %d. Sorted by %s\n", start, end, sortOn)

	data, err = json.Marshal(sorted[start:end])
	if err != nil {
		panic(err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func itemHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
    catId := vars["catId"]

	for _, sock := range catalogue {
		if sock.Id == catId {
			sock.ImageURL = "http://" + r.Host + sock.ImageURL
			data, _ := json.Marshal(sock)
			w.Header().Set("Content-Type", "application/json")
			w.Write(data)
			return
		}
	}
	w.WriteHeader(404)
}

func tagHandler(w http.ResponseWriter, r *http.Request) {
	body, err := json.Marshal(tagList)
	if err != nil { panic(err) }
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func sizeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte("{\"size\":" + strconv.Itoa(len(catalogue)) + "}"))
}
func loadCatalogue(file string) {
	f, err := ioutil.ReadFile(file)
    if err != nil {
        panic(err)
    }

    json.Unmarshal(f, &catalogue)
    fmt.Printf("Loaded %d items into catalogue.\n", len(catalogue))
}

func localizeUrl(socks []Sock, host string) []Sock {
	var r []Sock
	for _, s := range socks {
		s.ImageURL = host + s.ImageURL
		r = append(r, s)
	}
	return r
}

func filter(socks []Sock, tagString string) []Sock {
	if len(tagString) < 1 {
		return socks[:]
	}
	var r []Sock
	tags := strings.Split(tagString, ",")
	for _, s := range socks {
        var count []string
        for _, m := range tags {
            TAGLABEL:
            for _, t := range s.Tags {
				if t == m && !contains(count, t) {
                    count = append(count, t)
                    break TAGLABEL
				}
			}
		}
		if (len(count) == len(tags)) {
			r = append(r, s)
		}
	}
	return r
}

func contains(s []string, e string) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

type Sock struct {
	Id string `json:id`
	Name string `json:"name"`
	Description string `json:"description"`
	ImageURL string `json:"imageUrl"`
	Price int `json:price`
	Count int `json:"count"`
	Tags []string `json:"tag"`
}

type IdSorter []Sock

func (a IdSorter) Len() int           { return len(a) }
func (a IdSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a IdSorter) Less(i, j int) bool { return a[i].Id < a[j].Id }

type NameSorter []Sock

func (a NameSorter) Len() int           { return len(a) }
func (a NameSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }

type DescriptionSorter []Sock

func (a DescriptionSorter) Len() int           { return len(a) }
func (a DescriptionSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a DescriptionSorter) Less(i, j int) bool { return a[i].Description < a[j].Description }

type PriceSorter []Sock

func (a PriceSorter) Len() int           { return len(a) }
func (a PriceSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a PriceSorter) Less(i, j int) bool { return a[i].Price < a[j].Price }

type CountSorter []Sock

func (a CountSorter) Len() int           { return len(a) }
func (a CountSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a CountSorter) Less(i, j int) bool { return a[i].Count < a[j].Count }

type TagSorter []Sock

func (a TagSorter) Len() int           { return len(a) }
func (a TagSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a TagSorter) Less(i, j int) bool { return len(a[i].Tags) < len(a[j].Tags) }<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"testing"
	"time"
)

// Should redirect from HTTP to HTTPS without hitting origin.
func TestProtocolRedirect(t *testing.T) {
	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Request should not have made it to origin")
	})

	sourceUrl := fmt.Sprintf("http://%s/foo/bar", *edgeHost)
	destUrl := fmt.Sprintf("https://%s/foo/bar", *edgeHost)

	req, _ := http.NewRequest("GET", sourceUrl, nil)
	resp, err := client.RoundTrip(req)

	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != 301 {
		t.Errorf("Status code expected 301, got %d", resp.StatusCode)
	}
	if d := resp.Header.Get("Location"); d != destUrl {
		t.Errorf("Location header expected %s, got %s", destUrl, d)
	}
}

// Should return 403 for PURGE requests from IPs not in the whitelist. We
// assume that this is not running from a whitelisted address.
func TestRestrictPurgeRequests(t *testing.T) {
	const expectedStatusCode = 403

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Request should not have made it to origin")
	})

	url := fmt.Sprintf("https://%s/", *edgeHost)
	req, _ := http.NewRequest("PURGE", url, nil)

	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != expectedStatusCode {
		t.Errorf("Incorrect status code. Expected %d, got %d", expectedStatusCode, resp.StatusCode)
	}
}

// Should create an X-Forwarded-For header containing the client's IP.
func TestHeaderCreateXFF(t *testing.T) {
	t.Error("Not implemented")
}

// Should append client's IP to existing X-Forwarded-For header.
func TestHeaderAppendXFF(t *testing.T) {
	t.Error("Not implemented")
}

// Should create a True-Client-IP header containing the client's IP
// address, discarding the value provided in the original request.
func TestHeaderUnspoofableClientIP(t *testing.T) {
	const headerName = "True-Client-IP"
	const sentHeaderVal = "203.0.113.99"
	var sentHeaderIP = net.ParseIP(sentHeaderVal)
	var receivedHeaderVal string

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		receivedHeaderVal = r.Header.Get(headerName)
	})

	url := fmt.Sprintf("https://%s/%s", *edgeHost, NewUUID())
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set(headerName, sentHeaderVal)

	_, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	receivedHeaderIP := net.ParseIP(receivedHeaderVal)
	if receivedHeaderIP == nil {
		t.Fatalf("Origin received %q header with non-IP value %q", headerName, receivedHeaderVal)
	}
	if receivedHeaderIP.Equal(sentHeaderIP) {
		t.Errorf("Origin received %q header with unmodified value %q", headerName, receivedHeaderIP)
	}
}

// Should not modify Host header from original request.
func TestHeaderHostUnmodified(t *testing.T) {
	t.Error("Not implemented")
}

// Should serve a known static error page if cannot serve a page
// from origin, stale or any mirror.
// NB: ideally this should be a page that we control that has a mechanism
//     to alert us that it has been served.
func TestErrorPageIsServedWhenNoBackendAvailable(t *testing.T) {
	t.Error("Not implemented")
}

// ---------------------------------------------------------
// Test that useful common cache-related parameters are sent to the
// client by this CDN provider.

// Should set an Age header itself rather than passing the Age header from origin.
func TestAgeHeaderIsSetByProviderNotOrigin(t *testing.T) {
	const originAgeInSeconds = 100000
	const secondsToWaitBetweenRequests = 2
	requestReceivedCount := 0
	uuid := NewUUID()

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if requestReceivedCount == 0 {
			w.Header().Set("Age", fmt.Sprintf("%d", originAgeInSeconds))
			w.Write([]byte("cacheable request"))
		} else {
			t.Error("Unexpected subsequent request received at Origin")
		}
	})

	url := fmt.Sprintf("https://%s/?cache-lock=%s", *edgeHost, uuid)
	req, _ := http.NewRequest("GET", url, nil)

	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	// wait a little bit. Edge should update the Age header, we know Origin will not
	time.Sleep(time.Duration(secondsToWaitBetweenRequests) * time.Second)

	resp, err = client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	edgeAgeHeader := resp.Header.Get("Age")
	edgeAgeInSeconds, convErr := strconv.Atoi(edgeAgeHeader)
	if convErr != nil {
		t.Fatal(convErr)
	}
	if edgeAgeInSeconds == secondsToWaitBetweenRequests {
		// Edge has rewritten Age based on how long it has been since it
		// cached the page.
		return
	} else if edgeAgeInSeconds == originAgeInSeconds+secondsToWaitBetweenRequests {
		t.Error("Edge is adding to Age header from Origin. This is not as expected.")
	} else if edgeAgeInSeconds == 0 {
		t.Error("Edge is reporting Age to be '0'. This implies Egde is not preserving Age between cache nodes")
	}

	t.Errorf(
		"Age header from Edge is not as expected. Got %q, expected '%d'",
		edgeAgeHeader,
		secondsToWaitBetweenRequests,
	)

}

// Should set an X-Cache header containing HIT/MISS from 'origin, itself'
func TestXCacheHeaderContainsHitMissFromBothProviderAndOrigin(t *testing.T) {
	t.Error("Not implemented")
}

// Should set an X-Served-By header giving information on the (Fastly) node and location served from.
func TestXServedByHeaderContainsFastlyNodeIdAndLocation(t *testing.T) {

	expectedFastlyXServedByRegexp := regexp.MustCompile("^cache-[a-z0-9]+-[A-Z]{3}$")

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {})

	sourceUrl := fmt.Sprintf("https://%s/", *edgeHost)

	req, _ := http.NewRequest("GET", sourceUrl, nil)
	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	actualHeader := resp.Header.Get("X-Served-By")
	if actualHeader == "" {
		t.Error("X-Served-By header has not been set by Edge")
	}

	if expectedFastlyXServedByRegexp.FindString(actualHeader) != actualHeader {
		t.Errorf("X-Served-By is not as expected: got %q", actualHeader)
	}

}

// Should set an X-Cache-Hits header containing hit count for this object,
// from the Edge AND the Origin, assuming Origin sets one.
// This is in the format "{origin-hit-count}, {edge-hit-count}"
func TestXCacheHitsContainsProviderHitCountForThisObject(t *testing.T) {

	const originXCacheHits = "53"

	var (
		xCacheHits         string
		expectedXCacheHits string
	)

	uuid := NewUUID()
	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" && r.URL.Path == fmt.Sprintf("/%s", uuid) {
			w.Header().Set("X-Cache-Hits", originXCacheHits)
		}
	})

	sourceUrl := fmt.Sprintf("https://%s/%s", *edgeHost, uuid)

	// Get first request, will come from origin. Edge Hit Count 0
	req, _ := http.NewRequest("GET", sourceUrl, nil)
	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	xCacheHits = resp.Header.Get("X-Cache-Hits")
	expectedXCacheHits = fmt.Sprintf("%s, 0", originXCacheHits)
	if xCacheHits != expectedXCacheHits {
		t.Errorf(
			"X-Cache-Hits on initial hit is wrong: expected %q, got %q",
			expectedXCacheHits,
			xCacheHits,
		)
	}

	// Get request again. Should come from Edge now, hit count 1
	resp, err = client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	xCacheHits = resp.Header.Get("X-Cache-Hits")
	expectedXCacheHits = fmt.Sprintf("%s, 1", originXCacheHits)
	if xCacheHits != expectedXCacheHits {
		t.Errorf(
			"X-Cache-Hits on second hit is wrong: expected %q, got %q",
			expectedXCacheHits,
			xCacheHits,
		)
	}

}
<commit_msg>Add missed requestReceivedCount++<commit_after>package main

import (
	"fmt"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"testing"
	"time"
)

// Should redirect from HTTP to HTTPS without hitting origin.
func TestProtocolRedirect(t *testing.T) {
	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Request should not have made it to origin")
	})

	sourceUrl := fmt.Sprintf("http://%s/foo/bar", *edgeHost)
	destUrl := fmt.Sprintf("https://%s/foo/bar", *edgeHost)

	req, _ := http.NewRequest("GET", sourceUrl, nil)
	resp, err := client.RoundTrip(req)

	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != 301 {
		t.Errorf("Status code expected 301, got %d", resp.StatusCode)
	}
	if d := resp.Header.Get("Location"); d != destUrl {
		t.Errorf("Location header expected %s, got %s", destUrl, d)
	}
}

// Should return 403 for PURGE requests from IPs not in the whitelist. We
// assume that this is not running from a whitelisted address.
func TestRestrictPurgeRequests(t *testing.T) {
	const expectedStatusCode = 403

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Request should not have made it to origin")
	})

	url := fmt.Sprintf("https://%s/", *edgeHost)
	req, _ := http.NewRequest("PURGE", url, nil)

	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != expectedStatusCode {
		t.Errorf("Incorrect status code. Expected %d, got %d", expectedStatusCode, resp.StatusCode)
	}
}

// Should create an X-Forwarded-For header containing the client's IP.
func TestHeaderCreateXFF(t *testing.T) {
	t.Error("Not implemented")
}

// Should append client's IP to existing X-Forwarded-For header.
func TestHeaderAppendXFF(t *testing.T) {
	t.Error("Not implemented")
}

// Should create a True-Client-IP header containing the client's IP
// address, discarding the value provided in the original request.
func TestHeaderUnspoofableClientIP(t *testing.T) {
	const headerName = "True-Client-IP"
	const sentHeaderVal = "203.0.113.99"
	var sentHeaderIP = net.ParseIP(sentHeaderVal)
	var receivedHeaderVal string

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		receivedHeaderVal = r.Header.Get(headerName)
	})

	url := fmt.Sprintf("https://%s/%s", *edgeHost, NewUUID())
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set(headerName, sentHeaderVal)

	_, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	receivedHeaderIP := net.ParseIP(receivedHeaderVal)
	if receivedHeaderIP == nil {
		t.Fatalf("Origin received %q header with non-IP value %q", headerName, receivedHeaderVal)
	}
	if receivedHeaderIP.Equal(sentHeaderIP) {
		t.Errorf("Origin received %q header with unmodified value %q", headerName, receivedHeaderIP)
	}
}

// Should not modify Host header from original request.
func TestHeaderHostUnmodified(t *testing.T) {
	t.Error("Not implemented")
}

// Should serve a known static error page if cannot serve a page
// from origin, stale or any mirror.
// NB: ideally this should be a page that we control that has a mechanism
//     to alert us that it has been served.
func TestErrorPageIsServedWhenNoBackendAvailable(t *testing.T) {
	t.Error("Not implemented")
}

// ---------------------------------------------------------
// Test that useful common cache-related parameters are sent to the
// client by this CDN provider.

// Should set an Age header itself rather than passing the Age header from origin.
func TestAgeHeaderIsSetByProviderNotOrigin(t *testing.T) {
	const originAgeInSeconds = 100000
	const secondsToWaitBetweenRequests = 2
	requestReceivedCount := 0
	uuid := NewUUID()

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if requestReceivedCount == 0 {
			w.Header().Set("Age", fmt.Sprintf("%d", originAgeInSeconds))
			w.Write([]byte("cacheable request"))
		} else {
			t.Error("Unexpected subsequent request received at Origin")
		}
		requestReceivedCount++
	})

	url := fmt.Sprintf("https://%s/?cache-lock=%s", *edgeHost, uuid)
	req, _ := http.NewRequest("GET", url, nil)

	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	// wait a little bit. Edge should update the Age header, we know Origin will not
	time.Sleep(time.Duration(secondsToWaitBetweenRequests) * time.Second)

	resp, err = client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	edgeAgeHeader := resp.Header.Get("Age")
	edgeAgeInSeconds, convErr := strconv.Atoi(edgeAgeHeader)
	if convErr != nil {
		t.Fatal(convErr)
	}
	if edgeAgeInSeconds == secondsToWaitBetweenRequests {
		// Edge has rewritten Age based on how long it has been since it
		// cached the page.
		return
	} else if edgeAgeInSeconds == originAgeInSeconds+secondsToWaitBetweenRequests {
		t.Error("Edge is adding to Age header from Origin. This is not as expected.")
	} else if edgeAgeInSeconds == 0 {
		t.Error("Edge is reporting Age to be '0'. This implies Egde is not preserving Age between cache nodes")
	}

	t.Errorf(
		"Age header from Edge is not as expected. Got %q, expected '%d'",
		edgeAgeHeader,
		secondsToWaitBetweenRequests,
	)

}

// Should set an X-Cache header containing HIT/MISS from 'origin, itself'
func TestXCacheHeaderContainsHitMissFromBothProviderAndOrigin(t *testing.T) {
	t.Error("Not implemented")
}

// Should set an X-Served-By header giving information on the (Fastly) node and location served from.
func TestXServedByHeaderContainsFastlyNodeIdAndLocation(t *testing.T) {

	expectedFastlyXServedByRegexp := regexp.MustCompile("^cache-[a-z0-9]+-[A-Z]{3}$")

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {})

	sourceUrl := fmt.Sprintf("https://%s/", *edgeHost)

	req, _ := http.NewRequest("GET", sourceUrl, nil)
	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	actualHeader := resp.Header.Get("X-Served-By")
	if actualHeader == "" {
		t.Error("X-Served-By header has not been set by Edge")
	}

	if expectedFastlyXServedByRegexp.FindString(actualHeader) != actualHeader {
		t.Errorf("X-Served-By is not as expected: got %q", actualHeader)
	}

}

// Should set an X-Cache-Hits header containing hit count for this object,
// from the Edge AND the Origin, assuming Origin sets one.
// This is in the format "{origin-hit-count}, {edge-hit-count}"
func TestXCacheHitsContainsProviderHitCountForThisObject(t *testing.T) {

	const originXCacheHits = "53"

	var (
		xCacheHits         string
		expectedXCacheHits string
	)

	uuid := NewUUID()
	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" && r.URL.Path == fmt.Sprintf("/%s", uuid) {
			w.Header().Set("X-Cache-Hits", originXCacheHits)
		}
	})

	sourceUrl := fmt.Sprintf("https://%s/%s", *edgeHost, uuid)

	// Get first request, will come from origin. Edge Hit Count 0
	req, _ := http.NewRequest("GET", sourceUrl, nil)
	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	xCacheHits = resp.Header.Get("X-Cache-Hits")
	expectedXCacheHits = fmt.Sprintf("%s, 0", originXCacheHits)
	if xCacheHits != expectedXCacheHits {
		t.Errorf(
			"X-Cache-Hits on initial hit is wrong: expected %q, got %q",
			expectedXCacheHits,
			xCacheHits,
		)
	}

	// Get request again. Should come from Edge now, hit count 1
	resp, err = client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	xCacheHits = resp.Header.Get("X-Cache-Hits")
	expectedXCacheHits = fmt.Sprintf("%s, 1", originXCacheHits)
	if xCacheHits != expectedXCacheHits {
		t.Errorf(
			"X-Cache-Hits on second hit is wrong: expected %q, got %q",
			expectedXCacheHits,
			xCacheHits,
		)
	}

}
<|endoftext|>


<commit_before>package spdystream

import (
	"fmt"
	"io"
	"net"
	"sync"
	"testing"
)

const (
	LISTEN_ADDRESS = "127.0.0.1:7777"
)

func configureServer() (io.Closer, *sync.WaitGroup) {
	authenticated = true
	wg := &sync.WaitGroup{}
	server, serverErr := runServer(LISTEN_ADDRESS, wg)

	if serverErr != nil {
		panic(serverErr)
	}

	return server, wg
}

func BenchmarkDial10000(b *testing.B) {
	server, wg := configureServer()

	defer func() {
		server.Close()
		wg.Wait()
	}()

	for i := 0; i < b.N; i++ {
		conn, dialErr := net.Dial("tcp", LISTEN_ADDRESS)
		if dialErr != nil {
			panic(fmt.Sprintf("Error dialing server: %s", dialErr))
		}
		conn.Close()
	}
}

func BenchmarkDialWithSPDYStream10000(b *testing.B) {
	server, wg := configureServer()

	defer func() {
		server.Close()
		wg.Wait()
	}()

	for i := 0; i < b.N; i++ {
		conn, dialErr := net.Dial("tcp", LISTEN_ADDRESS)
		if dialErr != nil {
			b.Fatalf("Error dialing server: %s", dialErr)
		}

		spdyConn, spdyErr := NewConnection(conn, false)
		if spdyErr != nil {
			b.Fatalf("Error creating spdy connection: %s", spdyErr)
		}
		go spdyConn.Serve(NoOpStreamHandler)

		closeErr := spdyConn.Close()
		if closeErr != nil {
			b.Fatalf("Error closing connection: %s, closeErr")
		}
	}
}
<commit_msg>three more benchmarks that send data over the pipe<commit_after>package spdystream

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"
	"testing"
)

const (
	LISTEN_ADDRESS = "127.0.0.1:7777"
)

func configureServer() (io.Closer, *sync.WaitGroup) {
	authenticated = true
	wg := &sync.WaitGroup{}
	server, serverErr := runServer(LISTEN_ADDRESS, wg)

	if serverErr != nil {
		panic(serverErr)
	}

	return server, wg
}

func BenchmarkDial10000(b *testing.B) {
	server, wg := configureServer()

	defer func() {
		server.Close()
		wg.Wait()
	}()

	for i := 0; i < b.N; i++ {
		conn, dialErr := net.Dial("tcp", LISTEN_ADDRESS)
		if dialErr != nil {
			panic(fmt.Sprintf("Error dialing server: %s", dialErr))
		}
		conn.Close()
	}
}

func BenchmarkDialWithSPDYStream10000(b *testing.B) {
	server, wg := configureServer()

	defer func() {
		server.Close()
		wg.Wait()
	}()

	for i := 0; i < b.N; i++ {
		conn, dialErr := net.Dial("tcp", LISTEN_ADDRESS)
		if dialErr != nil {
			b.Fatalf("Error dialing server: %s", dialErr)
		}

		spdyConn, spdyErr := NewConnection(conn, false)
		if spdyErr != nil {
			b.Fatalf("Error creating spdy connection: %s", spdyErr)
		}
		go spdyConn.Serve(NoOpStreamHandler)

		closeErr := spdyConn.Close()
		if closeErr != nil {
			b.Fatalf("Error closing connection: %s, closeErr")
		}
	}
}

func benchmarkStreamWithDataAndSize(size uint64, b *testing.B) {
	server, wg := configureServer()

	defer func() {
		server.Close()
		wg.Wait()
	}()

	for i := 0; i < b.N; i++ {
		conn, dialErr := net.Dial("tcp", LISTEN_ADDRESS)
		if dialErr != nil {
			b.Fatalf("Error dialing server: %s", dialErr)
		}

		spdyConn, spdyErr := NewConnection(conn, false)
		if spdyErr != nil {
			b.Fatalf("Error creating spdy connection: %s", spdyErr)
		}

		go spdyConn.Serve(MirrorStreamHandler)

		stream, err := spdyConn.CreateStream(http.Header{}, nil, false)

		writer := make([]byte, size)

		stream.Write(writer)

		if err != nil {
			panic(err)
		}

		reader := make([]byte, size)
		stream.Read(reader)

		stream.Close()

		closeErr := spdyConn.Close()
		if closeErr != nil {
			b.Fatalf("Error closing connection: %s, closeErr")
		}
	}
}

func BenchmarkStreamWith1Byte10000(b *testing.B)     { benchmarkStreamWithDataAndSize(1, b) }
func BenchmarkStreamWith1KiloByte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1024, b) }
func BenchmarkStreamWith1Megabyte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1024*1024, b) }
<|endoftext|>


<commit_before>// +build freebsd

package mem

import (
	"context"
	"errors"
	"unsafe"

	"golang.org/x/sys/unix"
)

func VirtualMemory() (*VirtualMemoryStat, error) {
	return VirtualMemoryWithContext(context.Background())
}

func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
	pageSize, err := unix.SysctlUint32("vm.stats.vm.v_page_size")
	if err != nil {
		return nil, err
	}
	physmem, err := unix.SysctlUint64("hw.physmem")
	if err != nil {
		return nil, err
	}
	free, err := unix.SysctlUint32("vm.stats.vm.v_free_count")
	if err != nil {
		return nil, err
	}
	active, err := unix.SysctlUint32("vm.stats.vm.v_active_count")
	if err != nil {
		return nil, err
	}
	inactive, err := unix.SysctlUint32("vm.stats.vm.v_inactive_count")
	if err != nil {
		return nil, err
	}
	buffers, err := unix.SysctlUint64("vfs.bufspace")
	if err != nil {
		return nil, err
	}
	wired, err := unix.SysctlUint32("vm.stats.vm.v_wire_count")
	if err != nil {
		return nil, err
	}
	var cached, laundry uint32
	osreldate, _ := unix.SysctlUint32("kern.osreldate")
	if osreldate < 1102000 {
		cached, err = unix.SysctlUint32("vm.stats.vm.v_cache_count")
		if err != nil {
			return nil, err
		}
	} else {
		laundry, err = unix.SysctlUint32("vm.stats.vm.v_laundry_count")
		if err != nil {
			return nil, err
		}
	}

	p := uint64(pageSize)
	ret := &VirtualMemoryStat{
		Total:    uint64(physmem),
		Free:     uint64(free) * p,
		Active:   uint64(active) * p,
		Inactive: uint64(inactive) * p,
		Cached:   uint64(cached) * p,
		Buffers:  uint64(buffers),
		Wired:    uint64(wired) * p,
		Laundry:  uint64(laundry) * p,
	}

	ret.Available = ret.Inactive + ret.Cached + ret.Free + ret.Laundry
	ret.Used = ret.Total - ret.Available
	ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0

	return ret, nil
}

// Return swapinfo
func SwapMemory() (*SwapMemoryStat, error) {
	return SwapMemoryWithContext(context.Background())
}

// Constants from vm/vm_param.h
// nolint: golint
const (
	XSWDEV_VERSION = 1
)

// Types from vm/vm_param.h
type xswdev struct {
	Version uint32 // Version is the version
	Dev     uint32 // Dev is the device identifier
	Flags   int32  // Flags is the swap flags applied to the device
	NBlks   int32  // NBlks is the total number of blocks
	Used    int32  // Used is the number of blocks used
}

func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
	// FreeBSD can have multiple swap devices so we total them up
	i, err := unix.SysctlUint32("vm.nswapdev")
	if err != nil {
		return nil, err
	}

	if i == 0 {
		return nil, errors.New("no swap devices found")
	}

	c := int(i)

	i, err = unix.SysctlUint32("vm.stats.vm.v_page_size")
	if err != nil {
		return nil, err
	}
	pageSize := uint64(i)

	var buf []byte
	s := &SwapMemoryStat{}
	for n := 0; n < c; n++ {
		buf, err = unix.SysctlRaw("vm.swap_info", n)
		if err != nil {
			return nil, err
		}

		xsw := (*xswdev)(unsafe.Pointer(&buf[0]))
		if xsw.Version != XSWDEV_VERSION {
			return nil, errors.New("xswdev version mismatch")
		}
		s.Total += uint64(xsw.NBlks)
		s.Used += uint64(xsw.Used)
	}

	if s.Total != 0 {
		s.UsedPercent = float64(s.Used) / float64(s.Total) * 100
	}
	s.Total *= pageSize
	s.Used *= pageSize
	s.Free = s.Total - s.Used

	return s, nil
}
<commit_msg>[mem]freebsd: update xswdev_version to adapt FreeBSD 12.<commit_after>// +build freebsd

package mem

import (
	"context"
	"errors"
	"unsafe"

	"golang.org/x/sys/unix"
)

func VirtualMemory() (*VirtualMemoryStat, error) {
	return VirtualMemoryWithContext(context.Background())
}

func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
	pageSize, err := unix.SysctlUint32("vm.stats.vm.v_page_size")
	if err != nil {
		return nil, err
	}
	physmem, err := unix.SysctlUint64("hw.physmem")
	if err != nil {
		return nil, err
	}
	free, err := unix.SysctlUint32("vm.stats.vm.v_free_count")
	if err != nil {
		return nil, err
	}
	active, err := unix.SysctlUint32("vm.stats.vm.v_active_count")
	if err != nil {
		return nil, err
	}
	inactive, err := unix.SysctlUint32("vm.stats.vm.v_inactive_count")
	if err != nil {
		return nil, err
	}
	buffers, err := unix.SysctlUint64("vfs.bufspace")
	if err != nil {
		return nil, err
	}
	wired, err := unix.SysctlUint32("vm.stats.vm.v_wire_count")
	if err != nil {
		return nil, err
	}
	var cached, laundry uint32
	osreldate, _ := unix.SysctlUint32("kern.osreldate")
	if osreldate < 1102000 {
		cached, err = unix.SysctlUint32("vm.stats.vm.v_cache_count")
		if err != nil {
			return nil, err
		}
	} else {
		laundry, err = unix.SysctlUint32("vm.stats.vm.v_laundry_count")
		if err != nil {
			return nil, err
		}
	}

	p := uint64(pageSize)
	ret := &VirtualMemoryStat{
		Total:    uint64(physmem),
		Free:     uint64(free) * p,
		Active:   uint64(active) * p,
		Inactive: uint64(inactive) * p,
		Cached:   uint64(cached) * p,
		Buffers:  uint64(buffers),
		Wired:    uint64(wired) * p,
		Laundry:  uint64(laundry) * p,
	}

	ret.Available = ret.Inactive + ret.Cached + ret.Free + ret.Laundry
	ret.Used = ret.Total - ret.Available
	ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0

	return ret, nil
}

// Return swapinfo
func SwapMemory() (*SwapMemoryStat, error) {
	return SwapMemoryWithContext(context.Background())
}

// Constants from vm/vm_param.h
// nolint: golint
const (
	XSWDEV_VERSION11 = 1
	XSWDEV_VERSION   = 2
)

// Types from vm/vm_param.h
type xswdev struct {
	Version uint32 // Version is the version
	Dev     uint64 // Dev is the device identifier
	Flags   int32  // Flags is the swap flags applied to the device
	NBlks   int32  // NBlks is the total number of blocks
	Used    int32  // Used is the number of blocks used
}

// xswdev11 is a compatiblity for under FreeBSD 11
// sys/vm/swap_pager.c
type xswdev11 struct {
	Version uint32 // Version is the version
	Dev     uint32 // Dev is the device identifier
	Flags   int32  // Flags is the swap flags applied to the device
	NBlks   int32  // NBlks is the total number of blocks
	Used    int32  // Used is the number of blocks used
}

func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
	// FreeBSD can have multiple swap devices so we total them up
	i, err := unix.SysctlUint32("vm.nswapdev")
	if err != nil {
		return nil, err
	}

	if i == 0 {
		return nil, errors.New("no swap devices found")
	}

	c := int(i)

	i, err = unix.SysctlUint32("vm.stats.vm.v_page_size")
	if err != nil {
		return nil, err
	}
	pageSize := uint64(i)

	var buf []byte
	s := &SwapMemoryStat{}
	for n := 0; n < c; n++ {
		buf, err = unix.SysctlRaw("vm.swap_info", n)
		if err != nil {
			return nil, err
		}

		// first, try to parse with version 2
		xsw := (*xswdev)(unsafe.Pointer(&buf[0]))
		if xsw.Version == XSWDEV_VERSION11 {
			// this is version 1, so try to parse again
			xsw := (*xswdev11)(unsafe.Pointer(&buf[0]))
			if xsw.Version != XSWDEV_VERSION11 {
				return nil, errors.New("xswdev version mismatch(11)")
			}
			s.Total += uint64(xsw.NBlks)
			s.Used += uint64(xsw.Used)
		} else if xsw.Version != XSWDEV_VERSION {
			return nil, errors.New("xswdev version mismatch")
		} else {
			s.Total += uint64(xsw.NBlks)
			s.Used += uint64(xsw.Used)
		}

	}

	if s.Total != 0 {
		s.UsedPercent = float64(s.Used) / float64(s.Total) * 100
	}
	s.Total *= pageSize
	s.Used *= pageSize
	s.Free = s.Total - s.Used

	return s, nil
}
<|endoftext|>


<commit_before>package postman

import (
    "fmt"
    "time"
    "strings"
    "net/http"
    "io/ioutil"
)

type PostRequest struct {
    Url             string
    Request         string
    Ret             chan string
}

type Postman struct {
    RequestChan         chan *PostRequest
}

func NewPostman()  *Postman {
    pm := &Postman{make(chan *PostRequest, 128)}
    go pm.loop()
    return pm
}

func (this *Postman) loop() {
    for {
        select {
            case req := <-this.RequestChan:
               this.post(req)
        }
    }
}

func (this *Postman) post(req *PostRequest) {
    b := strings.NewReader(req.Request)
    fmt.Println("post-", req.Request, len(req.Request))
    http_req, err := http.Post(req.Url, "application/json", b)
    if err == nil {
        if body, e := ioutil.ReadAll(http_req.Body); e == nil {
            req.Ret <- string(body)
        } else {
            fmt.Println("http post ret err:", e)
        }
        http_req.Body.Close()
    } else {
        close(req.Ret)
    }
}

func (this *Postman) Post(url, s string) string {
    req := &PostRequest{url, s, make(chan string, 1)}
    //this.post(req)
    select {
        case this.RequestChan <- req:

        case <-time.After(60 * time.Second):
            close(req.Ret)
    }
    return <-req.Ret
}

<commit_msg>rm print<commit_after>package postman

import (
    "fmt"
    "time"
    "strings"
    "net/http"
    "io/ioutil"
)

type PostRequest struct {
    Url             string
    Request         string
    Ret             chan string
}

type Postman struct {
    RequestChan         chan *PostRequest
}

func NewPostman()  *Postman {
    pm := &Postman{make(chan *PostRequest, 128)}
    go pm.loop()
    return pm
}

func (this *Postman) loop() {
    for {
        select {
            case req := <-this.RequestChan:
               this.post(req)
        }
    }
}

func (this *Postman) post(req *PostRequest) {
    b := strings.NewReader(req.Request)
    //fmt.Println("post-", req.Request, len(req.Request))
    http_req, err := http.Post(req.Url, "application/json", b)
    if err == nil {
        if body, e := ioutil.ReadAll(http_req.Body); e == nil {
            req.Ret <- string(body)
        } else {
            fmt.Println("http post ret err:", e)
        }
        http_req.Body.Close()
    } else {
        close(req.Ret)
    }
}

func (this *Postman) Post(url, s string) string {
    req := &PostRequest{url, s, make(chan string, 1)}
    //this.post(req)
    select {
        case this.RequestChan <- req:

        case <-time.After(60 * time.Second):
            close(req.Ret)
    }
    return <-req.Ret
}

<|endoftext|>


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

// +build go1.3

package lxdclient

import (
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/juju/errors"
	"github.com/lxc/lxd/shared"
)

const (
	tempPrefix = "juju-lxd-"

	pemBlockTypeCert = "CERTIFICATE"
	pemBlockTypeKey  = "RSA PRIVATE KEY"
)

// Cert holds the information for a single certificate a client
// may use to connect to a remote server.
type Cert struct {
	// CertPEM is the PEM-encoded x.509 cert.
	CertPEM []byte

	// KeyPEM is the PEM-encoded x.509 private key.
	KeyPEM []byte
}

// NewCert creates a new Cert for the given cert and key.
func NewCert(certPEM, keyPEM []byte) *Cert {
	return &Cert{
		CertPEM: certPEM,
		KeyPEM:  keyPEM,
	}
}

// Validate ensures that the cert is valid.
func (cert Cert) Validate() error {
	if len(cert.CertPEM) == 0 {
		return errors.NotValidf("missing cert PEM")
	}
	if len(cert.KeyPEM) == 0 {
		return errors.NotValidf("missing key PEM")
	}

	// TODO(ericsnow) Ensure cert and key are valid?

	return nil
}

// WriteCertPEM writes the cert's x.509 PEM data to the given writer.
func (cert Cert) WriteCertPEM(out io.Writer) error {
	if _, err := out.Write(cert.CertPEM); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// WriteKeytPEM writes the key's x.509 PEM data to the given writer.
func (cert Cert) WriteKeyPEM(out io.Writer) error {
	if _, err := out.Write(cert.KeyPEM); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// Fingerprint returns the cert's LXD fingerprint.
func (cert Cert) Fingerprint() (string, error) {
	// See: https://github.com/lxc/lxd/blob/master/lxd/certificates.go
	x509Cert, err := cert.X509()
	if err != nil {
		return "", errors.Trace(err)
	}
	data := sha256.Sum256(x509Cert.Raw)
	return fmt.Sprintf("%x", data), nil
}

// X509 returns the x.509 certificate.
func (cert Cert) X509() (*x509.Certificate, error) {
	block, _ := pem.Decode(cert.CertPEM)
	if block == nil {
		return nil, errors.Errorf("invalid cert PEM (%d bytes)", len(cert.CertPEM))
	}

	x509Cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return x509Cert, nil
}

func genCertAndKey() ([]byte, []byte, error) {
	// See GenCert() in:
	//  https://github.com/lxc/lxd/blob/master/shared/cert.go
	// TODO(ericsnow) Split up GenCert so it is more re-usable.
	tempdir, err := ioutil.TempDir("", tempPrefix)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}
	defer os.RemoveAll(tempdir)
	certFile := filepath.Join(tempdir, configCertFile)
	keyFile := filepath.Join(tempdir, configKeyFile)
	if err := shared.GenCert(certFile, keyFile); err != nil {
		return nil, nil, errors.Trace(err)
	}

	certPEM, err := ioutil.ReadFile(certFile)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}

	keyPEM, err := ioutil.ReadFile(keyFile)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}

	return certPEM, keyPEM, nil
}
<commit_msg>Add Certificate.Name.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// +build go1.3

package lxdclient

import (
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/juju/errors"
	"github.com/lxc/lxd/shared"
)

const (
	tempPrefix = "juju-lxd-"

	pemBlockTypeCert = "CERTIFICATE"
	pemBlockTypeKey  = "RSA PRIVATE KEY"
)

// Cert holds the information for a single certificate a client
// may use to connect to a remote server.
type Cert struct {
	// Name is the name that LXD will use for the cert.
	Name string

	// CertPEM is the PEM-encoded x.509 cert.
	CertPEM []byte

	// KeyPEM is the PEM-encoded x.509 private key.
	KeyPEM []byte
}

// NewCert creates a new Cert for the given cert and key.
func NewCert(certPEM, keyPEM []byte) *Cert {
	return &Cert{
		CertPEM: certPEM,
		KeyPEM:  keyPEM,
	}
}

// Validate ensures that the cert is valid.
func (cert Cert) Validate() error {
	if len(cert.CertPEM) == 0 {
		return errors.NotValidf("missing cert PEM")
	}
	if len(cert.KeyPEM) == 0 {
		return errors.NotValidf("missing key PEM")
	}

	// TODO(ericsnow) Ensure cert and key are valid?

	return nil
}

// WriteCertPEM writes the cert's x.509 PEM data to the given writer.
func (cert Cert) WriteCertPEM(out io.Writer) error {
	if _, err := out.Write(cert.CertPEM); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// WriteKeytPEM writes the key's x.509 PEM data to the given writer.
func (cert Cert) WriteKeyPEM(out io.Writer) error {
	if _, err := out.Write(cert.KeyPEM); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// Fingerprint returns the cert's LXD fingerprint.
func (cert Cert) Fingerprint() (string, error) {
	// See: https://github.com/lxc/lxd/blob/master/lxd/certificates.go
	x509Cert, err := cert.X509()
	if err != nil {
		return "", errors.Trace(err)
	}
	data := sha256.Sum256(x509Cert.Raw)
	return fmt.Sprintf("%x", data), nil
}

// X509 returns the x.509 certificate.
func (cert Cert) X509() (*x509.Certificate, error) {
	block, _ := pem.Decode(cert.CertPEM)
	if block == nil {
		return nil, errors.Errorf("invalid cert PEM (%d bytes)", len(cert.CertPEM))
	}

	x509Cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return x509Cert, nil
}

func genCertAndKey() ([]byte, []byte, error) {
	// See GenCert() in:
	//  https://github.com/lxc/lxd/blob/master/shared/cert.go
	// TODO(ericsnow) Split up GenCert so it is more re-usable.
	tempdir, err := ioutil.TempDir("", tempPrefix)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}
	defer os.RemoveAll(tempdir)
	certFile := filepath.Join(tempdir, configCertFile)
	keyFile := filepath.Join(tempdir, configKeyFile)
	if err := shared.GenCert(certFile, keyFile); err != nil {
		return nil, nil, errors.Trace(err)
	}

	certPEM, err := ioutil.ReadFile(certFile)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}

	keyPEM, err := ioutil.ReadFile(keyFile)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}

	return certPEM, keyPEM, nil
}
<|endoftext|>


<commit_before>// When using FileStore, we will have yamls files to store the data.

// FIXME:
// Since I am not planning to use this in production (I prefer etcd as store)
// I would NOT be using any optimizations to cache the process file
// contents (ie, result of yaml parser) Please give me a patch (one way to clear
// the cache is by sending some signals to reread the file and update the cache.
// Also we should make sure we reread only the specific files in question).

package filestore

import (
	"errors"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"path/filepath"
	"rangeops"
	"strings"
)

const _config = "cluster.yaml"

type FileStore struct {
	StorePath  string // directory where yamls are stored
	MaxDepth   int    // TODO: we could use this for reverse lookup to limit nested look down
	FastLookup bool   // fast return, will return the first match
}

// check whether the StorePath Exists, etc
func ConnectFileStore(dir string, depth int, fast bool) (f *FileStore, err error) {
	// removing trailing path seperator
	if len(dir) > 0 && os.IsPathSeparator(dir[len(dir)-1]) {
		dir = dir[:len(dir)-1]
	}
	var fi os.FileInfo
	// check whether the dir exists
	fi, err = os.Stat(dir)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Path [%s] is not a FileStore directory (ERROR: %s)", dir, err))
	}
	// check whether it is a dir
	if !fi.IsDir() {
		return nil, errors.New(fmt.Sprintf("Path [%s] is not a directory", dir))
	}
	f = &FileStore{StorePath: dir, MaxDepth: depth, FastLookup: fast}
	return f, nil
}

// nothing to cleanup
func (f *FileStore) DisconnectFileStore() {
	return
}

////////////////////
// LOOKUP CLUSTER //
////////////////////

// LOGIC
// -----
// * for the first element in cluster create results array
//   * check whether the cluster is a leaf node
//   * if yes, call KeyLookup, with key == NODES
//   * if not, call listClusters
// * if more elements are there, repeat the above
//   but do an ArraytoSet with the results array
func (f *FileStore) ClusterLookup(cluster *[]string) (*[]string, error) {
	// store the resuls
	var results = make([]string, 0)
	// for each cluster, do a lookup
	// (this will only happen only for nested lookups eg, %%..)
	for _, elem := range *cluster {
		// handle RANGE separately
		if elem == "RANGE" {
			elem = "."
		}
		var err error
		isLeaf, err := f.checkIsLeafNode(elem)
		if err != nil {
			return &[]string{}, err
		}
		// if it is a leaf node, we need do a KeyLookup (NODES)
		if isLeaf {
			// by default, lookup for NODES
			result, err := f.KeyLookup(&[]string{elem}, "NODES")
			if err != nil {
				return &[]string{}, err
			}
			results = append(results, *result...)
		} else { // we need to return the children
			result, err := f.listClusters(elem)
			if err != nil {
				return &[]string{}, err
			}
			results = append(results, result...)
		}

	}

	return &results, nil
}

func (f *FileStore) KeyLookup(cluster *[]string, key string) (*[]string, error) {
	// store the resuls
	var results = make([]string, 0)
	// this will most likely be single element arrays
	// can't think of a reason otherwise
	for _, elem := range *cluster {
		// 1. read the config
		// 2. do a key lookup
		// 3. append the result
		content, err := f.readClusterConfig(elem)
		if err != nil {
			return &[]string{}, errors.New(fmt.Sprintf("KeyLookup for [%s] Failed (Error: %s)", elem, err))
		}
		result, err := yamlKeyLookup(content, key)
		if err != nil {
			return &[]string{}, errors.New(fmt.Sprintf("KeyLookup for [%s] Failed (Error: %s)", elem, err))
		}
		results = append(results, *result...)
	}

	return &results, nil
}

////////////////////
// LOOKUP REVERSE //
////////////////////

// same as KeyReverseLookupAttr where attr == NODES
func (f *FileStore) KeyReverseLookup(key string) (*[]string, error) {
	return f.KeyReverseLookupAttr(key, "NODES")
}

// same as KeyReverseLookupAttr where attr == NODES and hint == ""
func (f *FileStore) KeyReverseLookupAttr(key string, attr string) (*[]string, error) {
	return f.KeyReverseLookupHint(key, attr, "")
}

// given a key, it will serach for the cluster where the attr has that key
// hint is to limit the scope of search
func (f *FileStore) KeyReverseLookupHint(key string, attr string, hint string) (*[]string, error) {
	var clusters *[]string
	var err error
	var results = make([]string, 0)
	var seen bool

	clusters, err = f.getAllLeafNodes(hint)
	if err != nil {
		return &results, nil
	}

	for _, elem := range *clusters {
		// get the cluster config
		content, err := f.readClusterConfig(elem)
		if err != nil {
			return &[]string{}, errors.New(fmt.Sprintf("KeyLookup for [%s] Failed (Error: %s)", elem, err))
		}
		// look whether the attr exists
		result, err := yamlKeyLookup(content, attr)
		if err != nil {
			continue // looks like we didn't find the key
		} else {
			for _, i := range *result {
				if i == key {
					results = append(results, elem)
					seen = true
					break
				}
			}
			if seen && f.FastLookup {
				return &results, nil
			}
		}
	}

	return &results, nil
}

////////////////////////
// Internal Functions //
////////////////////////

// given a cluster name, it will convert to cluster
// in the file system
func (f *FileStore) clusterToPath(cluster string) string {
	return fmt.Sprintf("%s/%s", f.StorePath, strings.Replace(cluster, "-", "/", -1))
}

// reads the child clusters of this cluster.
// returns only those nodes for which this cluster is parent
func (f *FileStore) listClusters(cluster string) ([]string, error) {
	var dir = f.clusterToPath(cluster)
	var children = make([]string, 0)
	files, err := ioutil.ReadDir(dir)
	// if there is an error, return err
	if err != nil {
		return []string{}, err
	}
	for _, f := range files {
		if f.IsDir() {
			// the cluster string to append the children with, when we get a
			// top level query %RANGE, cluster = "."
			if cluster != "." {
				children = append(children, fmt.Sprintf("%s-%s", cluster, f.Name()))
			} else {
				children = append(children, f.Name())
			}
		}
	}
	return children, nil
}

// Checks whether the cluster is in leaf or not
// It will return error if the cluster doesn't exist,
// false if not a leaf node, true otherwise
func (f *FileStore) checkIsLeafNode(cluster string) (bool, error) {
	var err error
	var dir = f.clusterToPath(cluster)
	var fi os.FileInfo
	// check whether it is a dir
	fi, err = os.Stat(dir)
	if err != nil {
		return false, errors.New(fmt.Sprintf("cluser [%s] is NOT FOUND in FileStore [%s w.r.t %s] (ERROR: %s)", cluster, dir, f.StorePath, err))
	}
	if !fi.IsDir() {
		return false, errors.New(fmt.Sprintf("cluser [%s] is NOT A DIRECTORY in FileStore [%s w.r.t %s] (ERROR: %s)", cluster, dir, f.StorePath, err))
	}

	// now check whether this dir has "cluster.yaml" as its direct child
	_, err = os.Stat(fmt.Sprintf("%s/%s", dir, _config))
	// if err is nil, it means file exists
	if err == nil {
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	} else {
		return false, errors.New(fmt.Sprintf("cluser [%s] is NEITHER a LeafNode or a Cluster Dir in FileStore [%s w.r.t %s] (ERROR: %s)", cluster, dir, f.StorePath, err))
	}

	// not a dir
	return true, nil
}

// Given a cluster name, it will read the corresponding cluster config
// and return the file content as a string
func (f *FileStore) readClusterConfig(cluster string) (content []byte, err error) {
	var dir = f.clusterToPath(cluster)
	content, err = ioutil.ReadFile(fmt.Sprintf("%s/%s", dir, _config))
	if err != nil {
		return []byte{}, err
	}

	return content, nil
}

// Get all the leaf cluster nodes for a given dir
// NOTE: This code is not efficient as "path/filepath" Walk() function
// is not efficient since it does the walk in lexical order
func (f *FileStore) getAllLeafNodes(root string) (*[]string, error) {
	var leafs = make([]string, 0)
	var err error
	// if root is given, append to localize the lookup
	root = f.clusterToPath(root)
	// do a Clean to remove weirdness in path
	trimPath := fmt.Sprintf("%s/", filepath.Clean(f.StorePath))
	// do the walk
	err = filepath.Walk(
		root,
		// append only the name matches _config
		func(path string, fi os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if fi.Name() == _config {
				leafs = append(leafs, strings.Replace(strings.TrimPrefix(filepath.Dir(path), trimPath), "/", "-", -1))
			}
			return nil
		},
	)

	if err != nil {
		return &[]string{}, errors.New(fmt.Sprintf("filepath.Walk Failed for ROOT dir [%s]", root))
	}

	return &leafs, nil
}

// We expect the YAML data to be in key value, where value is
// an array. Incase value is not an array, we will still return
// as an array
func yamlKeyLookup(content []byte, key string) (*[]string, error) {
	var u map[string]interface{}
	var err error
	err = yaml.Unmarshal(content, &u)
	// if unmarshal fails, return early with error
	if err != nil {
		return &[]string{}, err
	}

	// handle KEYS separately
	// returns all the KEYS of a cluster
	if key == "KEYS" {
		var results = make([]string, 0)
		for k := range u {
			results = append(results, k)
		}
		return &results, nil
	}

	// check whether the map has the key we are looking for
	value, ok := u[key]
	if !ok {
		return &[]string{}, errors.New(fmt.Sprintf("Cannot find Key [%s]", key))
	}

	// try to return result pointer to an array of strings
	switch value.(type) {
	// if it is an array
	case []interface{}:
		var results = make([]string, 0)
		for _, elem := range value.([]interface{}) {
			switch elem.(type) {
			case string:
				results = append(results, elem.(string))
			case int:
				results = append(results, fmt.Sprintf("%d", elem.(int)))
			case bool:
				results = append(results, fmt.Sprintf("%t", elem.(bool)))
			}
		}
		// make sure there are no duplicates
		rangeops.ArrayToSet(&results)
		return &results, nil
		// if not an array, make it an array
	case string:
		return &[]string{value.(string)}, nil
	case int:
		return &[]string{fmt.Sprintf("%d", value.(int))}, nil
	case bool:
		return &[]string{fmt.Sprintf("%t", value.(bool))}, nil
	}

	return &[]string{}, nil
}
<commit_msg>fixing comment<commit_after>// When using FileStore, we will have yamls files to store the data.

// FIXME:
// Since I am not planning to use this in production (I prefer etcd as store)
// I would NOT be using any optimizations to cache the process file
// contents (ie, result of yaml parser) Please give me a patch (one way to clear
// the cache is by sending some signals to reread the file and update the cache.
// Also we should make sure we reread only the specific files in question).

package filestore

import (
	"errors"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"path/filepath"
	"rangeops"
	"strings"
)

const _config = "cluster.yaml"

type FileStore struct {
	StorePath  string // directory where yamls are stored
	MaxDepth   int    // TODO: we could use this for reverse lookup to limit nested look down
	FastLookup bool   // fast return, will return the first match
}

// check whether the StorePath Exists, etc
func ConnectFileStore(dir string, depth int, fast bool) (f *FileStore, err error) {
	// removing trailing path seperator
	if len(dir) > 0 && os.IsPathSeparator(dir[len(dir)-1]) {
		dir = dir[:len(dir)-1]
	}
	var fi os.FileInfo
	// check whether the dir exists
	fi, err = os.Stat(dir)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Path [%s] is not a FileStore directory (ERROR: %s)", dir, err))
	}
	// check whether it is a dir
	if !fi.IsDir() {
		return nil, errors.New(fmt.Sprintf("Path [%s] is not a directory", dir))
	}
	f = &FileStore{StorePath: dir, MaxDepth: depth, FastLookup: fast}
	return f, nil
}

// nothing to cleanup
func (f *FileStore) DisconnectFileStore() {
	return
}

////////////////////
// LOOKUP CLUSTER //
////////////////////

// LOGIC
// -----
// * for the first element in cluster create results array
//   * check whether the cluster is a leaf node
//   * if yes, call KeyLookup, with key == NODES
//   * if not, call listClusters
// * if more elements are there, repeat the above
//   but do an ArraytoSet with the results array
func (f *FileStore) ClusterLookup(cluster *[]string) (*[]string, error) {
	// store the resuls
	var results = make([]string, 0)
	// for each cluster, do a lookup
	// (this will only happen only for nested lookups eg, %%..)
	for _, elem := range *cluster {
		// handle RANGE separately
		if elem == "RANGE" {
			elem = "."
		}
		var err error
		isLeaf, err := f.checkIsLeafNode(elem)
		if err != nil {
			return &[]string{}, err
		}
		// if it is a leaf node, we need do a KeyLookup (NODES)
		if isLeaf {
			// by default, lookup for NODES
			result, err := f.KeyLookup(&[]string{elem}, "NODES")
			if err != nil {
				return &[]string{}, err
			}
			results = append(results, *result...)
		} else { // we need to return the children
			result, err := f.listClusters(elem)
			if err != nil {
				return &[]string{}, err
			}
			results = append(results, result...)
		}

	}

	return &results, nil
}

func (f *FileStore) KeyLookup(cluster *[]string, key string) (*[]string, error) {
	// store the resuls
	var results = make([]string, 0)
	// this will most likely be single element arrays
	// can't think of a reason otherwise
	for _, elem := range *cluster {
		// 1. read the config
		// 2. do a key lookup
		// 3. append the result
		content, err := f.readClusterConfig(elem)
		if err != nil {
			return &[]string{}, errors.New(fmt.Sprintf("KeyLookup for [%s] Failed (Error: %s)", elem, err))
		}
		result, err := yamlKeyLookup(content, key)
		if err != nil {
			return &[]string{}, errors.New(fmt.Sprintf("KeyLookup for [%s] Failed (Error: %s)", elem, err))
		}
		results = append(results, *result...)
	}

	return &results, nil
}

////////////////////
// LOOKUP REVERSE //
////////////////////

// same as KeyReverseLookupAttr where attr == NODES
func (f *FileStore) KeyReverseLookup(key string) (*[]string, error) {
	return f.KeyReverseLookupAttr(key, "NODES")
}

// same as KeyReverseLookupAttr where attr == NODES and hint == ""
func (f *FileStore) KeyReverseLookupAttr(key string, attr string) (*[]string, error) {
	return f.KeyReverseLookupHint(key, attr, "")
}

// given a key, it will search for the cluster where the attr has that key,
// hint is to limit the scope of search
func (f *FileStore) KeyReverseLookupHint(key string, attr string, hint string) (*[]string, error) {
	var clusters *[]string
	var err error
	var results = make([]string, 0)
	var seen bool

	clusters, err = f.getAllLeafNodes(hint)
	if err != nil {
		return &results, nil
	}

	for _, elem := range *clusters {
		// get the cluster config
		content, err := f.readClusterConfig(elem)
		if err != nil {
			return &[]string{}, errors.New(fmt.Sprintf("KeyLookup for [%s] Failed (Error: %s)", elem, err))
		}
		// look whether the attr exists
		result, err := yamlKeyLookup(content, attr)
		if err != nil {
			continue // looks like we didn't find the key
		} else {
			for _, i := range *result {
				if i == key {
					results = append(results, elem)
					seen = true
					break
				}
			}
			if seen && f.FastLookup {
				return &results, nil
			}
		}
	}

	return &results, nil
}

////////////////////////
// Internal Functions //
////////////////////////

// given a cluster name, it will convert to cluster
// in the file system
func (f *FileStore) clusterToPath(cluster string) string {
	return fmt.Sprintf("%s/%s", f.StorePath, strings.Replace(cluster, "-", "/", -1))
}

// reads the child clusters of this cluster.
// returns only those nodes for which this cluster is parent
func (f *FileStore) listClusters(cluster string) ([]string, error) {
	var dir = f.clusterToPath(cluster)
	var children = make([]string, 0)
	files, err := ioutil.ReadDir(dir)
	// if there is an error, return err
	if err != nil {
		return []string{}, err
	}
	for _, f := range files {
		if f.IsDir() {
			// the cluster string to append the children with, when we get a
			// top level query %RANGE, cluster = "."
			if cluster != "." {
				children = append(children, fmt.Sprintf("%s-%s", cluster, f.Name()))
			} else {
				children = append(children, f.Name())
			}
		}
	}
	return children, nil
}

// Checks whether the cluster is in leaf or not
// It will return error if the cluster doesn't exist,
// false if not a leaf node, true otherwise
func (f *FileStore) checkIsLeafNode(cluster string) (bool, error) {
	var err error
	var dir = f.clusterToPath(cluster)
	var fi os.FileInfo
	// check whether it is a dir
	fi, err = os.Stat(dir)
	if err != nil {
		return false, errors.New(fmt.Sprintf("cluser [%s] is NOT FOUND in FileStore [%s w.r.t %s] (ERROR: %s)", cluster, dir, f.StorePath, err))
	}
	if !fi.IsDir() {
		return false, errors.New(fmt.Sprintf("cluser [%s] is NOT A DIRECTORY in FileStore [%s w.r.t %s] (ERROR: %s)", cluster, dir, f.StorePath, err))
	}

	// now check whether this dir has "cluster.yaml" as its direct child
	_, err = os.Stat(fmt.Sprintf("%s/%s", dir, _config))
	// if err is nil, it means file exists
	if err == nil {
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	} else {
		return false, errors.New(fmt.Sprintf("cluser [%s] is NEITHER a LeafNode or a Cluster Dir in FileStore [%s w.r.t %s] (ERROR: %s)", cluster, dir, f.StorePath, err))
	}

	// not a dir
	return true, nil
}

// Given a cluster name, it will read the corresponding cluster config
// and return the file content as a string
func (f *FileStore) readClusterConfig(cluster string) (content []byte, err error) {
	var dir = f.clusterToPath(cluster)
	content, err = ioutil.ReadFile(fmt.Sprintf("%s/%s", dir, _config))
	if err != nil {
		return []byte{}, err
	}

	return content, nil
}

// Get all the leaf cluster nodes for a given dir
// NOTE: This code is not efficient as "path/filepath" Walk() function
// is not efficient since it does the walk in lexical order
func (f *FileStore) getAllLeafNodes(root string) (*[]string, error) {
	var leafs = make([]string, 0)
	var err error
	// if root is given, append to localize the lookup
	root = f.clusterToPath(root)
	// do a Clean to remove weirdness in path
	trimPath := fmt.Sprintf("%s/", filepath.Clean(f.StorePath))
	// do the walk
	err = filepath.Walk(
		root,
		// append only the name matches _config
		func(path string, fi os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if fi.Name() == _config {
				leafs = append(leafs, strings.Replace(strings.TrimPrefix(filepath.Dir(path), trimPath), "/", "-", -1))
			}
			return nil
		},
	)

	if err != nil {
		return &[]string{}, errors.New(fmt.Sprintf("filepath.Walk Failed for ROOT dir [%s]", root))
	}

	return &leafs, nil
}

// We expect the YAML data to be in key value, where value is
// an array. Incase value is not an array, we will still return
// as an array
func yamlKeyLookup(content []byte, key string) (*[]string, error) {
	var u map[string]interface{}
	var err error
	err = yaml.Unmarshal(content, &u)
	// if unmarshal fails, return early with error
	if err != nil {
		return &[]string{}, err
	}

	// handle KEYS separately
	// returns all the KEYS of a cluster
	if key == "KEYS" {
		var results = make([]string, 0)
		for k := range u {
			results = append(results, k)
		}
		return &results, nil
	}

	// check whether the map has the key we are looking for
	value, ok := u[key]
	if !ok {
		return &[]string{}, errors.New(fmt.Sprintf("Cannot find Key [%s]", key))
	}

	// try to return result pointer to an array of strings
	switch value.(type) {
	// if it is an array
	case []interface{}:
		var results = make([]string, 0)
		for _, elem := range value.([]interface{}) {
			switch elem.(type) {
			case string:
				results = append(results, elem.(string))
			case int:
				results = append(results, fmt.Sprintf("%d", elem.(int)))
			case bool:
				results = append(results, fmt.Sprintf("%t", elem.(bool)))
			}
		}
		// make sure there are no duplicates
		rangeops.ArrayToSet(&results)
		return &results, nil
		// if not an array, make it an array
	case string:
		return &[]string{value.(string)}, nil
	case int:
		return &[]string{fmt.Sprintf("%d", value.(int))}, nil
	case bool:
		return &[]string{fmt.Sprintf("%t", value.(bool))}, nil
	}

	return &[]string{}, nil
}
<|endoftext|>


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

// +build windows

package gldriver

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"

	"github.com/oakmound/oak/v2/shiny/driver/internal/win32"
	"github.com/oakmound/oak/v2/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/mouse"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/gl"
)

// TODO: change this to true, after manual testing on Win32.
const useLifecycler = true

// TODO: change this to true, after manual testing on Win32.
const handleSizeEventsAtChannelReceive = true

func main(f func(screen.Screen)) error {
	return win32.Main(func() { f(theScreen) })
}

var (
	eglGetPlatformDisplayEXT = gl.LibEGL.NewProc("eglGetPlatformDisplayEXT")
	eglInitialize            = gl.LibEGL.NewProc("eglInitialize")
	eglChooseConfig          = gl.LibEGL.NewProc("eglChooseConfig")
	eglGetError              = gl.LibEGL.NewProc("eglGetError")
	eglBindAPI               = gl.LibEGL.NewProc("eglBindAPI")
	eglCreateWindowSurface   = gl.LibEGL.NewProc("eglCreateWindowSurface")
	eglCreateContext         = gl.LibEGL.NewProc("eglCreateContext")
	eglMakeCurrent           = gl.LibEGL.NewProc("eglMakeCurrent")
	eglSwapInterval          = gl.LibEGL.NewProc("eglSwapInterval")
	eglDestroySurface        = gl.LibEGL.NewProc("eglDestroySurface")
	eglSwapBuffers           = gl.LibEGL.NewProc("eglSwapBuffers")
)

type eglConfig uintptr // void*

type eglInt int32

var rgb888 = [...]eglInt{
	_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
	_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
	_EGL_BLUE_SIZE, 8,
	_EGL_GREEN_SIZE, 8,
	_EGL_RED_SIZE, 8,
	_EGL_DEPTH_SIZE, 16,
	_EGL_STENCIL_SIZE, 8,
	_EGL_NONE,
}

type ctxWin32 struct {
	ctx     uintptr
	display uintptr // EGLDisplay
	surface uintptr // EGLSurface
}

func newWindow(opts screen.WindowGenerator) (uintptr, error) {
	w, err := win32.NewWindow(opts)
	if err != nil {
		return 0, err
	}

	return uintptr(w), nil
}

func moveWindow(w *windowImpl, opts screen.WindowGenerator) error {
	return win32.ResizeClientRect(win32.HWND(w.id), opts)
}

func initWindow(w *windowImpl) {
	w.glctx, w.worker = gl.NewContext()
}

func showWindow(w *windowImpl) {
	// Show makes an initial call to sizeEvent (via win32.SizeEvent), where
	// we setup the EGL surface and GL context.
	win32.Show(win32.HWND(w.id))
}

func closeWindow(id uintptr) {} // TODO

func drawLoop(w *windowImpl) {
	runtime.LockOSThread()

	display := w.ctx.(ctxWin32).display
	surface := w.ctx.(ctxWin32).surface
	ctx := w.ctx.(ctxWin32).ctx

	if ret, _, _ := eglMakeCurrent.Call(display, surface, surface, ctx); ret == 0 {
		panic(fmt.Sprintf("eglMakeCurrent failed: %v", eglErr()))
	}

	// TODO(crawshaw): exit this goroutine on Release.
	workAvailable := w.worker.WorkAvailable()
	for {
		select {
		case <-workAvailable:
			w.worker.DoWork()
		case <-w.publish:
		loop:
			for {
				select {
				case <-workAvailable:
					w.worker.DoWork()
				default:
					break loop
				}
			}
			if ret, _, _ := eglSwapBuffers.Call(display, surface); ret == 0 {
				panic(fmt.Sprintf("eglSwapBuffers failed: %v", eglErr()))
			}
			w.publishDone <- screen.PublishResult{}
		}
	}
}

func init() {
	win32.SizeEvent = sizeEvent
	win32.PaintEvent = paintEvent
	win32.MouseEvent = mouseEvent
	win32.KeyEvent = keyEvent
	win32.LifecycleEvent = lifecycleEvent
}

func lifecycleEvent(hwnd win32.HWND, to lifecycle.Stage) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	if w.lifecycleStage == to {
		return
	}
	w.Send(lifecycle.Event{
		From:        w.lifecycleStage,
		To:          to,
		DrawContext: w.glctx,
	})
	w.lifecycleStage = to
}

func mouseEvent(hwnd win32.HWND, e mouse.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	w.Send(e)
}

func keyEvent(hwnd win32.HWND, e key.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	w.Send(e)
}

func paintEvent(hwnd win32.HWND, e paint.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	if w.ctx == nil {
		// Sometimes a paint event comes in before initial
		// window size is set. Ignore it.
		return
	}

	// TODO: the paint.Event should have External: true.
	w.Send(paint.Event{})
}

func sizeEvent(hwnd win32.HWND, e size.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	if w.ctx == nil {
		// This is the initial size event on window creation.
		// Create an EGL surface and spin up a GL context.
		if err := createEGLSurface(hwnd, w); err != nil {
			panic(err)
		}
		go drawLoop(w)
	}

	if !handleSizeEventsAtChannelReceive {
		w.szMu.Lock()
		w.sz = e
		w.szMu.Unlock()
	}

	w.Send(e)

	if handleSizeEventsAtChannelReceive {
		return
	}

	// Screen is dirty, generate a paint event.
	//
	// The sizeEvent function is called on the goroutine responsible for
	// calling the GL worker.DoWork. When compiling with -tags gldebug,
	// these GL calls are blocking (so we can read the error message), so
	// to make progress they need to happen on another goroutine.
	go func() {
		// TODO: this call to Viewport is not right, but is very hard to
		// do correctly with our async events channel model. We want
		// the call to Viewport to be made the instant before the
		// paint.Event is received.
		w.glctxMu.Lock()
		w.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
		w.glctx.ClearColor(0, 0, 0, 1)
		w.glctx.Clear(gl.COLOR_BUFFER_BIT)
		w.glctxMu.Unlock()

		w.Send(paint.Event{})
	}()
}

func eglErr() error {
	if ret, _, _ := eglGetError.Call(); ret != _EGL_SUCCESS {
		return errors.New(eglErrString(ret))
	}
	return nil
}

func createEGLSurface(hwnd win32.HWND, w *windowImpl) error {
	var displayAttribPlatforms = [][]eglInt{
		// Default
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
		// Direct3D 11
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
		// Direct3D 9
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
		// Direct3D 11 with WARP
		//   https://msdn.microsoft.com/en-us/library/windows/desktop/gg615082.aspx
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
			_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
	}

	dc, err := win32.GetDC(hwnd)
	if err != nil {
		return fmt.Errorf("win32.GetDC failed: %v", err)
	}

	var display uintptr = _EGL_NO_DISPLAY
	for i, displayAttrib := range displayAttribPlatforms {
		lastTry := i == len(displayAttribPlatforms)-1

		display, _, _ = eglGetPlatformDisplayEXT.Call(
			_EGL_PLATFORM_ANGLE_ANGLE,
			uintptr(dc),
			uintptr(unsafe.Pointer(&displayAttrib[0])),
		)

		if display == _EGL_NO_DISPLAY {
			if !lastTry {
				continue
			}
			return fmt.Errorf("eglGetPlatformDisplayEXT failed: %v", eglErr())
		}

		if ret, _, _ := eglInitialize.Call(display, 0, 0); ret == 0 {
			if !lastTry {
				continue
			}
			return fmt.Errorf("eglInitialize failed: %v", eglErr())
		}
	}

	eglBindAPI.Call(_EGL_OPENGL_ES_API)
	if err := eglErr(); err != nil {
		return err
	}

	var numConfigs eglInt
	var config eglConfig
	ret, _, _ := eglChooseConfig.Call(
		display,
		uintptr(unsafe.Pointer(&rgb888[0])),
		uintptr(unsafe.Pointer(&config)),
		1,
		uintptr(unsafe.Pointer(&numConfigs)),
	)
	if ret == 0 {
		return fmt.Errorf("eglChooseConfig failed: %v", eglErr())
	}
	if numConfigs <= 0 {
		return errors.New("eglChooseConfig found no valid config")
	}

	surface, _, _ := eglCreateWindowSurface.Call(display, uintptr(config), uintptr(hwnd), 0, 0)
	if surface == _EGL_NO_SURFACE {
		return fmt.Errorf("eglCreateWindowSurface failed: %v", eglErr())
	}

	contextAttribs := [...]eglInt{
		_EGL_CONTEXT_CLIENT_VERSION, 2,
		_EGL_NONE,
	}
	context, _, _ := eglCreateContext.Call(
		display,
		uintptr(config),
		_EGL_NO_CONTEXT,
		uintptr(unsafe.Pointer(&contextAttribs[0])),
	)
	if context == _EGL_NO_CONTEXT {
		return fmt.Errorf("eglCreateContext failed: %v", eglErr())
	}

	eglSwapInterval.Call(display, 1)

	w.ctx = ctxWin32{
		ctx:     context,
		display: display,
		surface: surface,
	}

	return nil
}

func surfaceCreate() error {
	return errors.New("gldriver: surface creation not implemented on windows")
}
<commit_msg>shiny/driver/gldriver: use new window creation pattern for win32<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build windows

package gldriver

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"

	"github.com/oakmound/oak/v2/shiny/driver/internal/win32"
	"github.com/oakmound/oak/v2/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/mouse"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/gl"
)

const useLifecycler = true
const handleSizeEventsAtChannelReceive = true

var screenHWND win32.HWND

func main(f func(screen.Screen)) error {
	var err error
	screenHWND, err = win32.NewScreen()
	if err != nil {
		return err
	}
	return win32.Main(screenHWND, func() { f(theScreen) })
}

var (
	eglGetPlatformDisplayEXT = gl.LibEGL.NewProc("eglGetPlatformDisplayEXT")
	eglInitialize            = gl.LibEGL.NewProc("eglInitialize")
	eglChooseConfig          = gl.LibEGL.NewProc("eglChooseConfig")
	eglGetError              = gl.LibEGL.NewProc("eglGetError")
	eglBindAPI               = gl.LibEGL.NewProc("eglBindAPI")
	eglCreateWindowSurface   = gl.LibEGL.NewProc("eglCreateWindowSurface")
	eglCreateContext         = gl.LibEGL.NewProc("eglCreateContext")
	eglMakeCurrent           = gl.LibEGL.NewProc("eglMakeCurrent")
	eglSwapInterval          = gl.LibEGL.NewProc("eglSwapInterval")
	eglDestroySurface        = gl.LibEGL.NewProc("eglDestroySurface")
	eglSwapBuffers           = gl.LibEGL.NewProc("eglSwapBuffers")
)

type eglConfig uintptr // void*

type eglInt int32

var rgb888 = [...]eglInt{
	_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
	_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
	_EGL_BLUE_SIZE, 8,
	_EGL_GREEN_SIZE, 8,
	_EGL_RED_SIZE, 8,
	_EGL_DEPTH_SIZE, 16,
	_EGL_STENCIL_SIZE, 8,
	_EGL_NONE,
}

type ctxWin32 struct {
	ctx     uintptr
	display uintptr // EGLDisplay
	surface uintptr // EGLSurface
}

func newWindow(opts screen.WindowGenerator) (uintptr, error) {
	w, err := win32.NewWindow(screenHWND, opts)
	if err != nil {
		return 0, err
	}

	return uintptr(w), nil
}

func moveWindow(w *windowImpl, opts screen.WindowGenerator) error {
	return win32.ResizeClientRect(win32.HWND(w.id), opts)
}

func initWindow(w *windowImpl) {
	w.glctx, w.worker = gl.NewContext()
}

func showWindow(w *windowImpl) {
	// Show makes an initial call to sizeEvent (via win32.SizeEvent), where
	// we setup the EGL surface and GL context.
	win32.Show(win32.HWND(w.id))
}

func closeWindow(id uintptr) {} // TODO

func drawLoop(w *windowImpl) {
	runtime.LockOSThread()

	display := w.ctx.(ctxWin32).display
	surface := w.ctx.(ctxWin32).surface
	ctx := w.ctx.(ctxWin32).ctx

	if ret, _, _ := eglMakeCurrent.Call(display, surface, surface, ctx); ret == 0 {
		panic(fmt.Sprintf("eglMakeCurrent failed: %v", eglErr()))
	}

	// TODO(crawshaw): exit this goroutine on Release.
	workAvailable := w.worker.WorkAvailable()
	for {
		select {
		case <-workAvailable:
			w.worker.DoWork()
		case <-w.publish:
		loop:
			for {
				select {
				case <-workAvailable:
					w.worker.DoWork()
				default:
					break loop
				}
			}
			if ret, _, _ := eglSwapBuffers.Call(display, surface); ret == 0 {
				panic(fmt.Sprintf("eglSwapBuffers failed: %v", eglErr()))
			}
			w.publishDone <- screen.PublishResult{}
		}
	}
}

func init() {
	win32.SizeEvent = sizeEvent
	win32.PaintEvent = paintEvent
	win32.MouseEvent = mouseEvent
	win32.KeyEvent = keyEvent
	win32.LifecycleEvent = lifecycleEvent
}

func lifecycleEvent(hwnd win32.HWND, to lifecycle.Stage) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	if w.lifecycleStage == to {
		return
	}
	w.Send(lifecycle.Event{
		From:        w.lifecycleStage,
		To:          to,
		DrawContext: w.glctx,
	})
	w.lifecycleStage = to
}

func mouseEvent(hwnd win32.HWND, e mouse.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	w.Send(e)
}

func keyEvent(hwnd win32.HWND, e key.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	w.Send(e)
}

func paintEvent(hwnd win32.HWND, e paint.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	if w.ctx == nil {
		// Sometimes a paint event comes in before initial
		// window size is set. Ignore it.
		return
	}

	// TODO: the paint.Event should have External: true.
	w.Send(paint.Event{})
}

func sizeEvent(hwnd win32.HWND, e size.Event) {
	theScreen.mu.Lock()
	w := theScreen.windows[uintptr(hwnd)]
	theScreen.mu.Unlock()

	if w.ctx == nil {
		// This is the initial size event on window creation.
		// Create an EGL surface and spin up a GL context.
		if err := createEGLSurface(hwnd, w); err != nil {
			panic(err)
		}
		go drawLoop(w)
	}

	if !handleSizeEventsAtChannelReceive {
		w.szMu.Lock()
		w.sz = e
		w.szMu.Unlock()
	}

	w.Send(e)

	if handleSizeEventsAtChannelReceive {
		return
	}

	// Screen is dirty, generate a paint event.
	//
	// The sizeEvent function is called on the goroutine responsible for
	// calling the GL worker.DoWork. When compiling with -tags gldebug,
	// these GL calls are blocking (so we can read the error message), so
	// to make progress they need to happen on another goroutine.
	go func() {
		// TODO: this call to Viewport is not right, but is very hard to
		// do correctly with our async events channel model. We want
		// the call to Viewport to be made the instant before the
		// paint.Event is received.
		w.glctxMu.Lock()
		w.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
		w.glctx.ClearColor(0, 0, 0, 1)
		w.glctx.Clear(gl.COLOR_BUFFER_BIT)
		w.glctxMu.Unlock()

		w.Send(paint.Event{})
	}()
}

func eglErr() error {
	if ret, _, _ := eglGetError.Call(); ret != _EGL_SUCCESS {
		return errors.New(eglErrString(ret))
	}
	return nil
}

func createEGLSurface(hwnd win32.HWND, w *windowImpl) error {
	var displayAttribPlatforms = [][]eglInt{
		// Default
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
		// Direct3D 11
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
		// Direct3D 9
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
		// Direct3D 11 with WARP
		//   https://msdn.microsoft.com/en-us/library/windows/desktop/gg615082.aspx
		{
			_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
			_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
			_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
			_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
			_EGL_NONE,
		},
	}

	dc, err := win32.GetDC(hwnd)
	if err != nil {
		return fmt.Errorf("win32.GetDC failed: %v", err)
	}

	var display uintptr = _EGL_NO_DISPLAY
	for i, displayAttrib := range displayAttribPlatforms {
		lastTry := i == len(displayAttribPlatforms)-1

		display, _, _ = eglGetPlatformDisplayEXT.Call(
			_EGL_PLATFORM_ANGLE_ANGLE,
			uintptr(dc),
			uintptr(unsafe.Pointer(&displayAttrib[0])),
		)

		if display == _EGL_NO_DISPLAY {
			if !lastTry {
				continue
			}
			return fmt.Errorf("eglGetPlatformDisplayEXT failed: %v", eglErr())
		}

		if ret, _, _ := eglInitialize.Call(display, 0, 0); ret == 0 {
			if !lastTry {
				continue
			}
			return fmt.Errorf("eglInitialize failed: %v", eglErr())
		}
	}

	eglBindAPI.Call(_EGL_OPENGL_ES_API)
	if err := eglErr(); err != nil {
		return err
	}

	var numConfigs eglInt
	var config eglConfig
	ret, _, _ := eglChooseConfig.Call(
		display,
		uintptr(unsafe.Pointer(&rgb888[0])),
		uintptr(unsafe.Pointer(&config)),
		1,
		uintptr(unsafe.Pointer(&numConfigs)),
	)
	if ret == 0 {
		return fmt.Errorf("eglChooseConfig failed: %v", eglErr())
	}
	if numConfigs <= 0 {
		return errors.New("eglChooseConfig found no valid config")
	}

	surface, _, _ := eglCreateWindowSurface.Call(display, uintptr(config), uintptr(hwnd), 0, 0)
	if surface == _EGL_NO_SURFACE {
		return fmt.Errorf("eglCreateWindowSurface failed: %v", eglErr())
	}

	contextAttribs := [...]eglInt{
		_EGL_CONTEXT_CLIENT_VERSION, 2,
		_EGL_NONE,
	}
	context, _, _ := eglCreateContext.Call(
		display,
		uintptr(config),
		_EGL_NO_CONTEXT,
		uintptr(unsafe.Pointer(&contextAttribs[0])),
	)
	if context == _EGL_NO_CONTEXT {
		return fmt.Errorf("eglCreateContext failed: %v", eglErr())
	}

	eglSwapInterval.Call(display, 1)

	w.ctx = ctxWin32{
		ctx:     context,
		display: display,
		surface: surface,
	}

	return nil
}

func surfaceCreate() error {
	return errors.New("gldriver: surface creation not implemented on windows")
}
<|endoftext|>