<commit_before>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package graph

import (
	"net/http"
	"sync"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"

	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/logging"
)

const (
	defaultPongWait = 5
	writeWait       = 10 * time.Second
	maxMessageSize  = 1024 * 1024
)

type Server struct {
	Graph    *Graph
	Alert    *Alert
	Router   *mux.Router
	wsServer *WSServer
	Host     string
}

type ClientType int

const (
	GRAPHCLIENT ClientType = 1 + iota
	ALERTCLIENT
)

type WSClient struct {
	Type   ClientType
	conn   *websocket.Conn
	read   chan []byte
	send   chan []byte
	server *WSServer
}

type WSServer struct {
	Graph      *Graph
	Alert      *Alert
	clients    map[*WSClient]bool
	broadcast  chan string
	register   chan *WSClient
	unregister chan *WSClient
	pongWait   time.Duration
	pingPeriod time.Duration
}

func (c *WSClient) processGraphMessage(m []byte) {
	c.server.Graph.Lock()
	defer c.server.Graph.Unlock()

	msg, err := UnmarshalWSMessage(m)
	if err != nil {
		logging.GetLogger().Error("Graph: Unable to parse the event %s: %s", msg, err.Error())
		return
	}
	g := c.server.Graph

	switch msg.Type {
	case "SyncRequest":
		reply := WSMessage{
			Type: "SyncReply",
			Obj:  c.server.Graph,
		}
		c.send <- []byte(reply.String())

	case "SubGraphDeleted":
		n := msg.Obj.(*Node)

		logging.GetLogger().Debug("Got SubGraphDeleted event from the node %s", n.ID)

		node := g.GetNode(n.ID)
		if node != nil {
			g.DelSubGraph(node)
		}
	case "NodeUpdated":
		n := msg.Obj.(*Node)
		node := g.GetNode(n.ID)
		if node != nil {
			g.SetMetadatas(node, n.metadatas)
		}
	case "NodeDeleted":
		g.DelNode(msg.Obj.(*Node))
	case "NodeAdded":
		n := msg.Obj.(*Node)
		if g.GetNode(n.ID) == nil {
			g.AddNode(n)
		}
	case "EdgeUpdated":
		e := msg.Obj.(*Edge)
		edge := g.GetEdge(e.ID)
		if edge != nil {
			g.SetMetadatas(edge, e.metadatas)
		}
	case "EdgeDeleted":
		g.DelEdge(msg.Obj.(*Edge))
	case "EdgeAdded":
		e := msg.Obj.(*Edge)
		if g.GetEdge(e.ID) == nil {
			g.AddEdge(e)
		}
	}
}

func (c *WSClient) processGraphMessages(wg *sync.WaitGroup, quit chan struct{}) {
	for {
		select {
		case m, ok := <-c.read:
			if !ok {
				wg.Done()
				return
			}
			c.processGraphMessage(m)
		case <-quit:
			wg.Done()
			return
		}
	}
}

/* Called by alert.EvalNodes() */
func (c *WSClient) OnAlert(amsg *AlertMessage) {
	reply := WSMessage{
		Type: "AlertEvent",
		Obj:  *amsg,
	}
	c.send <- reply.Marshal()
}

func (c *WSClient) readPump() {
	defer func() {
		c.server.unregister <- c
		c.conn.Close()
	}()

	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(c.server.pongWait))
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(c.server.pongWait))
		return nil
	})

	for {
		_, m, err := c.conn.ReadMessage()
		if err != nil {
			break
		}

		c.read <- m
	}
}

func (c *WSClient) writePump(wg *sync.WaitGroup, quit chan struct{}) {
	ticker := time.NewTicker(c.server.pingPeriod)

	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				c.write(websocket.CloseMessage, []byte{})
				wg.Done()
				return
			}
			if err := c.write(websocket.TextMessage, message); err != nil {
				logging.GetLogger().Warning("Error while writing to the websocket: %s", err.Error())
				wg.Done()
				return
			}
		case <-ticker.C:
			if err := c.write(websocket.PingMessage, []byte{}); err != nil {
				wg.Done()
				return
			}
		case <-quit:
			wg.Done()
			return
		}
	}
}

func (c *WSClient) write(mt int, message []byte) error {
	c.conn.SetWriteDeadline(time.Now().Add(writeWait))
	return c.conn.WriteMessage(mt, message)
}

func (s *WSServer) ListenAndServe() {
	for {
		select {
		case c := <-s.register:
			s.clients[c] = true
			if c.Type == ALERTCLIENT {
				s.Alert.AddEventListener(c)
			}
		case c := <-s.unregister:
			_, ok := s.clients[c]
			if ok {
				if c.Type == ALERTCLIENT {
					s.Alert.DelEventListener(c)
				}

				delete(s.clients, c)
			}
		case m := <-s.broadcast:
			s.broadcastMessage(m)
		}
	}
}

func (s *WSServer) broadcastMessage(m string) {
	for c := range s.clients {
		select {
		case c.send <- []byte(m):
		default:
			delete(s.clients, c)
		}
	}
}

func (s *Server) sendGraphUpdateEvent(g WSMessage) {
	s.wsServer.broadcast <- g.String()
}

func (s *Server) OnNodeUpdated(n *Node) {
	s.sendGraphUpdateEvent(WSMessage{"NodeUpdated", n})
}

func (s *Server) OnNodeAdded(n *Node) {
	s.sendGraphUpdateEvent(WSMessage{"NodeAdded", n})
}

func (s *Server) OnNodeDeleted(n *Node) {
	s.sendGraphUpdateEvent(WSMessage{"NodeDeleted", n})
}

func (s *Server) OnEdgeUpdated(e *Edge) {
	s.sendGraphUpdateEvent(WSMessage{"EdgeUpdated", e})
}

func (s *Server) OnEdgeAdded(e *Edge) {
	s.sendGraphUpdateEvent(WSMessage{"EdgeAdded", e})
}

func (s *Server) OnEdgeDeleted(e *Edge) {
	s.sendGraphUpdateEvent(WSMessage{"EdgeDeleted", e})
}

func (s *Server) serveMessages(w http.ResponseWriter, r *http.Request) {
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}

	var ctype ClientType
	switch r.URL.Path {
	case "/ws/graph":
		ctype = GRAPHCLIENT
	case "/ws/alert":
		ctype = ALERTCLIENT
	}
	c := &WSClient{
		Type:   ctype,
		read:   make(chan []byte, maxMessageSize),
		send:   make(chan []byte, maxMessageSize),
		conn:   conn,
		server: s.wsServer,
	}
	logging.GetLogger().Info("New WebSocket Connection from %s : URI path %s", conn.RemoteAddr().String(), r.URL.Path)

	s.wsServer.register <- c

	var wg sync.WaitGroup
	wg.Add(2)

	quit := make(chan struct{})

	go c.writePump(&wg, quit)
	go c.processGraphMessages(&wg, quit)

	c.readPump()

	quit <- struct{}{}
	quit <- struct{}{}

	close(c.read)
	close(c.send)

	wg.Wait()
}

func (s *Server) ListenAndServe() {
	s.Graph.AddEventListener(s)

	s.Router.HandleFunc("/ws/graph", s.serveMessages)
	if s.Alert != nil {
		s.Router.HandleFunc("/ws/alert", s.serveMessages)
	}

	s.wsServer.ListenAndServe()
}

func NewServer(g *Graph, a *Alert, router *mux.Router, pongWait time.Duration) *Server {
	return &Server{
		Graph:  g,
		Alert:  a,
		Router: router,
		wsServer: &WSServer{
			Graph:      g,
			Alert:      a,
			broadcast:  make(chan string, 500),
			register:   make(chan *WSClient),
			unregister: make(chan *WSClient),
			clients:    make(map[*WSClient]bool),
			pongWait:   pongWait,
			pingPeriod: (pongWait * 8) / 10,
		},
	}
}

func NewServerFromConfig(g *Graph, a *Alert, router *mux.Router) (*Server, error) {
	w := config.GetConfig().Section("default").Key("ws_pong_timeout").MustInt(defaultPongWait)

	return NewServer(g, a, router, time.Duration(w)*time.Second), nil
}
<commit_msg>Fix potential race condition as ListenAndServe can be exec in goroutine<commit_after>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package graph

import (
	"net/http"
	"sync"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"

	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/logging"
)

const (
	defaultPongWait = 5
	writeWait       = 10 * time.Second
	maxMessageSize  = 1024 * 1024
)

type Server struct {
	Graph    *Graph
	Alert    *Alert
	Router   *mux.Router
	wsServer *WSServer
	Host     string
}

type ClientType int

const (
	GRAPHCLIENT ClientType = 1 + iota
	ALERTCLIENT
)

type WSClient struct {
	Type   ClientType
	conn   *websocket.Conn
	read   chan []byte
	send   chan []byte
	server *WSServer
}

type WSServer struct {
	Graph      *Graph
	Alert      *Alert
	clients    map[*WSClient]bool
	broadcast  chan string
	register   chan *WSClient
	unregister chan *WSClient
	pongWait   time.Duration
	pingPeriod time.Duration
}

func (c *WSClient) processGraphMessage(m []byte) {
	c.server.Graph.Lock()
	defer c.server.Graph.Unlock()

	msg, err := UnmarshalWSMessage(m)
	if err != nil {
		logging.GetLogger().Error("Graph: Unable to parse the event %s: %s", msg, err.Error())
		return
	}
	g := c.server.Graph

	switch msg.Type {
	case "SyncRequest":
		reply := WSMessage{
			Type: "SyncReply",
			Obj:  c.server.Graph,
		}
		c.send <- []byte(reply.String())

	case "SubGraphDeleted":
		n := msg.Obj.(*Node)

		logging.GetLogger().Debug("Got SubGraphDeleted event from the node %s", n.ID)

		node := g.GetNode(n.ID)
		if node != nil {
			g.DelSubGraph(node)
		}
	case "NodeUpdated":
		n := msg.Obj.(*Node)
		node := g.GetNode(n.ID)
		if node != nil {
			g.SetMetadatas(node, n.metadatas)
		}
	case "NodeDeleted":
		g.DelNode(msg.Obj.(*Node))
	case "NodeAdded":
		n := msg.Obj.(*Node)
		if g.GetNode(n.ID) == nil {
			g.AddNode(n)
		}
	case "EdgeUpdated":
		e := msg.Obj.(*Edge)
		edge := g.GetEdge(e.ID)
		if edge != nil {
			g.SetMetadatas(edge, e.metadatas)
		}
	case "EdgeDeleted":
		g.DelEdge(msg.Obj.(*Edge))
	case "EdgeAdded":
		e := msg.Obj.(*Edge)
		if g.GetEdge(e.ID) == nil {
			g.AddEdge(e)
		}
	}
}

func (c *WSClient) processGraphMessages(wg *sync.WaitGroup, quit chan struct{}) {
	for {
		select {
		case m, ok := <-c.read:
			if !ok {
				wg.Done()
				return
			}
			c.processGraphMessage(m)
		case <-quit:
			wg.Done()
			return
		}
	}
}

/* Called by alert.EvalNodes() */
func (c *WSClient) OnAlert(amsg *AlertMessage) {
	reply := WSMessage{
		Type: "AlertEvent",
		Obj:  *amsg,
	}
	c.send <- reply.Marshal()
}

func (c *WSClient) readPump() {
	defer func() {
		c.server.unregister <- c
		c.conn.Close()
	}()

	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(c.server.pongWait))
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(c.server.pongWait))
		return nil
	})

	for {
		_, m, err := c.conn.ReadMessage()
		if err != nil {
			break
		}

		c.read <- m
	}
}

func (c *WSClient) writePump(wg *sync.WaitGroup, quit chan struct{}) {
	ticker := time.NewTicker(c.server.pingPeriod)

	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				c.write(websocket.CloseMessage, []byte{})
				wg.Done()
				return
			}
			if err := c.write(websocket.TextMessage, message); err != nil {
				logging.GetLogger().Warning("Error while writing to the websocket: %s", err.Error())
				wg.Done()
				return
			}
		case <-ticker.C:
			if err := c.write(websocket.PingMessage, []byte{}); err != nil {
				wg.Done()
				return
			}
		case <-quit:
			wg.Done()
			return
		}
	}
}

func (c *WSClient) write(mt int, message []byte) error {
	c.conn.SetWriteDeadline(time.Now().Add(writeWait))
	return c.conn.WriteMessage(mt, message)
}

func (s *WSServer) ListenAndServe() {
	for {
		select {
		case c := <-s.register:
			s.clients[c] = true
			if c.Type == ALERTCLIENT {
				s.Alert.AddEventListener(c)
			}
		case c := <-s.unregister:
			_, ok := s.clients[c]
			if ok {
				if c.Type == ALERTCLIENT {
					s.Alert.DelEventListener(c)
				}

				delete(s.clients, c)
			}
		case m := <-s.broadcast:
			s.broadcastMessage(m)
		}
	}
}

func (s *WSServer) broadcastMessage(m string) {
	for c := range s.clients {
		select {
		case c.send <- []byte(m):
		default:
			delete(s.clients, c)
		}
	}
}

func (s *Server) sendGraphUpdateEvent(g WSMessage) {
	s.wsServer.broadcast <- g.String()
}

func (s *Server) OnNodeUpdated(n *Node) {
	s.sendGraphUpdateEvent(WSMessage{"NodeUpdated", n})
}

func (s *Server) OnNodeAdded(n *Node) {
	s.sendGraphUpdateEvent(WSMessage{"NodeAdded", n})
}

func (s *Server) OnNodeDeleted(n *Node) {
	s.sendGraphUpdateEvent(WSMessage{"NodeDeleted", n})
}

func (s *Server) OnEdgeUpdated(e *Edge) {
	s.sendGraphUpdateEvent(WSMessage{"EdgeUpdated", e})
}

func (s *Server) OnEdgeAdded(e *Edge) {
	s.sendGraphUpdateEvent(WSMessage{"EdgeAdded", e})
}

func (s *Server) OnEdgeDeleted(e *Edge) {
	s.sendGraphUpdateEvent(WSMessage{"EdgeDeleted", e})
}

func (s *Server) serveMessages(w http.ResponseWriter, r *http.Request) {
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}

	var ctype ClientType
	switch r.URL.Path {
	case "/ws/graph":
		ctype = GRAPHCLIENT
	case "/ws/alert":
		ctype = ALERTCLIENT
	}
	c := &WSClient{
		Type:   ctype,
		read:   make(chan []byte, maxMessageSize),
		send:   make(chan []byte, maxMessageSize),
		conn:   conn,
		server: s.wsServer,
	}
	logging.GetLogger().Info("New WebSocket Connection from %s : URI path %s", conn.RemoteAddr().String(), r.URL.Path)

	s.wsServer.register <- c

	var wg sync.WaitGroup
	wg.Add(2)

	quit := make(chan struct{})

	go c.writePump(&wg, quit)
	go c.processGraphMessages(&wg, quit)

	c.readPump()

	quit <- struct{}{}
	quit <- struct{}{}

	close(c.read)
	close(c.send)

	wg.Wait()
}

func (s *Server) ListenAndServe() {
	s.Graph.AddEventListener(s)

	s.wsServer.ListenAndServe()
}

func NewServer(g *Graph, a *Alert, router *mux.Router, pongWait time.Duration) *Server {
	s := &Server{
		Graph:  g,
		Alert:  a,
		Router: router,
		wsServer: &WSServer{
			Graph:      g,
			Alert:      a,
			broadcast:  make(chan string, 500),
			register:   make(chan *WSClient),
			unregister: make(chan *WSClient),
			clients:    make(map[*WSClient]bool),
			pongWait:   pongWait,
			pingPeriod: (pongWait * 8) / 10,
		},
	}

	s.Router.HandleFunc("/ws/graph", s.serveMessages)
	if s.Alert != nil {
		s.Router.HandleFunc("/ws/alert", s.serveMessages)
	}

	return s
}

func NewServerFromConfig(g *Graph, a *Alert, router *mux.Router) (*Server, error) {
	w := config.GetConfig().Section("default").Key("ws_pong_timeout").MustInt(defaultPongWait)

	return NewServer(g, a, router, time.Duration(w)*time.Second), nil
}
<|endoftext|>


<commit_before>package bench

import (
	"fmt"
	"os"
	"os/exec"
	"testing"
)

func TestSetup(t *testing.T) {
	conf, err := GetConfig()
	if err != nil {
		t.Fatalf("Configuration error: %s", err.Error())
	}

	segs, err := Segs()
	if err != nil {
		t.Fatalf("Cannot get deepgreen segs, error: %s.", err.Error())
	}

	seghosts := make(map[string]bool)
	for _, seg := range segs {
		seghosts[seg.Addr] = true
	}

	t.Run("Step=mkdirgen", func(t *testing.T) {
		cmd := fmt.Sprintf("mkdir -p %s/gen", Dir())
		err = exec.Command("bash", "-c", cmd).Run()
		if err != nil {
			t.Errorf("Cannot create gen dir.  error: %s", err.Error())
		}
	})

	t.Run("Step=xdrtoml", func(t *testing.T) {
		if conf.Ext != "XDR" {
			return
		}

		tomlf := Dir() + "/gen/xdrive.toml"
		xf, err := os.Create(tomlf)
		if err != nil {
			t.Errorf("Cannot create xdrive.toml file.  error: %s", err.Error())
		}

		fmt.Fprintf(xf, "[xdrive]\n")
		fmt.Fprintf(xf, "dir = \"%s\"\n", conf.Staging)
		fmt.Fprintf(xf, "host = [")
		prefix := " "
		for k, _ := range seghosts {
			fmt.Fprintf(xf, " %s\"%s:31416\" ", prefix, k)
			prefix = ","
		}
		fmt.Fprintf(xf, " ]\n\n")

		fmt.Fprintf(xf, "[[xdrive.mount]]\n")
		fmt.Fprintf(xf, "name = \"tpch-scale-%d\"\n", conf.Scale)
		fmt.Fprintf(xf, "scheme = \"nfs\"\n")
		fmt.Fprintf(xf, "root = \"./tpch/scale-%d\"\n", conf.Scale)
		fmt.Fprintf(xf, "conf = \"\"\n")

		xf.Close()

		err = exec.Command("xdrctl", "deploy", tomlf).Run()
		if err != nil {
			t.Errorf("Cannot deploy xdrive. error: %s", err.Error())
		}
	})

	t.Run("Step=db", func(t *testing.T) {
		conn, err := ConnectTemplate1()
		if err != nil {
			t.Errorf("Cannot connect to template1, error: %s", err.Error())
		}
		defer conn.Disconnect()

		conn.Execute(fmt.Sprintf("drop database %s", conf.Db))
		conn.Execute(fmt.Sprintf("create database %s", conf.Db))
	})

	t.Run("Step=ddl", func(t *testing.T) {
		ddlf := fmt.Sprintf("%s/sql/%s", Dir(), conf.DDL)
		cmd, err := PsqlCmd(ddlf)
		if err != nil {
			t.Errorf("Cannot build psql ddl command. error :%s", err.Error())
		}

		err = exec.Command("bash", "-c", cmd).Run()
		if err != nil {
			t.Errorf("Cannot run ddl.   error: %s", err.Error())
		}

		qf := fmt.Sprintf("%s/sql/mkview-n.sql", Dir())
		cmd, err = PsqlCmd(qf)
		if err != nil {
			t.Errorf("Cannot build psql query command. error :%s", err.Error())
		}

		err = exec.Command("bash", "-c", cmd).Run()
		if err != nil {
			t.Errorf("Cannot run query view ddl.   error: %s", err.Error())
		}
	})

	t.Run("Step=extddl", func(t *testing.T) {
		conn, err := Connect()
		if err != nil {
			t.Errorf("Cannot connect to database %s, error: %s", err.Error())
		}
		defer conn.Disconnect()

		conn.Execute("DROP SCHEMA IF EXISTS XDR CASCADE")
		conn.Execute("DROP SCHEMA IF EXISTS GPF CASCADE")
		conn.Execute("CREATE SCHEMA XDR")
		conn.Execute("CREATE SCHEMA GPF")

		var loc1f func(string) string
		var locallf func(string) string

		if conf.Ext == "XDR" {
			loc1f = func(t string) string {
				// xdrive syntax for nation, region is exactly the same as other tables.   In fact, for a
				// cluster running xdrive as single cluster mode, we must add a * wildcard -- otherwise,
				// if xdrive sees no wildcard, it will enforce the file exists, otherwise, error.
				return fmt.Sprintf("'xdrive://localhost:31416/tpch-scale-%d/seg-#SEGID#/%s.tbl*'", conf.Scale, t)
			}
			locallf = func(t string) string {
				return fmt.Sprintf("'xdrive://localhost:31416/tpch-scale-%d/seg-#SEGID#/%s.tbl*'", conf.Scale, t)
			}
		} else {
			loc1f = func(t string) string {
				return fmt.Sprintf("'gpfdist://%s:22222/tpch/scale-%d/seg-0/%s.tbl'", segs[0].Addr, conf.Scale, t)
			}
			locallf = func(t string) string {
				prefix := ""
				ret := ""
				for h, _ := range seghosts {
					ret = ret + prefix + fmt.Sprintf("'gpfdist://%s:22222/tpch/scale-%d/seg-*/%s.tbl.*'", h, conf.Scale, t)
					prefix = ","
				}
				return ret
			}
		}

		// Create two set of external tables, one for xdrive, one for gpfdist.
		//
		// nation.
		nation := `CREATE EXTERNAL TABLE %s.NATION  ( N_NATIONKEY  INTEGER,
                            N_NAME       VARCHAR(25) /*CHAR(25)*/,
                            N_REGIONKEY  INTEGER,
                            N_COMMENT    VARCHAR(152),
							DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(nation, conf.Ext, loc1f("nation")))

		// region
		region := ` CREATE EXTERNAL TABLE %s.REGION  ( R_REGIONKEY  INTEGER,
                            R_NAME       VARCHAR(25) /*CHAR(25)*/,
                            R_COMMENT    VARCHAR(152),
						DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(region, conf.Ext, loc1f("region")))

		// part
		part := `CREATE EXTERNAL TABLE %s.PART  ( P_PARTKEY     INTEGER,
                          P_NAME        VARCHAR(55),
                          P_MFGR        VARCHAR(25) /*CHAR(25)*/,
                          P_BRAND       VARCHAR(10) /*CHAR(10)*/,
                          P_TYPE        VARCHAR(25),
                          P_SIZE        INTEGER,
                          P_CONTAINER   VARCHAR(10) /*CHAR(10)*/,
                          P_RETAILPRICE DOUBLE PRECISION /*DECIMAL(15,2)*/,
                          P_COMMENT     VARCHAR(23),
						  DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		partsql := fmt.Sprintf(part, conf.Ext, locallf("part"))
		err = conn.Execute(partsql)
		if err != nil {
			t.Errorf("Cannot create ext table part.   DDS is %s", partsql)
		}

		// supplier
		supplier := `CREATE EXTERNAL TABLE %s.SUPPLIER ( S_SUPPKEY     INTEGER,
                             S_NAME        VARCHAR(25) /*CHAR(25)*/,
                             S_ADDRESS     VARCHAR(40),
                             S_NATIONKEY   INTEGER,
                             S_PHONE       VARCHAR(15) /*CHAR(15)*/,
                             S_ACCTBAL     DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             S_COMMENT     VARCHAR(101),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(supplier, conf.Ext, locallf("supplier")))

		partsupp := `CREATE EXTERNAL TABLE %s.PARTSUPP ( PS_PARTKEY     INTEGER,
                             PS_SUPPKEY     INTEGER,
                             PS_AVAILQTY    INTEGER,
                             PS_SUPPLYCOST  DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             PS_COMMENT     VARCHAR(199),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(partsupp, conf.Ext, locallf("partsupp")))

		customer := `CREATE EXTERNAL TABLE %s.CUSTOMER ( C_CUSTKEY     INTEGER,
                             C_NAME        VARCHAR(25),
                             C_ADDRESS     VARCHAR(40),
                             C_NATIONKEY   INTEGER,
                             C_PHONE       VARCHAR(15) /*CHAR(15)*/,
                             C_ACCTBAL     DOUBLE PRECISION/*DECIMAL(15,2)*/,
                             C_MKTSEGMENT  VARCHAR(10) /*CHAR(10)*/,
                             C_COMMENT     VARCHAR(117),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(customer, conf.Ext, locallf("customer")))

		orders := `CREATE EXTERNAL TABLE %s.ORDERS  ( O_ORDERKEY       BIGINT,
                           O_CUSTKEY        INTEGER,
                           O_ORDERSTATUS    VARCHAR(1)/*CHAR(1)*/,
                           O_TOTALPRICE     DOUBLE PRECISION /*DECIMAL(15,2)*/,
                           O_ORDERDATE      DATE,
                           O_ORDERPRIORITY  VARCHAR(15) /*CHAR(15)*/,
                           O_CLERK          VARCHAR(15) /*CHAR(15)*/,
                           O_SHIPPRIORITY   INTEGER,
                           O_COMMENT        VARCHAR(79),
						   DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(orders, conf.Ext, locallf("orders")))

		lineitem := `CREATE EXTERNAL TABLE %s.LINEITEM ( L_ORDERKEY BIGINT,
                             L_PARTKEY     INTEGER,
                             L_SUPPKEY     INTEGER,
                             L_LINENUMBER  INTEGER,
                             L_QUANTITY    INTEGER /*DECIMAL(15,2)*/,
                             L_EXTENDEDPRICE  DOUBLE PRECISION/*DECIMAL(15,2)*/,
                             L_DISCOUNT    DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             L_TAX         DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             L_RETURNFLAG  VARCHAR(1),
                             L_LINESTATUS  VARCHAR(1),
                             L_SHIPDATE    DATE,
                             L_COMMITDATE  DATE,
                             L_RECEIPTDATE DATE,
                             L_SHIPINSTRUCT VARCHAR(25) /*CHAR(25)*/,
                             L_SHIPMODE     VARCHAR(10) /*CHAR(10)*/,
                             L_COMMENT      VARCHAR(44),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(lineitem, conf.Ext, locallf("lineitem")))
	})
}
<commit_msg>Move to xdrive2.<commit_after>package bench

import (
	"fmt"
	"os"
	"os/exec"
	"testing"
)

func TestSetup(t *testing.T) {
	conf, err := GetConfig()
	if err != nil {
		t.Fatalf("Configuration error: %s", err.Error())
	}

	segs, err := Segs()
	if err != nil {
		t.Fatalf("Cannot get deepgreen segs, error: %s.", err.Error())
	}

	seghosts := make(map[string]bool)
	for _, seg := range segs {
		seghosts[seg.Addr] = true
	}

	t.Run("Step=mkdirgen", func(t *testing.T) {
		cmd := fmt.Sprintf("mkdir -p %s/gen", Dir())
		err = exec.Command("bash", "-c", cmd).Run()
		if err != nil {
			t.Errorf("Cannot create gen dir.  error: %s", err.Error())
		}
	})

	t.Run("Step=xdrtoml", func(t *testing.T) {
		if conf.Ext != "XDR" {
			return
		}

		tomlf := Dir() + "/gen/xdrive2.toml"
		xf, err := os.Create(tomlf)
		if err != nil {
			t.Errorf("Cannot create xdrive2.toml file.  error: %s", err.Error())
		}

		fmt.Fprintf(xf, "[xdrive2]\n")
		fmt.Fprintf(xf, "dir = \"%s\"\n", conf.Staging)
		fmt.Fprintf(xf, "pluginpath = [\"%s/plugin\"]\n", conf.Staging)
		fmt.Fprintf(xf, "host = [")
		prefix := " "
		for k, _ := range seghosts {
			fmt.Fprintf(xf, " %s\"%s:31416\" ", prefix, k)
			prefix = ","
		}
		fmt.Fprintf(xf, " ]\n\n")

		fmt.Fprintf(xf, "[[xdrive2.mount]]\n")
		fmt.Fprintf(xf, "name = \"tpch-scale-%d\"\n", conf.Scale)
		fmt.Fprintf(xf, "argv = [\"xdr_fs\", \"csv\", \"./tpch/scale-%d\"]\n", conf.Scale)

		fmt.Fprintf(xf, "\n[[xdrive2.mount]]\n")
		fmt.Fprintf(xf, "name = \"xdrive_pipe\"\n")
		fmt.Fprintf(xf, "argv = [\"xdrive_pipe\"]\n")

		xf.Close()

		err = exec.Command("xdrctl", "deploy", tomlf).Run()
		if err != nil {
			t.Errorf("Cannot deploy xdrive. error: %s", err.Error())
		}
	})

	t.Run("Step=db", func(t *testing.T) {
		conn, err := ConnectTemplate1()
		if err != nil {
			t.Errorf("Cannot connect to template1, error: %s", err.Error())
		}
		defer conn.Disconnect()

		conn.Execute(fmt.Sprintf("drop database %s", conf.Db))
		conn.Execute(fmt.Sprintf("create database %s", conf.Db))
	})

	t.Run("Step=ddl", func(t *testing.T) {
		ddlf := fmt.Sprintf("%s/sql/%s", Dir(), conf.DDL)
		cmd, err := PsqlCmd(ddlf)
		if err != nil {
			t.Errorf("Cannot build psql ddl command. error :%s", err.Error())
		}

		err = exec.Command("bash", "-c", cmd).Run()
		if err != nil {
			t.Errorf("Cannot run ddl.   error: %s", err.Error())
		}

		qf := fmt.Sprintf("%s/sql/mkview-n.sql", Dir())
		cmd, err = PsqlCmd(qf)
		if err != nil {
			t.Errorf("Cannot build psql query command. error :%s", err.Error())
		}

		err = exec.Command("bash", "-c", cmd).Run()
		if err != nil {
			t.Errorf("Cannot run query view ddl.   error: %s", err.Error())
		}
	})

	t.Run("Step=extddl", func(t *testing.T) {
		conn, err := Connect()
		if err != nil {
			t.Errorf("Cannot connect to database %s, error: %s", err.Error())
		}
		defer conn.Disconnect()

		conn.Execute("DROP SCHEMA IF EXISTS XDR CASCADE")
		conn.Execute("DROP SCHEMA IF EXISTS GPF CASCADE")
		conn.Execute("CREATE SCHEMA XDR")
		conn.Execute("CREATE SCHEMA GPF")

		var loc1f func(string) string
		var locallf func(string) string

		if conf.Ext == "XDR" {
			loc1f = func(t string) string {
				// xdrive syntax for nation, region is exactly the same as other tables.   In fact, for a
				// cluster running xdrive as single cluster mode, we must add a * wildcard -- otherwise,
				// if xdrive sees no wildcard, it will enforce the file exists, otherwise, error.
				return fmt.Sprintf("'xdrive://localhost:31416/tpch-scale-%d/seg-#SEGID#/%s.tbl*'", conf.Scale, t)
			}
			locallf = func(t string) string {
				return fmt.Sprintf("'xdrive://localhost:31416/tpch-scale-%d/seg-#SEGID#/%s.tbl*'", conf.Scale, t)
			}
		} else {
			loc1f = func(t string) string {
				return fmt.Sprintf("'gpfdist://%s:22222/tpch/scale-%d/seg-0/%s.tbl'", segs[0].Addr, conf.Scale, t)
			}
			locallf = func(t string) string {
				prefix := ""
				ret := ""
				for h, _ := range seghosts {
					ret = ret + prefix + fmt.Sprintf("'gpfdist://%s:22222/tpch/scale-%d/seg-*/%s.tbl.*'", h, conf.Scale, t)
					prefix = ","
				}
				return ret
			}
		}

		// Create two set of external tables, one for xdrive, one for gpfdist.
		//
		// nation.
		nation := `CREATE EXTERNAL TABLE %s.NATION  ( N_NATIONKEY  INTEGER,
                            N_NAME       VARCHAR(25) /*CHAR(25)*/,
                            N_REGIONKEY  INTEGER,
                            N_COMMENT    VARCHAR(152),
							DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(nation, conf.Ext, loc1f("nation")))

		// region
		region := ` CREATE EXTERNAL TABLE %s.REGION  ( R_REGIONKEY  INTEGER,
                            R_NAME       VARCHAR(25) /*CHAR(25)*/,
                            R_COMMENT    VARCHAR(152),
						DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(region, conf.Ext, loc1f("region")))

		// part
		part := `CREATE EXTERNAL TABLE %s.PART  ( P_PARTKEY     INTEGER,
                          P_NAME        VARCHAR(55),
                          P_MFGR        VARCHAR(25) /*CHAR(25)*/,
                          P_BRAND       VARCHAR(10) /*CHAR(10)*/,
                          P_TYPE        VARCHAR(25),
                          P_SIZE        INTEGER,
                          P_CONTAINER   VARCHAR(10) /*CHAR(10)*/,
                          P_RETAILPRICE DOUBLE PRECISION /*DECIMAL(15,2)*/,
                          P_COMMENT     VARCHAR(23),
						  DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		partsql := fmt.Sprintf(part, conf.Ext, locallf("part"))
		err = conn.Execute(partsql)
		if err != nil {
			t.Errorf("Cannot create ext table part.   DDS is %s", partsql)
		}

		// supplier
		supplier := `CREATE EXTERNAL TABLE %s.SUPPLIER ( S_SUPPKEY     INTEGER,
                             S_NAME        VARCHAR(25) /*CHAR(25)*/,
                             S_ADDRESS     VARCHAR(40),
                             S_NATIONKEY   INTEGER,
                             S_PHONE       VARCHAR(15) /*CHAR(15)*/,
                             S_ACCTBAL     DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             S_COMMENT     VARCHAR(101),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(supplier, conf.Ext, locallf("supplier")))

		partsupp := `CREATE EXTERNAL TABLE %s.PARTSUPP ( PS_PARTKEY     INTEGER,
                             PS_SUPPKEY     INTEGER,
                             PS_AVAILQTY    INTEGER,
                             PS_SUPPLYCOST  DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             PS_COMMENT     VARCHAR(199),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(partsupp, conf.Ext, locallf("partsupp")))

		customer := `CREATE EXTERNAL TABLE %s.CUSTOMER ( C_CUSTKEY     INTEGER,
                             C_NAME        VARCHAR(25),
                             C_ADDRESS     VARCHAR(40),
                             C_NATIONKEY   INTEGER,
                             C_PHONE       VARCHAR(15) /*CHAR(15)*/,
                             C_ACCTBAL     DOUBLE PRECISION/*DECIMAL(15,2)*/,
                             C_MKTSEGMENT  VARCHAR(10) /*CHAR(10)*/,
                             C_COMMENT     VARCHAR(117),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(customer, conf.Ext, locallf("customer")))

		orders := `CREATE EXTERNAL TABLE %s.ORDERS  ( O_ORDERKEY       BIGINT,
                           O_CUSTKEY        INTEGER,
                           O_ORDERSTATUS    VARCHAR(1)/*CHAR(1)*/,
                           O_TOTALPRICE     DOUBLE PRECISION /*DECIMAL(15,2)*/,
                           O_ORDERDATE      DATE,
                           O_ORDERPRIORITY  VARCHAR(15) /*CHAR(15)*/,
                           O_CLERK          VARCHAR(15) /*CHAR(15)*/,
                           O_SHIPPRIORITY   INTEGER,
                           O_COMMENT        VARCHAR(79),
						   DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(orders, conf.Ext, locallf("orders")))

		lineitem := `CREATE EXTERNAL TABLE %s.LINEITEM ( L_ORDERKEY BIGINT,
                             L_PARTKEY     INTEGER,
                             L_SUPPKEY     INTEGER,
                             L_LINENUMBER  INTEGER,
                             L_QUANTITY    INTEGER /*DECIMAL(15,2)*/,
                             L_EXTENDEDPRICE  DOUBLE PRECISION/*DECIMAL(15,2)*/,
                             L_DISCOUNT    DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             L_TAX         DOUBLE PRECISION /*DECIMAL(15,2)*/,
                             L_RETURNFLAG  VARCHAR(1),
                             L_LINESTATUS  VARCHAR(1),
                             L_SHIPDATE    DATE,
                             L_COMMITDATE  DATE,
                             L_RECEIPTDATE DATE,
                             L_SHIPINSTRUCT VARCHAR(25) /*CHAR(25)*/,
                             L_SHIPMODE     VARCHAR(10) /*CHAR(10)*/,
                             L_COMMENT      VARCHAR(44),
							 DUMMY TEXT)
				   LOCATION (%s)
				   FORMAT 'CSV' (DELIMITER '|')
				   `
		conn.Execute(fmt.Sprintf(lineitem, conf.Ext, locallf("lineitem")))
	})
}
<|endoftext|>


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

// Quick and Easy Performance Analyzer
// Useful for comparing A/B against different drafts of functions or different functions
// Loosely inspired by the go benchmark package
//
// Example:
//	import "github.com/spf13/nitro"
//	timer := nitro.Initialize()
//	prepTemplates()
//	timer.Step("initialize & template prep")
//	CreatePages()
//	timer.Step("import pages")
package nitro

import (
	"flag"
	"fmt"
	"runtime"
	"time"
)

// Used for every benchmark for measuring memory.
var memStats runtime.MemStats

var AnalysisOn = flag.Bool("stepAnalysis", false, "display memory and timing of different steps of the program")

type B struct {
	initialTime time.Time // Time entire process started
	start       time.Time // Time step started
	duration    time.Duration
	timerOn     bool
	result      R
	// The initial states of memStats.Mallocs and memStats.TotalAlloc.
	startAllocs uint64
	startBytes  uint64
	// The net total of this test after being run.
	netAllocs uint64
	netBytes  uint64
}

func (b *B) startTimer() {
	if !b.timerOn {
		runtime.ReadMemStats(&memStats)
		b.startAllocs = memStats.Mallocs
		b.startBytes = memStats.TotalAlloc
		b.start = time.Now()
		b.timerOn = true
	}
}

func (b *B) stopTimer() {
	if b.timerOn {
		b.duration += time.Since(b.start)
		runtime.ReadMemStats(&memStats)
		b.netAllocs += memStats.Mallocs - b.startAllocs
		b.netBytes += memStats.TotalAlloc - b.startBytes
		b.timerOn = false
	}
}

// ResetTimer sets the elapsed benchmark time to zero.
// It does not affect whether the timer is running.
func (b *B) resetTimer() {
	if b.timerOn {
		runtime.ReadMemStats(&memStats)
		b.startAllocs = memStats.Mallocs
		b.startBytes = memStats.TotalAlloc
		b.start = time.Now()
	}
	b.duration = 0
	b.netAllocs = 0
	b.netBytes = 0
}

// Call this first to get the performance object
// Should be called at the top of your function.
func Initialize() *B {
	if !*AnalysisOn {
		return nil
	}

	b := &B{}
	b.initialTime = time.Now()
	runtime.GC()
	b.resetTimer()
	b.startTimer()
	return b
}

// Call perf.Step("step name") at each step in your
// application you want to benchmark
// Measures time spent since last Step call.
func (b *B) Step(str string) {
	if !*AnalysisOn {
		return
	}

	b.stopTimer()
	fmt.Println(str + ":")
	fmt.Println(b.results().toString())

	b.resetTimer()
	b.startTimer()
}

func (b *B) results() R {
	return R{time.Since(b.initialTime), b.duration, b.netAllocs, b.netBytes}
}

type R struct {
	C         time.Duration // Cumulative time taken
	T         time.Duration // The total time taken.
	MemAllocs uint64        // The total number of memory allocations.
	MemBytes  uint64        // The total number of bytes allocated.
}

func (r R) mbPerSec() float64 {
	if r.MemBytes <= 0 || r.T <= 0 {
		return 0
	}

	return byteToMb(r.MemBytes) / r.T.Seconds()
}

func byteToMb(b uint64) float64 {
	if b <= 0 {
		return 0
	}
	return float64(b) / 1e6
}

func (r R) toString() string {
	time := fmt.Sprintf("%v (%5v)\t", r.T, r.C)
	mem := fmt.Sprintf("%7.2f MB \t%v Allocs", byteToMb(r.MemBytes), r.MemAllocs)
	return fmt.Sprintf("\t%s %s", time, mem)
}
<commit_msg>wrapper for Initalize<commit_after>// Copyright © 2013 Steve Francia <spf@spf13.com>.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
// Available at http://github.com/spf13/nitro

// Quick and Easy Performance Analyzer
// Useful for comparing A/B against different drafts of functions or different functions
// Loosely inspired by the go benchmark package
//
// Example:
//	import "github.com/spf13/nitro"
//	timer := nitro.Initialize()
//	prepTemplates()
//	timer.Step("initialize & template prep")
//	CreatePages()
//	timer.Step("import pages")
package nitro

import (
	"flag"
	"fmt"
	"runtime"
	"time"
)

// Used for every benchmark for measuring memory.
var memStats runtime.MemStats

var AnalysisOn = flag.Bool("stepAnalysis", false, "display memory and timing of different steps of the program")

type B struct {
	initialTime time.Time // Time entire process started
	start       time.Time // Time step started
	duration    time.Duration
	timerOn     bool
	result      R
	// The initial states of memStats.Mallocs and memStats.TotalAlloc.
	startAllocs uint64
	startBytes  uint64
	// The net total of this test after being run.
	netAllocs uint64
	netBytes  uint64
}

func (b *B) startTimer() {
	if !b.timerOn {
		runtime.ReadMemStats(&memStats)
		b.startAllocs = memStats.Mallocs
		b.startBytes = memStats.TotalAlloc
		b.start = time.Now()
		b.timerOn = true
	}
}

func (b *B) stopTimer() {
	if b.timerOn {
		b.duration += time.Since(b.start)
		runtime.ReadMemStats(&memStats)
		b.netAllocs += memStats.Mallocs - b.startAllocs
		b.netBytes += memStats.TotalAlloc - b.startBytes
		b.timerOn = false
	}
}

// ResetTimer sets the elapsed benchmark time to zero.
// It does not affect whether the timer is running.
func (b *B) resetTimer() {
	if b.timerOn {
		runtime.ReadMemStats(&memStats)
		b.startAllocs = memStats.Mallocs
		b.startBytes = memStats.TotalAlloc
		b.start = time.Now()
	}
	b.duration = 0
	b.netAllocs = 0
	b.netBytes = 0
}

// Call this first to get the performance object
// Should be called at the top of your function.
func Initialize() *B {
	if !*AnalysisOn {
		return nil
	}

	b := &B{}
	b.initialTime = time.Now()
	runtime.GC()
	b.resetTimer()
	b.startTimer()
	return b
}

// Simple wrapper for Initialize
// Maintain for legacy purposes
func Initalize() *B {
	return Initialize()
}

// Call perf.Step("step name") at each step in your
// application you want to benchmark
// Measures time spent since last Step call.
func (b *B) Step(str string) {
	if !*AnalysisOn {
		return
	}

	b.stopTimer()
	fmt.Println(str + ":")
	fmt.Println(b.results().toString())

	b.resetTimer()
	b.startTimer()
}

func (b *B) results() R {
	return R{time.Since(b.initialTime), b.duration, b.netAllocs, b.netBytes}
}

type R struct {
	C         time.Duration // Cumulative time taken
	T         time.Duration // The total time taken.
	MemAllocs uint64        // The total number of memory allocations.
	MemBytes  uint64        // The total number of bytes allocated.
}

func (r R) mbPerSec() float64 {
	if r.MemBytes <= 0 || r.T <= 0 {
		return 0
	}

	return byteToMb(r.MemBytes) / r.T.Seconds()
}

func byteToMb(b uint64) float64 {
	if b <= 0 {
		return 0
	}
	return float64(b) / 1e6
}

func (r R) toString() string {
	time := fmt.Sprintf("%v (%5v)\t", r.T, r.C)
	mem := fmt.Sprintf("%7.2f MB \t%v Allocs", byteToMb(r.MemBytes), r.MemAllocs)
	return fmt.Sprintf("\t%s %s", time, mem)
}
<|endoftext|>


<commit_before>package stripe

import (
	"strconv"

	"github.com/stripe/stripe-go/form"
)

// PlanInterval is the list of allowed values for a plan's interval.
type PlanInterval string

// List of values that PlanInterval can take.
const (
	PlanIntervalDay   PlanInterval = "day"
	PlanIntervalWeek  PlanInterval = "week"
	PlanIntervalMonth PlanInterval = "month"
	PlanIntervalYear  PlanInterval = "year"
)

// PlanBillingScheme is the list of allowed values for a plan's billing scheme.
type PlanBillingScheme string

// List of values that PlanBillingScheme can take.
const (
	PlanBillingSchemePerUnit PlanBillingScheme = "per_unit"
	PlanBillingSchemeTiered  PlanBillingScheme = "tiered"
)

// PlanUsageType is the list of allowed values for a plan's usage type.
type PlanUsageType string

// List of values that PlanUsageType can take.
const (
	PlanUsageTypeLicensed PlanUsageType = "licensed"
	PlanUsageTypeMetered  PlanUsageType = "metered"
)

// PlanTiersMode is the list of allowed values for a plan's tiers mode.
type PlanTiersMode string

// List of values that PlanTiersMode can take.
const (
	PlanTiersModeGraduated PlanTiersMode = "graduated"
	PlanTiersModeVolume    PlanTiersMode = "volume"
)

// PlanTransformUsageRound is the list of allowed values for a plan's transform usage round logic.
type PlanTransformUsageRound string

// List of values that PlanTransformUsageRound can take.
const (
	PlanTransformUsageRoundDown PlanTransformUsageRound = "down"
	PlanTransformUsageRoundUp   PlanTransformUsageRound = "up"
)

// PlanAggregateUsage is the list of allowed values for a plan's aggregate usage.
type PlanAggregateUsage string

// List of values that PlanAggregateUsage can take.
const (
	PlanAggregateUsageLastDuringPeriod PlanAggregateUsage = "last_during_period"
	PlanAggregateUsageLastEver         PlanAggregateUsage = "last_ever"
	PlanAggregateUsageMax              PlanAggregateUsage = "max"
	PlanAggregateUsageSum              PlanAggregateUsage = "sum"
)

// Plan is the resource representing a Stripe plan.
// For more details see https://stripe.com/docs/api#plans.
type Plan struct {
	Active          bool                `json:"active"`
	AggregateUsage  string              `json:"aggregate_usage"`
	Amount          int64               `json:"amount"`
	BillingScheme   PlanBillingScheme   `json:"billing_scheme"`
	Created         int64               `json:"created"`
	Currency        Currency            `json:"currency"`
	Deleted         bool                `json:"deleted"`
	ID              string              `json:"id"`
	Interval        PlanInterval        `json:"interval"`
	IntervalCount   int64               `json:"interval_count"`
	Livemode        bool                `json:"livemode"`
	Metadata        map[string]string   `json:"metadata"`
	Nickname        string              `json:"nickname"`
	Product         string              `json:"product"`
	Tiers           []*PlanTier         `json:"tiers"`
	TiersMode       string              `json:"tiers_mode"`
	TransformUsage  *PlanTransformUsage `json:"transform_usage"`
	TrialPeriodDays int64               `json:"trial_period_days"`
	UsageType       PlanUsageType       `json:"usage_type"`
}

// PlanList is a list of plans as returned from a list endpoint.
type PlanList struct {
	ListMeta
	Data []*Plan `json:"data"`
}

// PlanListParams is the set of parameters that can be used when listing plans.
// For more details see https://stripe.com/docs/api#list_plans.
type PlanListParams struct {
	ListParams   `form:"*"`
	Active       *bool             `form:"active"`
	Created      *int64            `form:"created"`
	CreatedRange *RangeQueryParams `form:"created"`
	Product      *string           `form:"product"`
}

// PlanParams is the set of parameters that can be used when creating or updating a plan.
// For more details see https://stripe.com/docs/api#create_plan and https://stripe.com/docs/api#update_plan.
type PlanParams struct {
	Params          `form:"*"`
	Active          *bool                     `form:"active"`
	AggregateUsage  *string                   `form:"aggregate_usage"`
	Amount          *int64                    `form:"amount"`
	BillingScheme   *string                   `form:"billing_scheme"`
	Currency        *string                   `form:"currency"`
	ID              *string                   `form:"id"`
	Interval        *string                   `form:"interval"`
	IntervalCount   *int64                    `form:"interval_count"`
	Nickname        *string                   `form:"nickname"`
	Product         *PlanProductParams        `form:"product"`
	ProductID       *string                   `form:"product"`
	Tiers           []*PlanTierParams         `form:"tiers"`
	TiersMode       *string                   `form:"tiers_mode"`
	TransformUsage  *PlanTransformUsageParams `form:"transform_usage"`
	TrialPeriodDays *int64                    `form:"trial_period_days"`
	UsageType       *string                   `form:"usage_type"`
}

// PlanTier configures tiered pricing
type PlanTier struct {
	UnitAmount int64 `json:"unit_amount"`
	UpTo       int64 `json:"up_to"`
}

// PlanTransformUsage represents the bucket billing configuration.
type PlanTransformUsage struct {
	DivideBy int64                   `json:"divide_by"`
	Round    PlanTransformUsageRound `json:"round"`
}

// PlanTransformUsageParams represents the bucket billing configuration.
type PlanTransformUsageParams struct {
	DivideBy *int64  `form:"divide_by"`
	Round    *string `form:"round"`
}

// PlanTierParams configures tiered pricing
type PlanTierParams struct {
	Params     `form:"*"`
	UnitAmount *int64 `form:"unit_amount"`
	UpTo       *int64 `form:"-"` // handled in custom AppendTo
	UpToInf    *bool  `form:"-"` // handled in custom AppendTo
}

// AppendTo implements custom up_to serialisation logic for tiers configuration
func (p *PlanTierParams) AppendTo(body *form.Values, keyParts []string) {
	if BoolValue(p.UpToInf) {
		body.Add(form.FormatKey(append(keyParts, "up_to")), "inf")
	} else {
		body.Add(form.FormatKey(append(keyParts, "up_to")), strconv.FormatInt(Int64Value(p.UpTo), 10))
	}
}

// PlanProductParams is the set of parameters that can be used when creating a product inside a plan
// This can only be used on plan creation and won't work on plan update.
// For more details see https://stripe.com/docs/api#create_plan-product and https://stripe.com/docs/api#update_plan-product
type PlanProductParams struct {
	ID                  *string           `form:"id"`
	Name                *string           `form:"name"`
	Metadata            map[string]string `form:"metadata"`
	StatementDescriptor *string           `form:"statement_descriptor"`
}
<commit_msg>Support expansion of products in plans<commit_after>package stripe

import (
	"strconv"

	"github.com/stripe/stripe-go/form"
)

// PlanInterval is the list of allowed values for a plan's interval.
type PlanInterval string

// List of values that PlanInterval can take.
const (
	PlanIntervalDay   PlanInterval = "day"
	PlanIntervalWeek  PlanInterval = "week"
	PlanIntervalMonth PlanInterval = "month"
	PlanIntervalYear  PlanInterval = "year"
)

// PlanBillingScheme is the list of allowed values for a plan's billing scheme.
type PlanBillingScheme string

// List of values that PlanBillingScheme can take.
const (
	PlanBillingSchemePerUnit PlanBillingScheme = "per_unit"
	PlanBillingSchemeTiered  PlanBillingScheme = "tiered"
)

// PlanUsageType is the list of allowed values for a plan's usage type.
type PlanUsageType string

// List of values that PlanUsageType can take.
const (
	PlanUsageTypeLicensed PlanUsageType = "licensed"
	PlanUsageTypeMetered  PlanUsageType = "metered"
)

// PlanTiersMode is the list of allowed values for a plan's tiers mode.
type PlanTiersMode string

// List of values that PlanTiersMode can take.
const (
	PlanTiersModeGraduated PlanTiersMode = "graduated"
	PlanTiersModeVolume    PlanTiersMode = "volume"
)

// PlanTransformUsageRound is the list of allowed values for a plan's transform usage round logic.
type PlanTransformUsageRound string

// List of values that PlanTransformUsageRound can take.
const (
	PlanTransformUsageRoundDown PlanTransformUsageRound = "down"
	PlanTransformUsageRoundUp   PlanTransformUsageRound = "up"
)

// PlanAggregateUsage is the list of allowed values for a plan's aggregate usage.
type PlanAggregateUsage string

// List of values that PlanAggregateUsage can take.
const (
	PlanAggregateUsageLastDuringPeriod PlanAggregateUsage = "last_during_period"
	PlanAggregateUsageLastEver         PlanAggregateUsage = "last_ever"
	PlanAggregateUsageMax              PlanAggregateUsage = "max"
	PlanAggregateUsageSum              PlanAggregateUsage = "sum"
)

// Plan is the resource representing a Stripe plan.
// For more details see https://stripe.com/docs/api#plans.
type Plan struct {
	Active          bool                `json:"active"`
	AggregateUsage  string              `json:"aggregate_usage"`
	Amount          int64               `json:"amount"`
	BillingScheme   PlanBillingScheme   `json:"billing_scheme"`
	Created         int64               `json:"created"`
	Currency        Currency            `json:"currency"`
	Deleted         bool                `json:"deleted"`
	ID              string              `json:"id"`
	Interval        PlanInterval        `json:"interval"`
	IntervalCount   int64               `json:"interval_count"`
	Livemode        bool                `json:"livemode"`
	Metadata        map[string]string   `json:"metadata"`
	Nickname        string              `json:"nickname"`
	Product         *Product            `json:"product"`
	Tiers           []*PlanTier         `json:"tiers"`
	TiersMode       string              `json:"tiers_mode"`
	TransformUsage  *PlanTransformUsage `json:"transform_usage"`
	TrialPeriodDays int64               `json:"trial_period_days"`
	UsageType       PlanUsageType       `json:"usage_type"`
}

// PlanList is a list of plans as returned from a list endpoint.
type PlanList struct {
	ListMeta
	Data []*Plan `json:"data"`
}

// PlanListParams is the set of parameters that can be used when listing plans.
// For more details see https://stripe.com/docs/api#list_plans.
type PlanListParams struct {
	ListParams   `form:"*"`
	Active       *bool             `form:"active"`
	Created      *int64            `form:"created"`
	CreatedRange *RangeQueryParams `form:"created"`
	Product      *string           `form:"product"`
}

// PlanParams is the set of parameters that can be used when creating or updating a plan.
// For more details see https://stripe.com/docs/api#create_plan and https://stripe.com/docs/api#update_plan.
type PlanParams struct {
	Params          `form:"*"`
	Active          *bool                     `form:"active"`
	AggregateUsage  *string                   `form:"aggregate_usage"`
	Amount          *int64                    `form:"amount"`
	BillingScheme   *string                   `form:"billing_scheme"`
	Currency        *string                   `form:"currency"`
	ID              *string                   `form:"id"`
	Interval        *string                   `form:"interval"`
	IntervalCount   *int64                    `form:"interval_count"`
	Nickname        *string                   `form:"nickname"`
	Product         *PlanProductParams        `form:"product"`
	ProductID       *string                   `form:"product"`
	Tiers           []*PlanTierParams         `form:"tiers"`
	TiersMode       *string                   `form:"tiers_mode"`
	TransformUsage  *PlanTransformUsageParams `form:"transform_usage"`
	TrialPeriodDays *int64                    `form:"trial_period_days"`
	UsageType       *string                   `form:"usage_type"`
}

// PlanTier configures tiered pricing
type PlanTier struct {
	UnitAmount int64 `json:"unit_amount"`
	UpTo       int64 `json:"up_to"`
}

// PlanTransformUsage represents the bucket billing configuration.
type PlanTransformUsage struct {
	DivideBy int64                   `json:"divide_by"`
	Round    PlanTransformUsageRound `json:"round"`
}

// PlanTransformUsageParams represents the bucket billing configuration.
type PlanTransformUsageParams struct {
	DivideBy *int64  `form:"divide_by"`
	Round    *string `form:"round"`
}

// PlanTierParams configures tiered pricing
type PlanTierParams struct {
	Params     `form:"*"`
	UnitAmount *int64 `form:"unit_amount"`
	UpTo       *int64 `form:"-"` // handled in custom AppendTo
	UpToInf    *bool  `form:"-"` // handled in custom AppendTo
}

// AppendTo implements custom up_to serialisation logic for tiers configuration
func (p *PlanTierParams) AppendTo(body *form.Values, keyParts []string) {
	if BoolValue(p.UpToInf) {
		body.Add(form.FormatKey(append(keyParts, "up_to")), "inf")
	} else {
		body.Add(form.FormatKey(append(keyParts, "up_to")), strconv.FormatInt(Int64Value(p.UpTo), 10))
	}
}

// PlanProductParams is the set of parameters that can be used when creating a product inside a plan
// This can only be used on plan creation and won't work on plan update.
// For more details see https://stripe.com/docs/api#create_plan-product and https://stripe.com/docs/api#update_plan-product
type PlanProductParams struct {
	ID                  *string           `form:"id"`
	Name                *string           `form:"name"`
	Metadata            map[string]string `form:"metadata"`
	StatementDescriptor *string           `form:"statement_descriptor"`
}
<|endoftext|>


<commit_before>package plexible

// MediaContainer is the top-level struct most Plex communication stanzas.
type MediaContainer struct {
	CommandID         string     `xml:"commandID,attr,omitempty"`
	MachineIdentifier string     `xml:"machineIdentifier,attr,omitempty"`
	Timelines         []Timeline `xml:"Timeline,omitempty"`
	Players           []player   `xml:"Player,omitempty"`
	Tracks            []Track    `xml:"Track,omitempty"`
}

// Track is an audio track in a MediaContainer.
type Track struct {
	PlayQueueItemID      int    `xml:"playQueueItemID,attr,omitempty"`
	RatingKey            int    `xml:"ratingKey,attr,omitempty"`
	Key                  string `xml:"key,attr,omitempty"`
	ParentRatingKey      int    `xml:"parentRatingKey,attr,omitempty"`
	GrandparentRatingKey int    `xml:"grandparentRatingKey,attr,omitempty"`
	GUID                 string `xml:"guid,attr,omitempty"`
	Type                 string `xml:"type_,attr,omitempty"`
	Title                string `xml:"title,attr,omitempty"`
	TitleSort            string `xml:"titleSort,attr,omitempty"`
	GrandparentKey       string `xml:"grandparentKey,attr,omitempty"`
	ParentKey            string `xml:"parentKey,attr,omitempty"`
	GrandparentTitle     string `xml:"grandparentTitle,attr,omitempty"`
	ParentTitle          string `xml:"parentTitle,attr,omitempty"`
	OriginalTitle        string `xml:"originalTitle,attr,omitempty"`
	Summary              string `xml:"summary,attr,omitempty"`
	Index                int    `xml:"index,attr,omitempty"`
	ParentIndex          int    `xml:"parentIndex,attr,omitempty"`
	ViewCount            int    `xml:"viewCount,attr,omitempty"`
	LastViewedAt         int    `xml:"lastViewedAt,attr,omitempty"`
	Thumb                string `xml:"thumb,attr,omitempty"`
	ParentThumb          string `xml:"parentThumb,attr,omitempty"`
	GrandparentThumb     string `xml:"grandparentThumb,attr,omitempty"`
	Duration             uint64 `xml:"duration,attr,omitempty"`
	AddedAt              int    `xml:"addedAt,attr,omitempty"`
	UpdatedAt            int    `xml:"updatedAt,attr,omitempty"`
	Media                *Media `xml:"Media,omitempty"`
}

// Media is an audio track media element.
type Media struct {
	ID            int    `xml:"id,attr,omitempty"`
	Duration      uint64 `xml:"duration,attr,omitempty"`
	Bitrate       int    `xml:"bitrate,attr,omitempty"`
	AudioChannels int    `xml:"audioChannels,attr,omitempty"`
	AudioCodec    string `xml:"audioCodec,attr,omitempty"`
	Container     string `xml:"container,attr,omitempty"`
	Part          *Part  `xml:"Part,omitempty"`
}

// Part is an audo track media part.
type Part struct {
	ID        int      `xml:"id,attr,omitempty"`
	Key       string   `xml:"key,attr,omitempty"`
	Duration  uint64   `xml:"duration,attr,omitempty"`
	File      string   `xml:"file,attr,omitempty"`
	Size      int      `xml:"size,attr,omitempty"`
	Container string   `xml:"container,attr,omitempty"`
	Streams   []Stream `xml:"Stream,omitempty"`
}

// Stream is an audio track media stream.
type Stream struct {
	ID           int    `xml:"id,attr,omitempty"`
	StreamType   int    `xml:"streamType,attr,omitempty"`
	Selected     int    `xml:"selected,attr,omitempty"`
	Codec        string `xml:"codec,attr,omitempty"`
	Index        int    `xml:"index,attr,omitempty"`
	Channels     int    `xml:"channels,attr,omitempty"`
	Bitrate      int    `xml:"bitrate,attr,omitempty"`
	BitrateMode  string `xml:"bitrateMode,attr,omitempty"`
	Duration     uint64 `xml:"duration,attr,omitempty"`
	SamplingRate int    `xml:"samplingRate,attr,omitempty"`
}

// Player capabilities.
const (
	CapabilityTimeline   = "timeline"
	CapabilityPlayback   = "playback"
	CapabilityNavigation = "navigation"
	CapabilityMirror     = "mirror"
	CapabilityPlayQueues = "playqueues"
)

// PlayerTimeline repesents the state of a Player. It does not include the
// fields that are better for the Client to add.
type PlayerTimeline struct {
	State    string `xml:"state,attr,omitempty"`
	Duration uint64 `xml:"duration,attr,omitempty"`
	Time     uint64 `xml:"time,attr,omitempty"`
}

// Timeline repesents the current state of a Player, including attributes
// better handled by the Client.
type Timeline struct {
	*PlayerTimeline
	Type string `xml:"type,attr,omitempty"`
}

// Player types.
const (
	TypeMusic = "music"
	TypePhoto = "photo"
	TypeVideo = "video"
)

// Timeline states.
const (
	StateStopped   = "stopped"
	StatePaused    = "paused"
	StatePlaying   = "playing"
	StateBuffering = "buffering"
	StateError     = "error"
)

const (
	discoveryIP         = "239.0.0.250"
	clientDiscoveryPort = 32412
	clientBroadcastPort = 32413
	serverDiscoveryPort = 32414
)

type player struct {
	Title                string `xml:"title,attr"`
	MachineIdentifier    string `xml:"machineIdentifier,attr"`
	Product              string `xml:"product,attr"`
	Version              string `xml:"version,attr"`
	ProtocolVersion      string `xml:"protocolVersion,attr"`
	ProtocolCapabilities string `xml:"protocolCapabilities,attr"`
	DeviceClass          string `xml:"deviceClass,attr"`
}

// PlayMediaCommand is sent to a player to start playback of new media.
type PlayMediaCommand struct {
	ServerURL      string
	MediaContainer *MediaContainer
	ContainerKey   string
	Key            string
	Offset         uint64
}

// PauseCommand is sent to a player to pause playback.
type PauseCommand struct {
}

// PlayCommand is sent to a player to resume playback.
type PlayCommand struct {
}

// StopCommand is sent to a player to stop playback.
type StopCommand struct {
}
<commit_msg>Extend PlayerTimeline to include just enough for working updates.<commit_after>package plexible

// MediaContainer is the top-level struct most Plex communication stanzas.
type MediaContainer struct {
	CommandID         string     `xml:"commandID,attr,omitempty"`
	MachineIdentifier string     `xml:"machineIdentifier,attr,omitempty"`
	Timelines         []Timeline `xml:"Timeline,omitempty"`
	Players           []player   `xml:"Player,omitempty"`
	Tracks            []Track    `xml:"Track,omitempty"`
}

// Track is an audio track in a MediaContainer.
type Track struct {
	PlayQueueItemID      int    `xml:"playQueueItemID,attr,omitempty"`
	RatingKey            int    `xml:"ratingKey,attr,omitempty"`
	Key                  string `xml:"key,attr,omitempty"`
	ParentRatingKey      int    `xml:"parentRatingKey,attr,omitempty"`
	GrandparentRatingKey int    `xml:"grandparentRatingKey,attr,omitempty"`
	GUID                 string `xml:"guid,attr,omitempty"`
	Type                 string `xml:"type_,attr,omitempty"`
	Title                string `xml:"title,attr,omitempty"`
	TitleSort            string `xml:"titleSort,attr,omitempty"`
	GrandparentKey       string `xml:"grandparentKey,attr,omitempty"`
	ParentKey            string `xml:"parentKey,attr,omitempty"`
	GrandparentTitle     string `xml:"grandparentTitle,attr,omitempty"`
	ParentTitle          string `xml:"parentTitle,attr,omitempty"`
	OriginalTitle        string `xml:"originalTitle,attr,omitempty"`
	Summary              string `xml:"summary,attr,omitempty"`
	Index                int    `xml:"index,attr,omitempty"`
	ParentIndex          int    `xml:"parentIndex,attr,omitempty"`
	ViewCount            int    `xml:"viewCount,attr,omitempty"`
	LastViewedAt         int    `xml:"lastViewedAt,attr,omitempty"`
	Thumb                string `xml:"thumb,attr,omitempty"`
	ParentThumb          string `xml:"parentThumb,attr,omitempty"`
	GrandparentThumb     string `xml:"grandparentThumb,attr,omitempty"`
	Duration             uint64 `xml:"duration,attr,omitempty"`
	AddedAt              int    `xml:"addedAt,attr,omitempty"`
	UpdatedAt            int    `xml:"updatedAt,attr,omitempty"`
	Media                *Media `xml:"Media,omitempty"`
}

// Media is an audio track media element.
type Media struct {
	ID            int    `xml:"id,attr,omitempty"`
	Duration      uint64 `xml:"duration,attr,omitempty"`
	Bitrate       int    `xml:"bitrate,attr,omitempty"`
	AudioChannels int    `xml:"audioChannels,attr,omitempty"`
	AudioCodec    string `xml:"audioCodec,attr,omitempty"`
	Container     string `xml:"container,attr,omitempty"`
	Part          *Part  `xml:"Part,omitempty"`
}

// Part is an audo track media part.
type Part struct {
	ID        int      `xml:"id,attr,omitempty"`
	Key       string   `xml:"key,attr,omitempty"`
	Duration  uint64   `xml:"duration,attr,omitempty"`
	File      string   `xml:"file,attr,omitempty"`
	Size      int      `xml:"size,attr,omitempty"`
	Container string   `xml:"container,attr,omitempty"`
	Streams   []Stream `xml:"Stream,omitempty"`
}

// Stream is an audio track media stream.
type Stream struct {
	ID           int    `xml:"id,attr,omitempty"`
	StreamType   int    `xml:"streamType,attr,omitempty"`
	Selected     int    `xml:"selected,attr,omitempty"`
	Codec        string `xml:"codec,attr,omitempty"`
	Index        int    `xml:"index,attr,omitempty"`
	Channels     int    `xml:"channels,attr,omitempty"`
	Bitrate      int    `xml:"bitrate,attr,omitempty"`
	BitrateMode  string `xml:"bitrateMode,attr,omitempty"`
	Duration     uint64 `xml:"duration,attr,omitempty"`
	SamplingRate int    `xml:"samplingRate,attr,omitempty"`
}

// Player capabilities.
const (
	CapabilityTimeline   = "timeline"
	CapabilityPlayback   = "playback"
	CapabilityNavigation = "navigation"
	CapabilityMirror     = "mirror"
	CapabilityPlayQueues = "playqueues"
)

// PlayerTimeline repesents the state of a Player. It does not include the
// fields that are better for the Client to add.
type PlayerTimeline struct {
	State        string `xml:"state,attr,omitempty"`
	Duration     uint64 `xml:"duration,attr,omitempty"`
	Time         uint64 `xml:"time,attr,omitempty"`
	RatingKey    int    `xml:"ratingKey,attr,omitempty"`
	Key          string `xml:"key,attr,omitempty"`
	ContainerKey string `xml:"containerKey,attr,omitempty"`
}

// Timeline repesents the current state of a Player, including attributes
// better handled by the Client.
type Timeline struct {
	*PlayerTimeline
	Type string `xml:"type,attr,omitempty"`
}

// Player types.
const (
	TypeMusic = "music"
	TypePhoto = "photo"
	TypeVideo = "video"
)

// Timeline states.
const (
	StateStopped   = "stopped"
	StatePaused    = "paused"
	StatePlaying   = "playing"
	StateBuffering = "buffering"
	StateError     = "error"
)

const (
	discoveryIP         = "239.0.0.250"
	clientDiscoveryPort = 32412
	clientBroadcastPort = 32413
	serverDiscoveryPort = 32414
)

type player struct {
	Title                string `xml:"title,attr"`
	MachineIdentifier    string `xml:"machineIdentifier,attr"`
	Product              string `xml:"product,attr"`
	Version              string `xml:"version,attr"`
	ProtocolVersion      string `xml:"protocolVersion,attr"`
	ProtocolCapabilities string `xml:"protocolCapabilities,attr"`
	DeviceClass          string `xml:"deviceClass,attr"`
}

// PlayMediaCommand is sent to a player to start playback of new media.
type PlayMediaCommand struct {
	ServerURL      string
	MediaContainer *MediaContainer
	ContainerKey   string
	Key            string
	Offset         uint64
}

// PauseCommand is sent to a player to pause playback.
type PauseCommand struct {
}

// PlayCommand is sent to a player to resume playback.
type PlayCommand struct {
}

// StopCommand is sent to a player to stop playback.
type StopCommand struct {
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/codegangsta/cli"
)

type PollResponse struct {
	Links struct {
		Poll struct {
			Href string
		}
	}
}

var verbose = false

func main() {

	app := cli.NewApp()
	app.Name = "poll"
	app.Version = "0.0.1"
	app.Usage = "Call an API that returns a poll URL, and then poll that URL until a 200 or error"
	app.Authors = []cli.Author{
		{Name: "Rob Warner", Email: "rwarner@grailbox.com"},
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "user, u",
			EnvVar: "REMOTE_USER",
			Usage:  "authorized user",
		},
		cli.StringFlag{
			Name:   "baseurl, b",
			EnvVar: "POLL_BASE_URL",
			Usage:  "the base URL for the initial request",
		},
		cli.IntFlag{
			Name:   "interval, i",
			EnvVar: "POLL_INTERVAL",
			Usage:  "poll interval, in seconds",
		},
		cli.IntFlag{
			Name:   "maxtries, m",
			EnvVar: "POLL_MAX_TRIES",
			Usage:  "the maximum number of times to poll",
		},
		cli.BoolFlag{
			Name:   "verbose, V",
			EnvVar: "POLL_VERBOSE",
			Usage:  "run in verbose mode",
		},
	}

	app.Action = func(c *cli.Context) {
		user := c.String("user")
		baseurl := c.String("baseurl")
		if baseurl == "" {
			baseurl = "http://localhost:8280/v1/"
		}
		interval := c.Int("interval")
		if interval == 0 {
			interval = 1
		}
		maxtries := c.Int("maxtries")
		if maxtries == 0 {
			maxtries = 10
		}
		verbose = c.Bool("verbose")

		if len(c.Args()) > 0 {
			url := fmt.Sprint(baseurl, c.Args()[0])
			sc, location, json, err := getUrl(user, url)
			handleError(err)

			if sc == 202 {
				var pollurl string
				if location != nil {
					pollurl = location.String()
					log(fmt.Sprint("Using location header ", pollurl))
				} else {
					pollurl, err = parse(json)
					handleError(err)
					log(fmt.Sprint("Using URL from JSON ", pollurl))
				}

				s := "seconds"
				if interval == 1 {
					s = "second"
				}
				for i := 0; i < maxtries; i++ {
					log(fmt.Sprint("Sleeping ", interval, " ", s, "..."))
					time.Sleep(time.Second * time.Duration(interval))
					log(fmt.Sprint("Poll #", (i + 1), "..."))
					sc, _, json, err = getUrl(user, pollurl)
					handleError(err)
					if sc != 202 {
						break
					}
				}
			}
      log(fmt.Sprint("Status Code:", sc, "\n"))
			fmt.Println(string(json))
		} else {
			fmt.Println("Missing URL parameter")
		}
	}

	app.Run(os.Args)
}

func handleError(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func log(message string) {
	if verbose {
		fmt.Println(message)
	}
}

func getUrl(user string, url string) (int, *url.URL, []byte, error) {
	log(fmt.Sprint("Getting url ", url, " as user ", user))

	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("RemoteUser", user)
	resp, err := client.Do(req)
	if err != nil {
		return 0, nil, nil, err
	}

	defer resp.Body.Close()
	payload, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, nil, nil, err
	}
	location, _ := resp.Location()
	return resp.StatusCode, location, payload, nil
}

func parse(b []byte) (string, error) {
	var pollResponse PollResponse
	err := json.Unmarshal(b, &pollResponse)
	handleError(err)

	if pollResponse.Links.Poll.Href != "" {
		return pollResponse.Links.Poll.Href, nil
	}
	return "", errors.New(fmt.Sprint("No poll url in response:\n\n", string(b)))
}
<commit_msg>Fix indentation<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/codegangsta/cli"
)

type PollResponse struct {
	Links struct {
		Poll struct {
			Href string
		}
	}
}

var verbose = false

func main() {

	app := cli.NewApp()
	app.Name = "poll"
	app.Version = "0.0.1"
	app.Usage = "Call an API that returns a poll URL, and then poll that URL until a 200 or error"
	app.Authors = []cli.Author{
		{Name: "Rob Warner", Email: "rwarner@grailbox.com"},
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "user, u",
			EnvVar: "REMOTE_USER",
			Usage:  "authorized user",
		},
		cli.StringFlag{
			Name:   "baseurl, b",
			EnvVar: "POLL_BASE_URL",
			Usage:  "the base URL for the initial request",
		},
		cli.IntFlag{
			Name:   "interval, i",
			EnvVar: "POLL_INTERVAL",
			Usage:  "poll interval, in seconds",
		},
		cli.IntFlag{
			Name:   "maxtries, m",
			EnvVar: "POLL_MAX_TRIES",
			Usage:  "the maximum number of times to poll",
		},
		cli.BoolFlag{
			Name:   "verbose, V",
			EnvVar: "POLL_VERBOSE",
			Usage:  "run in verbose mode",
		},
	}

	app.Action = func(c *cli.Context) {
		user := c.String("user")
		baseurl := c.String("baseurl")
		if baseurl == "" {
			baseurl = "http://localhost:8280/v1/"
		}
		interval := c.Int("interval")
		if interval == 0 {
			interval = 1
		}
		maxtries := c.Int("maxtries")
		if maxtries == 0 {
			maxtries = 10
		}
		verbose = c.Bool("verbose")

		if len(c.Args()) > 0 {
			url := fmt.Sprint(baseurl, c.Args()[0])
			sc, location, json, err := getUrl(user, url)
			handleError(err)

			if sc == 202 {
				var pollurl string
				if location != nil {
					pollurl = location.String()
					log(fmt.Sprint("Using location header ", pollurl))
				} else {
					pollurl, err = parse(json)
					handleError(err)
					log(fmt.Sprint("Using URL from JSON ", pollurl))
				}

				s := "seconds"
				if interval == 1 {
					s = "second"
				}
				for i := 0; i < maxtries; i++ {
					log(fmt.Sprint("Sleeping ", interval, " ", s, "..."))
					time.Sleep(time.Second * time.Duration(interval))
					log(fmt.Sprint("Poll #", (i + 1), "..."))
					sc, _, json, err = getUrl(user, pollurl)
					handleError(err)
					if sc != 202 {
						break
					}
				}
			}
			log(fmt.Sprint("Status Code:", sc, "\n"))
			fmt.Println(string(json))
		} else {
			fmt.Println("Missing URL parameter")
		}
	}

	app.Run(os.Args)
}

func handleError(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func log(message string) {
	if verbose {
		fmt.Println(message)
	}
}

func getUrl(user string, url string) (int, *url.URL, []byte, error) {
	log(fmt.Sprint("Getting url ", url, " as user ", user))

	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("RemoteUser", user)
	resp, err := client.Do(req)
	if err != nil {
		return 0, nil, nil, err
	}

	defer resp.Body.Close()
	payload, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, nil, nil, err
	}
	location, _ := resp.Location()
	return resp.StatusCode, location, payload, nil
}

func parse(b []byte) (string, error) {
	var pollResponse PollResponse
	err := json.Unmarshal(b, &pollResponse)
	handleError(err)

	if pollResponse.Links.Poll.Href != "" {
		return pollResponse.Links.Poll.Href, nil
	}
	return "", errors.New(fmt.Sprint("No poll url in response:\n\n", string(b)))
}
<|endoftext|>


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

package renameio

import (
	"io/ioutil"
	"os"
	"path/filepath"
)

// TempDir checks whether os.TempDir() can be used as a temporary directory for
// later atomically replacing files within dest. If no (os.TempDir() resides on
// a different mount point), dest is returned.
//
// Note that the returned value ceases to be valid once either os.TempDir()
// changes (e.g. on Linux, once the TMPDIR environment variable changes) or the
// file system is unmounted.
func TempDir(dest string) string {
	return tempDir("", dest)
}

func tempDir(dir, dest string) string {
	if dir != "" {
		return dir // caller-specified directory always wins
	}

	// Chose the destination directory as temporary directory so that we
	// definitely can rename the file, for which both temporary and destination
	// file need to point to the same mount point.
	fallback := filepath.Dir(dest)

	// The user might have overridden the os.TempDir() return value by setting
	// the TMPDIR environment variable.
	tmpdir := os.TempDir()

	testsrc, err := ioutil.TempFile(tmpdir, "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	cleanup := true
	defer func() {
		if cleanup {
			os.Remove(testsrc.Name())
		}
	}()
	testsrc.Close()

	testdest, err := ioutil.TempFile(filepath.Dir(dest), "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	defer os.Remove(testdest.Name())
	testdest.Close()

	if err := os.Rename(testsrc.Name(), testdest.Name()); err != nil {
		return fallback
	}
	cleanup = false // testsrc no longer exists
	return tmpdir
}

// PendingFile is a pending temporary file, waiting to replace the destination
// path in a call to CloseAtomicallyReplace.
type PendingFile struct {
	*os.File

	path   string
	done   bool
	closed bool
}

// Cleanup is a no-op if CloseAtomicallyReplace succeeded, and otherwise closes
// and removes the temporary file.
func (t *PendingFile) Cleanup() error {
	if t.done {
		return nil
	}
	// An error occurred. Close and remove the tempfile. Errors are returned for
	// reporting, there is nothing the caller can recover here.
	var closeErr error
	if !t.closed {
		closeErr = t.Close()
	}
	if err := os.Remove(t.Name()); err != nil {
		return err
	}
	return closeErr
}

// CloseAtomicallyReplace closes the temporary file and atomatically replaces
// the destination file with it, i.e., a concurrent open(2) call will either
// open the file previously located at the destination path (if any), or the
// just written file, but the file will always be present.
func (t *PendingFile) CloseAtomicallyReplace() error {
	// Even on an ordered file system (e.g. ext4 with data=ordered) or file
	// systems with write barriers, we cannot skip the fsync(2) call as per
	// Theodore Ts'o (ext2/3/4 lead developer):
	//
	// > data=ordered only guarantees the avoidance of stale data (e.g., the previous
	// > contents of a data block showing up after a crash, where the previous data
	// > could be someone's love letters, medical records, etc.). Without the fsync(2)
	// > a zero-length file is a valid and possible outcome after the rename.
	if err := t.Sync(); err != nil {
		return err
	}
	t.closed = true
	if err := t.Close(); err != nil {
		return err
	}
	if err := os.Rename(t.Name(), t.path); err != nil {
		return err
	}
	t.done = true
	return nil
}

// TempFile wraps ioutil.TempFile for the use case of atomically creating or
// replacing the destination file at path.
//
// If dir is the empty string, TempDir(filepath.Base(path)) is used. If you are
// going to write a large number of files to the same file system, store the
// result of TempDir(filepath.Base(path)) and pass it instead of the empty
// string.
//
// The file's permissions will be 0600 by default. You can change these by
// explictly calling Chmod on the returned PendingFile.
func TempFile(dir, path string) (*PendingFile, error) {
	f, err := ioutil.TempFile(tempDir(dir, path), "."+filepath.Base(path))
	if err != nil {
		return nil, err
	}

	return &PendingFile{File: f, path: path}, nil
}

// Symlink wraps os.Symlink, replacing an existing symlink with the same name
// atomically (os.Symlink fails when newname already exists, at least on Linux).
func Symlink(oldname, newname string) error {
	// Fast path: if newname does not exist yet, we can skip the whole dance
	// below.
	if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) {
		return err
	}

	// We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
	// and removing+symlinking creates a TOCTOU race.
	d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname))
	if err != nil {
		return err
	}
	defer os.RemoveAll(d)

	symlink := filepath.Join(d, "tmp.symlink")
	if err := os.Symlink(oldname, symlink); err != nil {
		return err
	}

	if err := os.Rename(symlink, newname); err != nil {
		return err
	}

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

package renameio

import (
	"io/ioutil"
	"os"
	"path/filepath"
)

// TempDir checks whether os.TempDir() can be used as a temporary directory for
// later atomically replacing files within dest. If no (os.TempDir() resides on
// a different mount point), dest is returned.
//
// Note that the returned value ceases to be valid once either os.TempDir()
// changes (e.g. on Linux, once the TMPDIR environment variable changes) or the
// file system is unmounted.
func TempDir(dest string) string {
	return tempDir("", filepath.Join(dest, "renameio-TempDir"))
}

func tempDir(dir, dest string) string {
	if dir != "" {
		return dir // caller-specified directory always wins
	}

	// Chose the destination directory as temporary directory so that we
	// definitely can rename the file, for which both temporary and destination
	// file need to point to the same mount point.
	fallback := filepath.Dir(dest)

	// The user might have overridden the os.TempDir() return value by setting
	// the TMPDIR environment variable.
	tmpdir := os.TempDir()

	testsrc, err := ioutil.TempFile(tmpdir, "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	cleanup := true
	defer func() {
		if cleanup {
			os.Remove(testsrc.Name())
		}
	}()
	testsrc.Close()

	testdest, err := ioutil.TempFile(filepath.Dir(dest), "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	defer os.Remove(testdest.Name())
	testdest.Close()

	if err := os.Rename(testsrc.Name(), testdest.Name()); err != nil {
		return fallback
	}
	cleanup = false // testsrc no longer exists
	return tmpdir
}

// PendingFile is a pending temporary file, waiting to replace the destination
// path in a call to CloseAtomicallyReplace.
type PendingFile struct {
	*os.File

	path   string
	done   bool
	closed bool
}

// Cleanup is a no-op if CloseAtomicallyReplace succeeded, and otherwise closes
// and removes the temporary file.
func (t *PendingFile) Cleanup() error {
	if t.done {
		return nil
	}
	// An error occurred. Close and remove the tempfile. Errors are returned for
	// reporting, there is nothing the caller can recover here.
	var closeErr error
	if !t.closed {
		closeErr = t.Close()
	}
	if err := os.Remove(t.Name()); err != nil {
		return err
	}
	return closeErr
}

// CloseAtomicallyReplace closes the temporary file and atomatically replaces
// the destination file with it, i.e., a concurrent open(2) call will either
// open the file previously located at the destination path (if any), or the
// just written file, but the file will always be present.
func (t *PendingFile) CloseAtomicallyReplace() error {
	// Even on an ordered file system (e.g. ext4 with data=ordered) or file
	// systems with write barriers, we cannot skip the fsync(2) call as per
	// Theodore Ts'o (ext2/3/4 lead developer):
	//
	// > data=ordered only guarantees the avoidance of stale data (e.g., the previous
	// > contents of a data block showing up after a crash, where the previous data
	// > could be someone's love letters, medical records, etc.). Without the fsync(2)
	// > a zero-length file is a valid and possible outcome after the rename.
	if err := t.Sync(); err != nil {
		return err
	}
	t.closed = true
	if err := t.Close(); err != nil {
		return err
	}
	if err := os.Rename(t.Name(), t.path); err != nil {
		return err
	}
	t.done = true
	return nil
}

// TempFile wraps ioutil.TempFile for the use case of atomically creating or
// replacing the destination file at path.
//
// If dir is the empty string, TempDir(filepath.Base(path)) is used. If you are
// going to write a large number of files to the same file system, store the
// result of TempDir(filepath.Base(path)) and pass it instead of the empty
// string.
//
// The file's permissions will be 0600 by default. You can change these by
// explictly calling Chmod on the returned PendingFile.
func TempFile(dir, path string) (*PendingFile, error) {
	f, err := ioutil.TempFile(tempDir(dir, path), "."+filepath.Base(path))
	if err != nil {
		return nil, err
	}

	return &PendingFile{File: f, path: path}, nil
}

// Symlink wraps os.Symlink, replacing an existing symlink with the same name
// atomically (os.Symlink fails when newname already exists, at least on Linux).
func Symlink(oldname, newname string) error {
	// Fast path: if newname does not exist yet, we can skip the whole dance
	// below.
	if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) {
		return err
	}

	// We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
	// and removing+symlinking creates a TOCTOU race.
	d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname))
	if err != nil {
		return err
	}
	defer os.RemoveAll(d)

	symlink := filepath.Join(d, "tmp.symlink")
	if err := os.Symlink(oldname, symlink); err != nil {
		return err
	}

	if err := os.Rename(symlink, newname); err != nil {
		return err
	}

	return os.RemoveAll(d)
}
<|endoftext|>


<commit_before>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
/*
 *  Filename:    template.go
 *  Package:     main
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Sun Jul  3 17:55:40 PDT 2011
 *  Description:
 */
import (
    "os"
    "fmt"
    //"log"
    "bytes"
    "strings"
    "io/ioutil"
    "path/filepath"
    "template"
    //"github.com/hoisie/mustache.go"
)

var (
    NoTemplateError = os.NewError("Requested template does not exist")
    ParseError      = os.NewError("Couldn't parse template")
)

func TestName(filename string) string {
    var test = filename
    if strings.HasSuffix(test, ".go") {
        test = test[:len(test)-3]
    }
    if strings.HasSuffix(test, "_test") {
        test = test[:len(test)-5]
    }
    return strings.Title(test)
}

//  The $GOROOT environment variable.
func GetGoroot() string {
    goroot, err := os.Getenverror("GOROOT")
    if err != nil {
        panic("goroot")
    }
    return goroot
}

//  The template directory of the goinstall'ed gonew package.
func GetTemplateRoot() []string {
    return []string{GetGoroot(), "src", "pkg",
        "github.com", "bmatsuo", "gonew", "templates"}
}

//  Get a full template path from a path slice relative to the templates
//  directory.
func GetTemplatePath(relpath []string) string {
    var (
        rootpath = GetTemplateRoot()
        path     = make([]string, len(rootpath)+len(relpath))
    )
    copy(path, rootpath)
    copy(path[len(rootpath):], relpath)
    joined := filepath.Join(path...)
    if stat, err := os.Stat(joined); stat == nil || err != nil {
        return ""
    }
    return joined
}

func GetAltTemplatePath(relpath []string) string {
    if AppConfig.AltRoot == "" {
        Debug(0, "No alt root found.")
        return ""
    }
    altpath := GetRootedTemplatePath([]string{AppConfig.AltRoot}, relpath)
    if stat, err := os.Stat(altpath); stat == nil || err != nil {
        Debug(0, fmt.Sprintf("Error stat'ing %s.", altpath))
        return ""
    }
    return altpath
}

//  Get a full template path from a path slice relative to another path
//  slice.
func GetRootedTemplatePath(rootpath []string, relpath []string) string {
    path := make([]string, len(rootpath)+len(relpath))
    copy(path, rootpath)
    copy(path[len(rootpath):], relpath)
    return filepath.Join(path...)
}

func extraData(filename string) map[string]string {
    return map[string]string{"file": filename, "test": TestName(filename)}
}

func combined(dict, extra map[string]string) map[string]string {
    numEntries := len(dict) + len(extra)
    comb := make(map[string]string, numEntries)
    add := func(d map[string]string) {
        for k, v := range d {
            comb[k] = v
        }
    }
    add(dict)
    add(extra)
    return comb
}

func ParseAltTemplate(filename string, dict map[string]string, relpath []string) (string, os.Error) {
    var templ *template.Template
    if tpath := GetTemplatePath(relpath); tpath == "" {
        return "", NoTemplateError
    } else {
        templ = template.MustParseFile(tpath, nil)
        Debug(0, fmt.Sprintf("scanning: %s", tpath))
        Debug(1, fmt.Sprintf("context:\n%v", dict))
    }

    buff := bytes.NewBuffer(make([]byte, 0, 1<<20))
    errTExec := templ.Execute(buff, combined(dict, extraData(filename)))
    return buff.String(), errTExec
}

//  Given a filename and dictionary context, create a context dict+("file"=>filename),
//  and read a template specified by relpath. See GetTemplatePath().
func ParseTemplate(filename string, dict map[string]string, relpath []string) (string, os.Error) {
    var templ *template.Template
    if tpath := GetTemplatePath(relpath); tpath == "" {
        return "", NoTemplateError
    } else {
        templ = template.MustParseFile(tpath, nil)

        Debug(0, fmt.Sprintf("scanning: %s", tpath))
        Debug(1, fmt.Sprintf("context:\n%v", dict))
    }

    buff := bytes.NewBuffer(make([]byte, 0, 1<<20))
    errTExec := templ.Execute(buff, combined(dict, extraData(filename)))
    return buff.String(), errTExec
}

//  Given a filename, dictionary context, and the path to a template,
//  write the parsed template to the specified filename. The context of
//  the template will have a rule "file":filename which should override
//  any previous "file" rule in dict.
func WriteTemplate(filename, desc string, dict map[string]string, relpath ...string) os.Error {
    var templ string
    if altt, err := ParseAltTemplate(filename, dict, relpath); err == nil {
        templ = altt
        Verbose(fmt.Sprintf("Using alternate template %s\n", GetAltTemplatePath(relpath)))
    } else if stdt, err := ParseTemplate(filename, dict, relpath); err == nil {
        templ = stdt
    } else {
        return err
    }

    Verbose(fmt.Sprintf("Creating %s %s\n", desc, filename))
    Debug(2, fmt.Sprint("\n", templ, "\n"))

    templout := make([]byte, len(templ))
    copy(templout, templ)
    return ioutil.WriteFile(filename, templout, FilePermissions)
}
func AppendTemplate(filename, desc string, dict map[string]string, relpath ...string) os.Error {
    var templ string
    if altt, err := ParseAltTemplate(filename, dict, relpath); err == nil {
        templ = altt
        Verbose(fmt.Sprintf("Using alternate template %s\n", GetAltTemplatePath(relpath)))
    } else if stdt, err := ParseTemplate(filename, dict, relpath); err == nil {
        templ = stdt
    } else {
        return err
    }

    Verbose(fmt.Sprintf("Appending %s %s\n", desc, filename))
    Debug(2, fmt.Sprint("\n", templ, "\n"))

    fout, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, FilePermissions)
    if err != nil {
        return err
    }
    if _, err := fout.WriteString(templ); err != nil {
        return err
    }
    if err := fout.Close(); err != nil {
        return err
    }
    return nil
}

/* Some functions for tests and debugging. */
func getDebugTemplateRoot() []string { return []string{"templates"} }
func getDebugTemplatePath(relpath ...string) string {
    return GetRootedTemplatePath(getDebugTemplateRoot(), relpath)
}
<commit_msg>Make gonew compile under weekly 2011-08-17<commit_after>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
/*
 *  Filename:    template.go
 *  Package:     main
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Sun Jul  3 17:55:40 PDT 2011
 *  Description:
 */
import (
    "os"
    "fmt"
    //"log"
    "bytes"
    "strings"
    "io/ioutil"
    "path/filepath"
    "template"
    //"github.com/hoisie/mustache.go"
)

var (
    NoTemplateError = os.NewError("Requested template does not exist")
    ParseError      = os.NewError("Couldn't parse template")
)

func TestName(filename string) string {
    var test = filename
    if strings.HasSuffix(test, ".go") {
        test = test[:len(test)-3]
    }
    if strings.HasSuffix(test, "_test") {
        test = test[:len(test)-5]
    }
    return strings.Title(test)
}

//  The $GOROOT environment variable.
func GetGoroot() string {
    goroot, err := os.Getenverror("GOROOT")
    if err != nil {
        panic("goroot")
    }
    return goroot
}

//  The template directory of the goinstall'ed gonew package.
func GetTemplateRoot() []string {
    return []string{GetGoroot(), "src", "pkg",
        "github.com", "bmatsuo", "gonew", "templates"}
}

//  Get a full template path from a path slice relative to the templates
//  directory.
func GetTemplatePath(relpath []string) string {
    var (
        rootpath = GetTemplateRoot()
        path     = make([]string, len(rootpath)+len(relpath))
    )
    copy(path, rootpath)
    copy(path[len(rootpath):], relpath)
    joined := filepath.Join(path...)
    if stat, err := os.Stat(joined); stat == nil || err != nil {
        return ""
    }
    return joined
}

func GetAltTemplatePath(relpath []string) string {
    if AppConfig.AltRoot == "" {
        Debug(0, "No alt root found.")
        return ""
    }
    altpath := GetRootedTemplatePath([]string{AppConfig.AltRoot}, relpath)
    if stat, err := os.Stat(altpath); stat == nil || err != nil {
        Debug(0, fmt.Sprintf("Error stat'ing %s.", altpath))
        return ""
    }
    return altpath
}

//  Get a full template path from a path slice relative to another path
//  slice.
func GetRootedTemplatePath(rootpath []string, relpath []string) string {
    path := make([]string, len(rootpath)+len(relpath))
    copy(path, rootpath)
    copy(path[len(rootpath):], relpath)
    return filepath.Join(path...)
}

func extraData(filename string) map[string]string {
    return map[string]string{"file": filename, "test": TestName(filename)}
}

func combined(dict, extra map[string]string) map[string]string {
    numEntries := len(dict) + len(extra)
    comb := make(map[string]string, numEntries)
    add := func(d map[string]string) {
        for k, v := range d {
            comb[k] = v
        }
    }
    add(dict)
    add(extra)
    return comb
}

func ParseAltTemplate(filename string, dict map[string]string, relpath []string) (string, os.Error) {
    var templ *template.Template
    if tpath := GetTemplatePath(relpath); tpath == "" {
        return "", NoTemplateError
    } else {
        templ = template.Must(template.ParseFile(tpath))
        Debug(0, fmt.Sprintf("scanning: %s", tpath))
        Debug(1, fmt.Sprintf("context:\n%v", dict))
    }

    buff := bytes.NewBuffer(make([]byte, 0, 1<<20))
    errTExec := templ.Execute(buff, combined(dict, extraData(filename)))
    return buff.String(), errTExec
}

//  Given a filename and dictionary context, create a context dict+("file"=>filename),
//  and read a template specified by relpath. See GetTemplatePath().
func ParseTemplate(filename string, dict map[string]string, relpath []string) (string, os.Error) {
    var templ *template.Template
    if tpath := GetTemplatePath(relpath); tpath == "" {
        return "", NoTemplateError
    } else {
        templ = template.Must(template.ParseFile(tpath))

        Debug(0, fmt.Sprintf("scanning: %s", tpath))
        Debug(1, fmt.Sprintf("context:\n%v", dict))
    }

    buff := bytes.NewBuffer(make([]byte, 0, 1<<20))
    errTExec := templ.Execute(buff, combined(dict, extraData(filename)))
    return buff.String(), errTExec
}

//  Given a filename, dictionary context, and the path to a template,
//  write the parsed template to the specified filename. The context of
//  the template will have a rule "file":filename which should override
//  any previous "file" rule in dict.
func WriteTemplate(filename, desc string, dict map[string]string, relpath ...string) os.Error {
    var templ string
    if altt, err := ParseAltTemplate(filename, dict, relpath); err == nil {
        templ = altt
        Verbose(fmt.Sprintf("Using alternate template %s\n", GetAltTemplatePath(relpath)))
    } else if stdt, err := ParseTemplate(filename, dict, relpath); err == nil {
        templ = stdt
    } else {
        return err
    }

    Verbose(fmt.Sprintf("Creating %s %s\n", desc, filename))
    Debug(2, fmt.Sprint("\n", templ, "\n"))

    templout := make([]byte, len(templ))
    copy(templout, templ)
    return ioutil.WriteFile(filename, templout, FilePermissions)
}
func AppendTemplate(filename, desc string, dict map[string]string, relpath ...string) os.Error {
    var templ string
    if altt, err := ParseAltTemplate(filename, dict, relpath); err == nil {
        templ = altt
        Verbose(fmt.Sprintf("Using alternate template %s\n", GetAltTemplatePath(relpath)))
    } else if stdt, err := ParseTemplate(filename, dict, relpath); err == nil {
        templ = stdt
    } else {
        return err
    }

    Verbose(fmt.Sprintf("Appending %s %s\n", desc, filename))
    Debug(2, fmt.Sprint("\n", templ, "\n"))

    fout, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, FilePermissions)
    if err != nil {
        return err
    }
    if _, err := fout.WriteString(templ); err != nil {
        return err
    }
    if err := fout.Close(); err != nil {
        return err
    }
    return nil
}

/* Some functions for tests and debugging. */
func getDebugTemplateRoot() []string { return []string{"templates"} }
func getDebugTemplatePath(relpath ...string) string {
    return GetRootedTemplatePath(getDebugTemplateRoot(), relpath)
}
<|endoftext|>