<commit_before>package api

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/levigross/grequests"
)

// Log is EngineLogs's type structure
type Log struct {
	InstanceName string `json:"instanceName"`
	Content      string `json:"content"`
	Type         string `json:"type"`
	Time         string `json:"time"`
	GroupName    string `json:"groupName"`
	Production   int    `json:"prod"`
	Stream       string `json:"stream"`
	ID           string `json:"id"`
}

// LogReceiver is print func interface to PrintLogs
type LogReceiver func(*Log) error

// ReceiveLogsByLimit will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByLimit(printer LogReceiver, appID string, masterKey string, isProd bool, group string, limit int, follow bool) error {
	params := map[string]string{
		"limit":     strconv.Itoa(limit),
		"prod":      "0",
		"groupName": group,
	}
	if isProd {
		params["prod"] = "1"
	}

	logIDSet := map[string]bool{}
	for {
		for k, v := range params {
			println(k, v)
		}

		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}

		for i := len(logs) - 1; i >= 0; i-- {
			log := logs[i]
			if _, ok := logIDSet[log.ID]; ok {
				continue
			}
			logIDSet[log.ID] = true
			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if !follow {
			break
		}

		// limit is not necessary in second round of fetch
		delete(params, "limit")

		if len(logs) > 0 {
			params["to"] = logs[0].Time
		}
		params["from"] = time.Now().UTC().Format("2006-01-02T15:04:05.000000000Z")

		time.Sleep(5 * time.Second)
	}

	return nil
}

// ReceiveLogsByRange will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByRange(printer LogReceiver, appID string, masterKey string, isProd bool, group string, from time.Time, to time.Time) error {
	params := map[string]string{
		"prod":      "0",
		"groupName": group,
		"limit":     "1000",
	}

	if isProd {
		params["prod"] = "1"
	}

	if from != (time.Time{}) {
		params["from"] = from.UTC().Format("2006-01-02T15:04:05.000000000Z")
	}
	if to != (time.Time{}) {
		params["to"] = to.UTC().Format("2006-01-02T15:04:05.000000000Z")
	}

	for {
		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}
		for _, log := range logs {
			logTime, err := time.Parse("2006-01-02T15:04:05.999999999Z", log.Time)
			if err != nil {
				return err
			}
			if to != (time.Time{}) && logTime.After(to) {
				// reached the end
				return nil
			}

			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if len(logs) == 0 {
			// no more logs
			return nil
		}

		if len(logs) > 0 {
			params["since"] = logs[len(logs)-1].Time
		}
	}
}

func fetchLogs(appID string, masterKey string, params map[string]string, isProd bool) ([]Log, error) {
	client := NewClientByApp(appID)
	url := "/1.1/engine/logs"

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID
	opts.Params = params

	var resp *grequests.Response
	retryCount := 0
	for {
		resp, err = client.get(url, opts)
		if err == nil {
			break
		}
		if retryCount >= 3 {
			return nil, err
		}
		retryCount++
		time.Sleep(1123 * time.Millisecond) // 1123 is a prime number, prime number makes less bugs.
	}

	var logs []Log
	err = resp.JSON(&logs)
	return logs, err
}
<commit_msg>chore: log range<commit_after>package api

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/levigross/grequests"
)

// Log is EngineLogs's type structure
type Log struct {
	InstanceName string `json:"instanceName"`
	Content      string `json:"content"`
	Type         string `json:"type"`
	Time         string `json:"time"`
	GroupName    string `json:"groupName"`
	Production   int    `json:"prod"`
	Stream       string `json:"stream"`
	ID           string `json:"id"`
}

// LogReceiver is print func interface to PrintLogs
type LogReceiver func(*Log) error

// ReceiveLogsByLimit will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByLimit(printer LogReceiver, appID string, masterKey string, isProd bool, group string, limit int, follow bool) error {
	params := map[string]string{
		"limit":     strconv.Itoa(limit),
		"prod":      "0",
		"groupName": group,
	}
	if isProd {
		params["prod"] = "1"
	}

	logIDSet := map[string]bool{}
	for {
		for k, v := range params {
			println(k, v)
		}

		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}

		for i := len(logs) - 1; i >= 0; i-- {
			log := logs[i]
			if _, ok := logIDSet[log.ID]; ok {
				continue
			}
			logIDSet[log.ID] = true
			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if !follow {
			break
		}

		// limit is not necessary in second round of fetch
		delete(params, "limit")

		if len(logs) > 0 {
			params["to"] = logs[0].Time
		}
		params["from"] = time.Now().UTC().Format("2006-01-02T15:04:05.000000000Z")

		time.Sleep(5 * time.Second)
	}

	return nil
}

// ReceiveLogsByRange will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByRange(printer LogReceiver, appID string, masterKey string, isProd bool, group string, from time.Time, to time.Time) error {
	params := map[string]string{
		"prod":      "0",
		"groupName": group,
		"limit":     "1000",
	}

	if isProd {
		params["prod"] = "1"
	}

	if from != (time.Time{}) {
		params["from"] = from.UTC().Format("2006-01-02T15:04:05.000000000Z")
	}
	if to != (time.Time{}) {
		params["to"] = to.UTC().Format("2006-01-02T15:04:05.000000000Z")
	}

	logs, err := fetchLogs(appID, masterKey, params, isProd)
	if err != nil {
		return err
	}
	start := 0
	end := len(logs)
	if (to != (time.Time{}) && from == (time.Time{})) ||
		(from != (time.Time{}) && to != (time.Time{}) && from.After(to)) {
		start = len(logs) - 1
		end = -1
	}
	for i := start; i != end; {
		log := logs[i]
		err = printer(&log)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
		}
		if start < end {
			i++
		} else {
			i--
		}
	}

	return nil
}

func fetchLogs(appID string, masterKey string, params map[string]string, isProd bool) ([]Log, error) {
	client := NewClientByApp(appID)
	url := "/1.1/engine/logs"

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID
	opts.Params = params

	var resp *grequests.Response
	retryCount := 0
	for {
		resp, err = client.get(url, opts)
		if err == nil {
			break
		}
		if retryCount >= 3 {
			return nil, err
		}
		retryCount++
		time.Sleep(1123 * time.Millisecond) // 1123 is a prime number, prime number makes less bugs.
	}

	var logs []Log
	err = resp.JSON(&logs)
	return logs, err
}
<|endoftext|>


<commit_before>package app

import (
	"github.com/toonsevrin/simplechain/types"
	"time"
	"bytes"
	"fmt"
)
type App struct {
	Blockchain []types.Block
}

func (app *App) createNextBlock(data string) types.Block {
	previous := app.getLatestBlock()
	next := types.Block{Index: previous.Index, PreviousHash: previous.Hash, Timestamp: time.Now().Unix(), Hash: [32]byte{}, Data: data}
	next.Hash = *next.GenerateHash();
	return next
}

func (app *App) isValidNewBlock(block types.Block) bool{
	return block.IsValid() && bytes.Equal(block.PreviousHash[:], (&app.getLatestBlock().Hash)[:]) && app.getLatestBlock().Index + 1 == block.Index
}

func (app *App) getLatestBlock() types.Block {
	return app.Blockchain[len(app.Blockchain)-1]
}

func (app *App) isValidChain(chain []types.Block) bool{
	if *chain[0].GenerateHash() != app.Blockchain[0].Hash {
		return false
	}
	for i, block := range chain {
		if(i == 0){
			continue
		}
		if block.PreviousHash != chain[i - 1].Hash {
			return false
		}
		if !block.IsValid() {
			return false
		}
	}
	return true
}

func (app *App) pickLongestChain(newChain []types.Block)  {
	if len(newChain) > len(app.Blockchain) && app.isValidChain(newChain) {
		fmt.Println("Received longer chain of length ", len(newChain))
		app.Blockchain = newChain
		//broadcast new chain
	}else{
		fmt.Println("Received invalid chain")
	}
}<commit_msg>Fixed reference issue?<commit_after>package app

import (
	"github.com/toonsevrin/simplechain/types"
	"time"
	"bytes"
	"fmt"
)
type App struct {
	Blockchain []types.Block
}

func (app *App) createNextBlock(data string) types.Block {
	previous := app.getLatestBlock()
	next := types.Block{Index: previous.Index, PreviousHash: previous.Hash, Timestamp: time.Now().Unix(), Hash: [32]byte{}, Data: data}
	next.Hash = *next.GenerateHash();
	return next
}

func (app *App) isValidNewBlock(block types.Block) bool{
	return block.IsValid() && bytes.Equal(block.PreviousHash[:], app.getLatestBlock().Hash[:]) && app.getLatestBlock().Index + 1 == block.Index
}

func (app *App) getLatestBlock() *types.Block {
	return &app.Blockchain[len(app.Blockchain)-1]
}

func (app *App) isValidChain(chain []types.Block) bool{
	if *chain[0].GenerateHash() != app.Blockchain[0].Hash {
		return false
	}
	for i, block := range chain {
		if(i == 0){
			continue
		}
		if block.PreviousHash != chain[i - 1].Hash {
			return false
		}
		if !block.IsValid() {
			return false
		}
	}
	return true
}

func (app *App) pickLongestChain(newChain []types.Block)  {
	if len(newChain) > len(app.Blockchain) && app.isValidChain(newChain) {
		fmt.Println("Received longer chain of length ", len(newChain))
		app.Blockchain = newChain
		//broadcast new chain
	}else{
		fmt.Println("Received invalid chain")
	}
}<|endoftext|>


<commit_before>package app

import (
	"errors"
	"fmt"
	"github.com/mattn/go-gtk/gdk"
	"irken/client"
	"irken/client/msg"
	"irken/gui"
	"log"
	"os/user"
	"strconv"
)

const DEFAULT_TITLE = "Irken"
const DEFAULT_CONT = ""

// TODO: Close listeners after parted channels

// generic handler func that takes a Line argument
type Handler func(*msg.Line)

type IrkenApp struct {
	gui *gui.GUI
	cs  *client.ConnectSession

	conf *client.Config
	// map from a command string to an action
	handlers  map[string]Handler
	listeners map[string]chan struct{}
}

func NewIrkenApp(cfgPath string) *IrkenApp {

	conf, confErr := loadCfg(cfgPath)
	w, _ := conf.GetCfgValue("window_width")
	wWidth, _ := strconv.Atoi(w)
	h, _ := conf.GetCfgValue("window_height")
	wHeight, _ := strconv.Atoi(h)

	nick, _ := conf.GetCfgValue("nick")
	realname, _ := conf.GetCfgValue("realname")
	g := gui.NewGUI(DEFAULT_TITLE, wWidth, wHeight)
	cs := client.NewConnectSession(nick, realname)
	ia := &IrkenApp{
		gui:       g,
		cs:        cs,
		conf:      conf,
		handlers:  make(map[string]Handler),
		listeners: make(map[string]chan struct{}),
	}
	initHandlers(ia)

	g.CreateChannelWindow(DEFAULT_CONT, func() {
		text, err := g.GetEntryText("")
		if err != nil {
			err := g.WriteToChannel("Couldn't get input", "")
			handleFatalErr(err)
		}
		err = ia.cs.Send(text, DEFAULT_CONT)
		if err != nil {
			err := g.WriteToChannel("Couldn't parse input", "")
			handleFatalErr(err)
		}
		g.EmptyEntryText("")
	})
	ia.BeginInput("")

	err := g.WriteToChannel("Welcome to Irken!", DEFAULT_CONT)
	handleFatalErr(err)
	if confErr != nil {
		err := g.WriteToChannel("Cannot parse config file - using default values",
			DEFAULT_CONT)
		handleFatalErr(err)
	}
	err = g.WriteToChannel("Nick is "+nick, DEFAULT_CONT)
	handleFatalErr(err)
	err = g.WriteToChannel("Real name is "+realname, DEFAULT_CONT)
	handleFatalErr(err)

	return ia
}

func (ia *IrkenApp) BeginInput(context string) {
	ch := make(chan struct{})
	ia.listeners[context] = ch
	go func() {
		for {
			select {
			case <-ch:
				return
			default:
				line := <-ia.cs.IrcChannels[context].Ch
				gdk.ThreadsEnter()
				handlErr := ia.handle(line)
				gdk.ThreadsLeave()
				if handlErr != nil {
					gdk.ThreadsEnter()
					err := ia.gui.WriteToChannel(line.Output(), context)
					gdk.ThreadsLeave()
					handleFatalErr(err)
				}
			}

		}
	}()
	return
}

func (ia *IrkenApp) EndInput(context string) {
	close(ia.listeners[context])
}

func initHandlers(ia *IrkenApp) {
	ia.handlers["CCONNECT"] = func(l *msg.Line) {
		addr := l.Args()[len(l.Args())-1]
		err := ia.cs.Connect(addr)
		if err != nil {
			errMsg := fmt.Sprintf("Couldn't connect to %s\n, error: %v",
				addr, err)
			err = ia.gui.WriteToChannel(errMsg, "")
			handleFatalErr(err)
		}
	}

	ia.handlers["CJOIN"] = func(l *msg.Line) {
		if !ia.cs.IsConnected() {
			err := ia.gui.WriteToChannel("Error: Not connected to any server",
				"")
			handleFatalErr(err)
			return
		}

		chanCont := l.Context()
		ia.cs.NewChannel(chanCont)

		ia.gui.CreateChannelWindow(chanCont, func() {
			text, err := ia.gui.GetEntryText(chanCont)
			if err != nil {
				err := ia.gui.WriteToChannel("Couldn't get input", chanCont)
				handleFatalErr(err)
			}
			err = ia.cs.Send(text, chanCont)
			if err != nil {
				err := ia.gui.WriteToChannel("Couldn't parse input", chanCont)
				handleFatalErr(err)
			}
			ia.gui.EmptyEntryText(chanCont)
		})
		ia.BeginInput(chanCont)
	}

	ia.handlers["CPART"] = func(l *msg.Line) {
		if !ia.cs.IsConnected() {
			err := ia.gui.WriteToChannel("Error: Not in any channel",
				"")
			handleFatalErr(err)
			return
		}

		ia.cs.DeleteChannel(l.Context())
		ia.EndInput(l.Context())
		ia.gui.DeleteCurrentWindow()
	}
}

func (ia *IrkenApp) GUI() *gui.GUI {
	return ia.gui
}

func (ia *IrkenApp) AddHandler(h Handler, cmd string) (err error) {
	_, ok := ia.handlers[cmd]
	if ok {
		return errors.New("Command already has a handler")
	}
	ia.handlers[cmd] = h
	return
}

func (ia *IrkenApp) handle(l *msg.Line) (err error) {
	h, ok := ia.handlers[l.Cmd()]
	if !ok {
		return errors.New("Couldn't find a handler")
	}
	h(l)
	return
}

func loadCfg(filename string) (c *client.Config, err error) {
	c, err = client.NewConfig(filename)
	if !c.HasValue("nick") {
		u, err := user.Current()
		if err != nil {
			return nil, err
		}
		c.AddCfgValue("nick", u.Username)
	}
	if !c.HasValue("realname") {
		u, err := user.Current()
		if err != nil {
			return nil, err
		}
		c.AddCfgValue("realname", u.Name)
	}
	if !c.HasValue("window_width") {
		c.AddCfgValue("window_width", "860")
	}
	if !c.HasValue("window_height") {
		c.AddCfgValue("window_height", "640")
	}

	return
}

func handleFatalErr(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}
<commit_msg>Connect handler checks for connection<commit_after>package app

import (
	"errors"
	"fmt"
	"github.com/mattn/go-gtk/gdk"
	"irken/client"
	"irken/client/msg"
	"irken/gui"
	"log"
	"os/user"
	"strconv"
)

const DEFAULT_TITLE = "Irken"
const DEFAULT_CONT = ""

// TODO: Close listeners after parted channels

// generic handler func that takes a Line argument
type Handler func(*msg.Line)

type IrkenApp struct {
	gui *gui.GUI
	cs  *client.ConnectSession

	conf *client.Config
	// map from a command string to an action
	handlers  map[string]Handler
	listeners map[string]chan struct{}
}

func NewIrkenApp(cfgPath string) *IrkenApp {

	conf, confErr := loadCfg(cfgPath)
	w, _ := conf.GetCfgValue("window_width")
	wWidth, _ := strconv.Atoi(w)
	h, _ := conf.GetCfgValue("window_height")
	wHeight, _ := strconv.Atoi(h)

	nick, _ := conf.GetCfgValue("nick")
	realname, _ := conf.GetCfgValue("realname")
	g := gui.NewGUI(DEFAULT_TITLE, wWidth, wHeight)
	cs := client.NewConnectSession(nick, realname)
	ia := &IrkenApp{
		gui:       g,
		cs:        cs,
		conf:      conf,
		handlers:  make(map[string]Handler),
		listeners: make(map[string]chan struct{}),
	}
	initHandlers(ia)

	g.CreateChannelWindow(DEFAULT_CONT, func() {
		text, err := g.GetEntryText("")
		if err != nil {
			err := g.WriteToChannel("Couldn't get input", "")
			handleFatalErr(err)
		}
		err = ia.cs.Send(text, DEFAULT_CONT)
		if err != nil {
			err := g.WriteToChannel("Couldn't parse input", "")
			handleFatalErr(err)
		}
		g.EmptyEntryText("")
	})
	ia.BeginInput("")

	err := g.WriteToChannel("Welcome to Irken!", DEFAULT_CONT)
	handleFatalErr(err)
	if confErr != nil {
		err := g.WriteToChannel("Cannot parse config file - using default values",
			DEFAULT_CONT)
		handleFatalErr(err)
	}
	err = g.WriteToChannel("Nick is "+nick, DEFAULT_CONT)
	handleFatalErr(err)
	err = g.WriteToChannel("Real name is "+realname, DEFAULT_CONT)
	handleFatalErr(err)

	return ia
}

func (ia *IrkenApp) BeginInput(context string) {
	ch := make(chan struct{})
	ia.listeners[context] = ch
	go func() {
		for {
			select {
			case <-ch:
				return
			default:
				line := <-ia.cs.IrcChannels[context].Ch
				gdk.ThreadsEnter()
				handlErr := ia.handle(line)
				gdk.ThreadsLeave()
				if handlErr != nil {
					gdk.ThreadsEnter()
					err := ia.gui.WriteToChannel(line.Output(), context)
					gdk.ThreadsLeave()
					handleFatalErr(err)
				}
			}

		}
	}()
	return
}

func (ia *IrkenApp) EndInput(context string) {
	close(ia.listeners[context])
}

func initHandlers(ia *IrkenApp) {
	ia.handlers["CCONNECT"] = func(l *msg.Line) {
		if ai.cs.IsConnected() {
			err = ia.gui.WriteToChannel("Disconnect first to connect to a new server", "")
			handleFatalErr(err)
		}

		addr := l.Args()[len(l.Args())-1]
		err := ia.cs.Connect(addr)
		if err != nil {
			errMsg := fmt.Sprintf("Couldn't connect to %s\n, error: %v",
				addr, err)
			err = ia.gui.WriteToChannel(errMsg, "")
			handleFatalErr(err)
		}
	}

	ia.handlers["CJOIN"] = func(l *msg.Line) {
		if !ia.cs.IsConnected() {
			err := ia.gui.WriteToChannel("Error: Not connected to any server",
				"")
			handleFatalErr(err)
			return
		}

		chanCont := l.Context()
		ia.cs.NewChannel(chanCont)

		ia.gui.CreateChannelWindow(chanCont, func() {
			text, err := ia.gui.GetEntryText(chanCont)
			if err != nil {
				err := ia.gui.WriteToChannel("Couldn't get input", chanCont)
				handleFatalErr(err)
			}
			err = ia.cs.Send(text, chanCont)
			if err != nil {
				err := ia.gui.WriteToChannel("Couldn't parse input", chanCont)
				handleFatalErr(err)
			}
			ia.gui.EmptyEntryText(chanCont)
		})
		ia.BeginInput(chanCont)
	}

	ia.handlers["CPART"] = func(l *msg.Line) {
		if !ia.cs.IsConnected() {
			err := ia.gui.WriteToChannel("Error: Not in any channel",
				"")
			handleFatalErr(err)
			return
		}

		ia.cs.DeleteChannel(l.Context())
		ia.EndInput(l.Context())
		ia.gui.DeleteCurrentWindow()
	}
}

func (ia *IrkenApp) GUI() *gui.GUI {
	return ia.gui
}

func (ia *IrkenApp) AddHandler(h Handler, cmd string) (err error) {
	_, ok := ia.handlers[cmd]
	if ok {
		return errors.New("Command already has a handler")
	}
	ia.handlers[cmd] = h
	return
}

func (ia *IrkenApp) handle(l *msg.Line) (err error) {
	h, ok := ia.handlers[l.Cmd()]
	if !ok {
		return errors.New("Couldn't find a handler")
	}
	h(l)
	return
}

func loadCfg(filename string) (c *client.Config, err error) {
	c, err = client.NewConfig(filename)
	if !c.HasValue("nick") {
		u, err := user.Current()
		if err != nil {
			return nil, err
		}
		c.AddCfgValue("nick", u.Username)
	}
	if !c.HasValue("realname") {
		u, err := user.Current()
		if err != nil {
			return nil, err
		}
		c.AddCfgValue("realname", u.Name)
	}
	if !c.HasValue("window_width") {
		c.AddCfgValue("window_width", "860")
	}
	if !c.HasValue("window_height") {
		c.AddCfgValue("window_height", "640")
	}

	return
}

func handleFatalErr(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/mohae/deepcopy"
)

// Strings for VolumeSpec
const (
	Name                     = "name"
	SpecParent               = "parent"
	SpecEphemeral            = "ephemeral"
	SpecShared               = "shared"
	SpecSticky               = "sticky"
	SpecSize                 = "size"
	SpecScale                = "scale"
	SpecFilesystem           = "fs"
	SpecBlockSize            = "block_size"
	SpecHaLevel              = "repl"
	SpecPriority             = "io_priority"
	SpecSnapshotInterval     = "snap_interval"
	SpecAggregationLevel     = "aggregation_level"
	SpecDedupe               = "dedupe"
	SpecPassphrase           = "secret_key"
	SpecAutoAggregationValue = "auto"
	SpecGroup                = "group"
	SpecGroupEnforce         = "rg"
)

// OptionKey specifies a set of recognized query params.
const (
	// OptName query parameter used to lookup volume by name.
	OptName = "Name"
	// OptVolumeID query parameter used to lookup volume by ID.
	OptVolumeID = "VolumeID"
	// OptLabel query parameter used to lookup volume by set of labels.
	OptLabel = "Label"
	// OptConfigLabel query parameter used to lookup volume by set of labels.
	OptConfigLabel = "ConfigLabel"
	// OptCumulative query parameter used to request cumulative stats.
	OptCumulative = "Cumulative"
)

// Api client-server Constants
const (
	OsdVolumePath   = "osd-volumes"
	OsdSnapshotPath = "osd-snapshot"
	TimeLayout      = "Jan 2 15:04:05 UTC 2006"
)

const (
	// AutoAggregation value indicates driver to select aggregation level.
	AutoAggregation = math.MaxUint32
)

// Node describes the state of a node.
// It includes the current physical state (CPU, memory, storage, network usage) as
// well as the containers running on the system.
type Node struct {
	Id        string
	Cpu       float64 // percentage.
	MemTotal  uint64
	MemUsed   uint64
	MemFree   uint64
	Avgload   int
	Status    Status
	GenNumber uint64
	Disks     map[string]StorageResource
	Pools     []StoragePool
	MgmtIp    string
	DataIp    string
	Timestamp time.Time
	StartTime time.Time
	Hostname  string
	NodeData  map[string]interface{}
	// User defined labels for node. Key Value pairs
	NodeLabels map[string]string
}

// Cluster represents the state of the cluster.
type Cluster struct {
	Status Status

	// Id is the ID of the cluster.
	Id string

	// NodeId is the ID of the node on which this cluster object
	// is initialized
	NodeId string

	// Nodes is an array of all the nodes in the cluster.
	Nodes []Node

	// Logging url for the cluster.
	LoggingURL string
}

// StatPoint represents the basic structure of a single Stat reported
// TODO: This is the first step to introduce stats in openstorage.
//       Follow up task is to introduce an API for logging stats
type StatPoint struct {
	// Name of the Stat
	Name string
	// Tags for the Stat
	Tags map[string]string
	// Fields and values of the stat
	Fields map[string]interface{}
	// Timestamp in Unix format
	Timestamp int64
}

// DriverTypeSimpleValueOf returns the string format of DriverType
func DriverTypeSimpleValueOf(s string) (DriverType, error) {
	obj, err := simpleValueOf("driver_type", DriverType_value, s)
	return DriverType(obj), err
}

// SimpleString returns the string format of DriverType
func (x DriverType) SimpleString() string {
	return simpleString("driver_type", DriverType_name, int32(x))
}

// FSTypeSimpleValueOf returns the string format of FSType
func FSTypeSimpleValueOf(s string) (FSType, error) {
	obj, err := simpleValueOf("fs_type", FSType_value, s)
	return FSType(obj), err
}

// SimpleString returns the string format of DriverType
func (x FSType) SimpleString() string {
	return simpleString("fs_type", FSType_name, int32(x))
}

// CosTypeSimpleValueOf returns the string format of CosType
func CosTypeSimpleValueOf(s string) (CosType, error) {
	obj, exists := CosType_value[strings.ToUpper(s)]
	if !exists {
		return -1, fmt.Errorf("Invalid cos value: %s", s)
	}
	return CosType(obj), nil
}

// SimpleString returns the string format of CosType
func (x CosType) SimpleString() string {
	return simpleString("cos_type", CosType_name, int32(x))
}

// GraphDriverChangeTypeSimpleValueOf returns the string format of GraphDriverChangeType
func GraphDriverChangeTypeSimpleValueOf(s string) (GraphDriverChangeType, error) {
	obj, err := simpleValueOf("graph_driver_change_type", GraphDriverChangeType_value, s)
	return GraphDriverChangeType(obj), err
}

// SimpleString returns the string format of GraphDriverChangeType
func (x GraphDriverChangeType) SimpleString() string {
	return simpleString("graph_driver_change_type", GraphDriverChangeType_name, int32(x))
}

// VolumeActionParamSimpleValueOf returns the string format of VolumeAction
func VolumeActionParamSimpleValueOf(s string) (VolumeActionParam, error) {
	obj, err := simpleValueOf("volume_action_param", VolumeActionParam_value, s)
	return VolumeActionParam(obj), err
}

// SimpleString returns the string format of VolumeAction
func (x VolumeActionParam) SimpleString() string {
	return simpleString("volume_action_param", VolumeActionParam_name, int32(x))
}

// VolumeStateSimpleValueOf returns the string format of VolumeState
func VolumeStateSimpleValueOf(s string) (VolumeState, error) {
	obj, err := simpleValueOf("volume_state", VolumeState_value, s)
	return VolumeState(obj), err
}

// SimpleString returns the string format of VolumeState
func (x VolumeState) SimpleString() string {
	return simpleString("volume_state", VolumeState_name, int32(x))
}

// VolumeStatusSimpleValueOf returns the string format of VolumeStatus
func VolumeStatusSimpleValueOf(s string) (VolumeStatus, error) {
	obj, err := simpleValueOf("volume_status", VolumeStatus_value, s)
	return VolumeStatus(obj), err
}

// SimpleString returns the string format of VolumeStatus
func (x VolumeStatus) SimpleString() string {
	return simpleString("volume_status", VolumeStatus_name, int32(x))
}

// IoProfileSimpleValueOf returns the string format of IoProfile
func IoProfileSimpleValueOf(s string) (IoProfile, error) {
	obj, err := simpleValueOf("io_profile", IoProfile_value, s)
	return IoProfile(obj), err
}

// SimpleString returns the string format of IoProfile
func (x IoProfile) SimpleString() string {
	return simpleString("io_profile", IoProfile_name, int32(x))
}

func simpleValueOf(typeString string, valueMap map[string]int32, s string) (int32, error) {
	obj, ok := valueMap[strings.ToUpper(fmt.Sprintf("%s_%s", typeString, s))]
	if !ok {
		return 0, fmt.Errorf("no openstorage.%s for %s", strings.ToUpper(typeString), s)
	}
	return obj, nil
}

func simpleString(typeString string, nameMap map[int32]string, v int32) string {
	s, ok := nameMap[v]
	if !ok {
		return strconv.Itoa(int(v))
	}
	return strings.TrimPrefix(strings.ToLower(s), fmt.Sprintf("%s_", strings.ToLower(typeString)))
}

func toSec(ms uint64) uint64 {
	return ms / 1000
}

// WriteThroughput returns the write throughput
func (v *Stats) WriteThroughput() uint64 {
	if v.IntervalMs == 0 {
		return 0
	}
	return (v.WriteBytes) / toSec(v.IntervalMs)
}

// ReadThroughput returns the read throughput
func (v *Stats) ReadThroughput() uint64 {
	if v.IntervalMs == 0 {
		return 0
	}
	return (v.ReadBytes) / toSec(v.IntervalMs)
}

// Latency returns latency
func (v *Stats) Latency() uint64 {
	ops := v.Writes + v.Reads
	if ops == 0 {
		return 0
	}
	return (uint64)((v.IoMs * 1000) / (v.Writes + v.Reads))
}

// Iops returns iops
func (v *Stats) Iops() uint64 {
	if v.IntervalMs == 0 {
		return 0
	}
	return (v.Writes + v.Reads) / toSec(v.IntervalMs)
}

// Scaled returns true if the volume is scaled.
func (v *Volume) Scaled() bool {
	return v.Spec.Scale > 1
}

// Contains returns true if mid is a member of volume's replication set.
func (m *Volume) Contains(mid string) bool {
	rsets := m.GetReplicaSets()
	for _, rset := range rsets {
		for _, node := range rset.Nodes {
			if node == mid {
				return true
			}
		}
	}
	return false
}

// Copy makes a deep copy of VolumeSpec
func (s *VolumeSpec) Copy() *VolumeSpec {
	spec := *s
	if s.VolumeLabels != nil {
		spec.VolumeLabels = make(map[string]string)
		for k, v := range s.VolumeLabels {
			spec.VolumeLabels[k] = v
		}
	}
	if s.ReplicaSet != nil {
		spec.ReplicaSet = &ReplicaSet{Nodes: make([]string, len(s.ReplicaSet.Nodes))}
		copy(spec.ReplicaSet.Nodes, s.ReplicaSet.Nodes)
	}
	return &spec
}

// Copy makes a deep copy of Node
func (s *Node) Copy() *Node {
	localCopy := deepcopy.Copy(*s)
	nodeCopy := localCopy.(Node)
	return &nodeCopy
}
<commit_msg>Make docker option fg (force group) instead of rg.<commit_after>package api

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/mohae/deepcopy"
)

// Strings for VolumeSpec
const (
	Name                     = "name"
	SpecParent               = "parent"
	SpecEphemeral            = "ephemeral"
	SpecShared               = "shared"
	SpecSticky               = "sticky"
	SpecSize                 = "size"
	SpecScale                = "scale"
	SpecFilesystem           = "fs"
	SpecBlockSize            = "block_size"
	SpecHaLevel              = "repl"
	SpecPriority             = "io_priority"
	SpecSnapshotInterval     = "snap_interval"
	SpecAggregationLevel     = "aggregation_level"
	SpecDedupe               = "dedupe"
	SpecPassphrase           = "secret_key"
	SpecAutoAggregationValue = "auto"
	SpecGroup                = "group"
	SpecGroupEnforce         = "fg"
)

// OptionKey specifies a set of recognized query params.
const (
	// OptName query parameter used to lookup volume by name.
	OptName = "Name"
	// OptVolumeID query parameter used to lookup volume by ID.
	OptVolumeID = "VolumeID"
	// OptLabel query parameter used to lookup volume by set of labels.
	OptLabel = "Label"
	// OptConfigLabel query parameter used to lookup volume by set of labels.
	OptConfigLabel = "ConfigLabel"
	// OptCumulative query parameter used to request cumulative stats.
	OptCumulative = "Cumulative"
)

// Api client-server Constants
const (
	OsdVolumePath   = "osd-volumes"
	OsdSnapshotPath = "osd-snapshot"
	TimeLayout      = "Jan 2 15:04:05 UTC 2006"
)

const (
	// AutoAggregation value indicates driver to select aggregation level.
	AutoAggregation = math.MaxUint32
)

// Node describes the state of a node.
// It includes the current physical state (CPU, memory, storage, network usage) as
// well as the containers running on the system.
type Node struct {
	Id        string
	Cpu       float64 // percentage.
	MemTotal  uint64
	MemUsed   uint64
	MemFree   uint64
	Avgload   int
	Status    Status
	GenNumber uint64
	Disks     map[string]StorageResource
	Pools     []StoragePool
	MgmtIp    string
	DataIp    string
	Timestamp time.Time
	StartTime time.Time
	Hostname  string
	NodeData  map[string]interface{}
	// User defined labels for node. Key Value pairs
	NodeLabels map[string]string
}

// Cluster represents the state of the cluster.
type Cluster struct {
	Status Status

	// Id is the ID of the cluster.
	Id string

	// NodeId is the ID of the node on which this cluster object
	// is initialized
	NodeId string

	// Nodes is an array of all the nodes in the cluster.
	Nodes []Node

	// Logging url for the cluster.
	LoggingURL string
}

// StatPoint represents the basic structure of a single Stat reported
// TODO: This is the first step to introduce stats in openstorage.
//       Follow up task is to introduce an API for logging stats
type StatPoint struct {
	// Name of the Stat
	Name string
	// Tags for the Stat
	Tags map[string]string
	// Fields and values of the stat
	Fields map[string]interface{}
	// Timestamp in Unix format
	Timestamp int64
}

// DriverTypeSimpleValueOf returns the string format of DriverType
func DriverTypeSimpleValueOf(s string) (DriverType, error) {
	obj, err := simpleValueOf("driver_type", DriverType_value, s)
	return DriverType(obj), err
}

// SimpleString returns the string format of DriverType
func (x DriverType) SimpleString() string {
	return simpleString("driver_type", DriverType_name, int32(x))
}

// FSTypeSimpleValueOf returns the string format of FSType
func FSTypeSimpleValueOf(s string) (FSType, error) {
	obj, err := simpleValueOf("fs_type", FSType_value, s)
	return FSType(obj), err
}

// SimpleString returns the string format of DriverType
func (x FSType) SimpleString() string {
	return simpleString("fs_type", FSType_name, int32(x))
}

// CosTypeSimpleValueOf returns the string format of CosType
func CosTypeSimpleValueOf(s string) (CosType, error) {
	obj, exists := CosType_value[strings.ToUpper(s)]
	if !exists {
		return -1, fmt.Errorf("Invalid cos value: %s", s)
	}
	return CosType(obj), nil
}

// SimpleString returns the string format of CosType
func (x CosType) SimpleString() string {
	return simpleString("cos_type", CosType_name, int32(x))
}

// GraphDriverChangeTypeSimpleValueOf returns the string format of GraphDriverChangeType
func GraphDriverChangeTypeSimpleValueOf(s string) (GraphDriverChangeType, error) {
	obj, err := simpleValueOf("graph_driver_change_type", GraphDriverChangeType_value, s)
	return GraphDriverChangeType(obj), err
}

// SimpleString returns the string format of GraphDriverChangeType
func (x GraphDriverChangeType) SimpleString() string {
	return simpleString("graph_driver_change_type", GraphDriverChangeType_name, int32(x))
}

// VolumeActionParamSimpleValueOf returns the string format of VolumeAction
func VolumeActionParamSimpleValueOf(s string) (VolumeActionParam, error) {
	obj, err := simpleValueOf("volume_action_param", VolumeActionParam_value, s)
	return VolumeActionParam(obj), err
}

// SimpleString returns the string format of VolumeAction
func (x VolumeActionParam) SimpleString() string {
	return simpleString("volume_action_param", VolumeActionParam_name, int32(x))
}

// VolumeStateSimpleValueOf returns the string format of VolumeState
func VolumeStateSimpleValueOf(s string) (VolumeState, error) {
	obj, err := simpleValueOf("volume_state", VolumeState_value, s)
	return VolumeState(obj), err
}

// SimpleString returns the string format of VolumeState
func (x VolumeState) SimpleString() string {
	return simpleString("volume_state", VolumeState_name, int32(x))
}

// VolumeStatusSimpleValueOf returns the string format of VolumeStatus
func VolumeStatusSimpleValueOf(s string) (VolumeStatus, error) {
	obj, err := simpleValueOf("volume_status", VolumeStatus_value, s)
	return VolumeStatus(obj), err
}

// SimpleString returns the string format of VolumeStatus
func (x VolumeStatus) SimpleString() string {
	return simpleString("volume_status", VolumeStatus_name, int32(x))
}

// IoProfileSimpleValueOf returns the string format of IoProfile
func IoProfileSimpleValueOf(s string) (IoProfile, error) {
	obj, err := simpleValueOf("io_profile", IoProfile_value, s)
	return IoProfile(obj), err
}

// SimpleString returns the string format of IoProfile
func (x IoProfile) SimpleString() string {
	return simpleString("io_profile", IoProfile_name, int32(x))
}

func simpleValueOf(typeString string, valueMap map[string]int32, s string) (int32, error) {
	obj, ok := valueMap[strings.ToUpper(fmt.Sprintf("%s_%s", typeString, s))]
	if !ok {
		return 0, fmt.Errorf("no openstorage.%s for %s", strings.ToUpper(typeString), s)
	}
	return obj, nil
}

func simpleString(typeString string, nameMap map[int32]string, v int32) string {
	s, ok := nameMap[v]
	if !ok {
		return strconv.Itoa(int(v))
	}
	return strings.TrimPrefix(strings.ToLower(s), fmt.Sprintf("%s_", strings.ToLower(typeString)))
}

func toSec(ms uint64) uint64 {
	return ms / 1000
}

// WriteThroughput returns the write throughput
func (v *Stats) WriteThroughput() uint64 {
	if v.IntervalMs == 0 {
		return 0
	}
	return (v.WriteBytes) / toSec(v.IntervalMs)
}

// ReadThroughput returns the read throughput
func (v *Stats) ReadThroughput() uint64 {
	if v.IntervalMs == 0 {
		return 0
	}
	return (v.ReadBytes) / toSec(v.IntervalMs)
}

// Latency returns latency
func (v *Stats) Latency() uint64 {
	ops := v.Writes + v.Reads
	if ops == 0 {
		return 0
	}
	return (uint64)((v.IoMs * 1000) / (v.Writes + v.Reads))
}

// Iops returns iops
func (v *Stats) Iops() uint64 {
	if v.IntervalMs == 0 {
		return 0
	}
	return (v.Writes + v.Reads) / toSec(v.IntervalMs)
}

// Scaled returns true if the volume is scaled.
func (v *Volume) Scaled() bool {
	return v.Spec.Scale > 1
}

// Contains returns true if mid is a member of volume's replication set.
func (m *Volume) Contains(mid string) bool {
	rsets := m.GetReplicaSets()
	for _, rset := range rsets {
		for _, node := range rset.Nodes {
			if node == mid {
				return true
			}
		}
	}
	return false
}

// Copy makes a deep copy of VolumeSpec
func (s *VolumeSpec) Copy() *VolumeSpec {
	spec := *s
	if s.VolumeLabels != nil {
		spec.VolumeLabels = make(map[string]string)
		for k, v := range s.VolumeLabels {
			spec.VolumeLabels[k] = v
		}
	}
	if s.ReplicaSet != nil {
		spec.ReplicaSet = &ReplicaSet{Nodes: make([]string, len(s.ReplicaSet.Nodes))}
		copy(spec.ReplicaSet.Nodes, s.ReplicaSet.Nodes)
	}
	return &spec
}

// Copy makes a deep copy of Node
func (s *Node) Copy() *Node {
	localCopy := deepcopy.Copy(*s)
	nodeCopy := localCopy.(Node)
	return &nodeCopy
}
<|endoftext|>


<commit_before>package api

// A Result summarizes an executed SQL command.
type Result interface {
	// LastInsertId returns the integer generated by the database
	// in response to a command. Typically this will be from an
	// "auto increment" column when inserting a new row. Not all
	// databases support this feature, and the syntax of such
	// statements varies.
	LastInsertId() (int64, error)

	// RowsAffected returns the number of rows affected by an
	// update, insert, or delete. Not every database or database
	// driver may support this.
	RowsAffected() (int64, error)
}

// Rows is the result of a query. Its cursor starts before the first row
// of the result set. Use Next to advance through the rows:
//
//     rows, err := db.Query("SELECT ...")
//     ...
//     defer rows.Close()
//     for rows.Next() {
//         var id int
//         var name string
//         err = rows.Scan(&id, &name)
//         ...
//     }
//     err = rows.Err() // get any error encountered during iteration
//     ...
type Rows interface {
	// Next prepares the next result row for reading with the Scan method.  It
	// returns true on success, or false if there is no next result row or an error
	// happened while preparing it.  Err should be consulted to distinguish between
	// the two cases.
	//
	// Every call to Scan, even the first one, must be preceded by a call to Next.
	Next() bool

	// Err returns the error, if any, that was encountered during iteration.
	// Err may be called after an explicit or implicit Close.
	Err() error

	// Columns returns the column names.
	// Columns returns an error if the rows are closed, or if the rows
	// are from QueryRow and there was a deferred error.
	Columns() ([]string, error)

	// Scan copies the columns in the current row into the values pointed
	// at by dest. The number of values in dest must be the same as the
	// number of columns in Rows.
	//
	// Scan converts columns read from the database into the following
	// common Go types and special types provided by the sql package:
	//
	//    *string
	//    *[]byte
	//    *int, *int8, *int16, *int32, *int64
	//    *uint, *uint8, *uint16, *uint32, *uint64
	//    *bool
	//    *float32, *float64
	//    *interface{}
	//    *RawBytes
	//    any type implementing Scanner (see Scanner docs)
	//
	// In the most simple case, if the type of the value from the source
	// column is an integer, bool or string type T and dest is of type *T,
	// Scan simply assigns the value through the pointer.
	//
	// Scan also converts between string and numeric types, as long as no
	// information would be lost. While Scan stringifies all numbers
	// scanned from numeric database columns into *string, scans into
	// numeric types are checked for overflow. For example, a float64 with
	// value 300 or a string with value "300" can scan into a uint16, but
	// not into a uint8, though float64(255) or "255" can scan into a
	// uint8. One exception is that scans of some float64 numbers to
	// strings may lose information when stringifying. In general, scan
	// floating point columns into *float64.
	//
	// If a dest argument has type *[]byte, Scan saves in that argument a
	// copy of the corresponding data. The copy is owned by the caller and
	// can be modified and held indefinitely. The copy can be avoided by
	// using an argument of type *RawBytes instead; see the documentation
	// for RawBytes for restrictions on its use.
	//
	// If an argument has type *interface{}, Scan copies the value
	// provided by the underlying driver without conversion. When scanning
	// from a source value of type []byte to *interface{}, a copy of the
	// slice is made and the caller owns the result.
	//
	// Source values of type time.Time may be scanned into values of type
	// *time.Time, *interface{}, *string, or *[]byte. When converting to
	// the latter two, time.Format3339Nano is used.
	//
	// Source values of type bool may be scanned into types *bool,
	// *interface{}, *string, *[]byte, or *RawBytes.
	//
	// For scanning into *bool, the source may be true, false, 1, 0, or
	// string inputs parseable by strconv.ParseBool.
	Scan(dest ...interface{}) error

	// Close closes the Rows, preventing further enumeration. If Next returns
	// false, the Rows are closed automatically and it will suffice to check the
	// result of Err. Close is idempotent and does not affect the result of Err.
	Close() error
}

// Executor runs the queries that are processed by proteus.
type Executor interface {
	// Exec executes a query without returning any rows.
	// The args are for any placeholder parameters in the query.
	Exec(query string, args ...interface{}) (Result, error)

	// Query executes a query that returns rows, typically a SELECT.
	// The args are for any placeholder parameters in the query.
	Query(query string, args ...interface{}) (Rows, error)
}

// ParamAdapter maps to valid positional parameters in a DBMS.
// For example, MySQL uses ? for every parameter, while Postgres uses $NUM and Oracle uses :NUM
type ParamAdapter func(pos int) string
<commit_msg>Removing LastInsertId from api.Result because it's probably not useful (postgres doesn't support it, sqlite was giving back a nonsense value)<commit_after>package api

// A Result summarizes an executed SQL command.
type Result interface {
	// RowsAffected returns the number of rows affected by an
	// update, insert, or delete. Not every database or database
	// driver may support this.
	RowsAffected() (int64, error)
}

// Rows is the result of a query. Its cursor starts before the first row
// of the result set. Use Next to advance through the rows:
//
//     rows, err := db.Query("SELECT ...")
//     ...
//     defer rows.Close()
//     for rows.Next() {
//         var id int
//         var name string
//         err = rows.Scan(&id, &name)
//         ...
//     }
//     err = rows.Err() // get any error encountered during iteration
//     ...
type Rows interface {
	// Next prepares the next result row for reading with the Scan method.  It
	// returns true on success, or false if there is no next result row or an error
	// happened while preparing it.  Err should be consulted to distinguish between
	// the two cases.
	//
	// Every call to Scan, even the first one, must be preceded by a call to Next.
	Next() bool

	// Err returns the error, if any, that was encountered during iteration.
	// Err may be called after an explicit or implicit Close.
	Err() error

	// Columns returns the column names.
	// Columns returns an error if the rows are closed, or if the rows
	// are from QueryRow and there was a deferred error.
	Columns() ([]string, error)

	// Scan copies the columns in the current row into the values pointed
	// at by dest. The number of values in dest must be the same as the
	// number of columns in Rows.
	//
	// Scan converts columns read from the database into the following
	// common Go types and special types provided by the sql package:
	//
	//    *string
	//    *[]byte
	//    *int, *int8, *int16, *int32, *int64
	//    *uint, *uint8, *uint16, *uint32, *uint64
	//    *bool
	//    *float32, *float64
	//    *interface{}
	//    *RawBytes
	//    any type implementing Scanner (see Scanner docs)
	//
	// In the most simple case, if the type of the value from the source
	// column is an integer, bool or string type T and dest is of type *T,
	// Scan simply assigns the value through the pointer.
	//
	// Scan also converts between string and numeric types, as long as no
	// information would be lost. While Scan stringifies all numbers
	// scanned from numeric database columns into *string, scans into
	// numeric types are checked for overflow. For example, a float64 with
	// value 300 or a string with value "300" can scan into a uint16, but
	// not into a uint8, though float64(255) or "255" can scan into a
	// uint8. One exception is that scans of some float64 numbers to
	// strings may lose information when stringifying. In general, scan
	// floating point columns into *float64.
	//
	// If a dest argument has type *[]byte, Scan saves in that argument a
	// copy of the corresponding data. The copy is owned by the caller and
	// can be modified and held indefinitely. The copy can be avoided by
	// using an argument of type *RawBytes instead; see the documentation
	// for RawBytes for restrictions on its use.
	//
	// If an argument has type *interface{}, Scan copies the value
	// provided by the underlying driver without conversion. When scanning
	// from a source value of type []byte to *interface{}, a copy of the
	// slice is made and the caller owns the result.
	//
	// Source values of type time.Time may be scanned into values of type
	// *time.Time, *interface{}, *string, or *[]byte. When converting to
	// the latter two, time.Format3339Nano is used.
	//
	// Source values of type bool may be scanned into types *bool,
	// *interface{}, *string, *[]byte, or *RawBytes.
	//
	// For scanning into *bool, the source may be true, false, 1, 0, or
	// string inputs parseable by strconv.ParseBool.
	Scan(dest ...interface{}) error

	// Close closes the Rows, preventing further enumeration. If Next returns
	// false, the Rows are closed automatically and it will suffice to check the
	// result of Err. Close is idempotent and does not affect the result of Err.
	Close() error
}

// Executor runs the queries that are processed by proteus.
type Executor interface {
	// Exec executes a query without returning any rows.
	// The args are for any placeholder parameters in the query.
	Exec(query string, args ...interface{}) (Result, error)

	// Query executes a query that returns rows, typically a SELECT.
	// The args are for any placeholder parameters in the query.
	Query(query string, args ...interface{}) (Rows, error)
}

// ParamAdapter maps to valid positional parameters in a DBMS.
// For example, MySQL uses ? for every parameter, while Postgres uses $NUM and Oracle uses :NUM
type ParamAdapter func(pos int) string
<|endoftext|>


<commit_before>// @APIVersion 1.0.0
// @APITitle 9volt API
// @APIDescription 9volt's API for fetching check state, event data and cluster information
// @Contact daniel.selans@gmail.com
// @License MIT
// @LicenseUrl https://opensource.org/licenses/MIT
// @BasePath /api/v1
// @SubApi Cluster State [/cluster]
// @SubApi Monitor Configuration [/monitor]

package api

import (
	"net/http"

	"github.com/InVisionApp/rye"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"

	"github.com/9corp/9volt/config"
)

type Api struct {
	Config     *config.Config
	Version    string
	MemberID   string
	Identifier string
	MWHandler  *rye.MWHandler
	DebugUI    bool
}

type JSONStatus struct {
	Status  string
	Message string
}

func New(cfg *config.Config, mwHandler *rye.MWHandler, version string, debugUI bool) *Api {
	return &Api{
		Config:     cfg,
		Version:    version,
		MemberID:   cfg.MemberID,
		Identifier: "api",
		MWHandler:  mwHandler,
		DebugUI:    debugUI,
	}
}

func (a *Api) Run() {
	log.Debugf("%v: Starting API server", a.Identifier)

	routes := mux.NewRouter().StrictSlash(true)

	routes.Handle("/", a.MWHandler.Handle([]rye.Handler{
		a.HomeHandler,
	})).Methods("GET")

	// Common handlers
	routes.Handle("/version", a.MWHandler.Handle([]rye.Handler{
		a.VersionHandler,
	})).Methods("GET")

	routes.Handle("/status/check", a.MWHandler.Handle([]rye.Handler{
		a.StatusHandler,
	})).Methods("GET")

	// State handlers (route order matters!)
	routes.Handle("/api/v1/state", a.MWHandler.Handle([]rye.Handler{
		a.StateWithTagsHandler,
	})).Methods("GET").Queries("tags", "")

	routes.Handle("/api/v1/state", a.MWHandler.Handle([]rye.Handler{
		a.StateHandler,
	})).Methods("GET")

	// Cluster handlers
	routes.Handle("/api/v1/cluster", a.MWHandler.Handle([]rye.Handler{
		a.ClusterHandler,
	})).Methods("GET")

	// Monitor handlers (route order matters!)
	routes.Handle("/api/v1/monitor", a.MWHandler.Handle([]rye.Handler{
		a.MonitorHandler,
	})).Methods("GET")

	routes.Handle("/api/v1/monitor/{check}", a.MWHandler.Handle([]rye.Handler{
		a.MonitorDisableHandler,
	})).Methods("GET").Queries("disable", "")

	routes.Handle("/api/v1/monitor/{check}", a.MWHandler.Handle([]rye.Handler{
		a.MonitorCheckHandler,
	})).Methods("GET")

	// Events handlers
	routes.Handle("/api/v1/event", a.MWHandler.Handle([]rye.Handler{
		a.EventWithTypeHandler,
	})).Methods("GET").Queries("type", "")

	routes.Handle("/api/v1/event", a.MWHandler.Handle([]rye.Handler{
		a.EventHandler,
	})).Methods("GET")

	if a.DebugUI {
		log.Info("ui: local debug mode (from /ui/dist)")
		routes.PathPrefix("/dist").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiDistHandler,
		}))

		routes.PathPrefix("/ui").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiHandler,
		}))
	} else {
		log.Info("ui: statik mode (from statik.go)")
		routes.PathPrefix("/dist").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiDistStatikHandler,
		}))

		routes.PathPrefix("/ui").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiStatikHandler,
		}))
	}

	http.ListenAndServe(a.Config.ListenAddress, routes)
}
<commit_msg>route logger<commit_after>// @APIVersion 1.0.0
// @APITitle 9volt API
// @APIDescription 9volt's API for fetching check state, event data and cluster information
// @Contact daniel.selans@gmail.com
// @License MIT
// @LicenseUrl https://opensource.org/licenses/MIT
// @BasePath /api/v1
// @SubApi Cluster State [/cluster]
// @SubApi Monitor Configuration [/monitor]

package api

import (
	"net/http"
	"os"

	"github.com/InVisionApp/rye"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"

	"github.com/9corp/9volt/config"
)

type Api struct {
	Config     *config.Config
	Version    string
	MemberID   string
	Identifier string
	MWHandler  *rye.MWHandler
	DebugUI    bool
}

type JSONStatus struct {
	Status  string
	Message string
}

func New(cfg *config.Config, mwHandler *rye.MWHandler, version string, debugUI bool) *Api {
	return &Api{
		Config:     cfg,
		Version:    version,
		MemberID:   cfg.MemberID,
		Identifier: "api",
		MWHandler:  mwHandler,
		DebugUI:    debugUI,
	}
}

func (a *Api) Run() {
	log.Debugf("%v: Starting API server", a.Identifier)

	routes := mux.NewRouter().StrictSlash(true)

	routes.Handle(a.setupHandler(
		"/", []rye.Handler{
			a.HomeHandler,
		})).Methods("GET")

	// Common handlers
	routes.Handle(a.setupHandler(
		"/version", []rye.Handler{
			a.VersionHandler,
		})).Methods("GET")

	routes.Handle(a.setupHandler(
		"/status/check", []rye.Handler{
			a.StatusHandler,
		})).Methods("GET")

	// State handlers (route order matters!)
	routes.Handle(a.setupHandler(
		"/api/v1/state", []rye.Handler{
			a.StateWithTagsHandler,
		})).Methods("GET").Queries("tags", "")

	routes.Handle(a.setupHandler(
		"/api/v1/state", []rye.Handler{
			a.StateHandler,
		})).Methods("GET")

	// Cluster handlers
	routes.Handle(a.setupHandler(
		"/api/v1/cluster", []rye.Handler{
			a.ClusterHandler,
		})).Methods("GET")

	// Monitor handlers (route order matters!)
	routes.Handle(a.setupHandler(
		"/api/v1/monitor", []rye.Handler{
			a.MonitorHandler,
		})).Methods("GET")

	routes.Handle(a.setupHandler(
		"/api/v1/monitor/{check}", []rye.Handler{
			a.MonitorDisableHandler,
		})).Methods("GET").Queries("disable", "")

	routes.Handle(a.setupHandler(
		"/api/v1/monitor/{check}", []rye.Handler{
			a.MonitorCheckHandler,
		})).Methods("GET")

	// Events handlers
	routes.Handle(a.setupHandler(
		"/api/v1/event", []rye.Handler{
			a.EventWithTypeHandler,
		})).Methods("GET").Queries("type", "")

	routes.Handle(a.setupHandler(
		"/api/v1/event", []rye.Handler{
			a.EventHandler,
		})).Methods("GET")

	if a.DebugUI {
		log.Info("ui: local debug mode (from /ui/dist)")
		routes.PathPrefix("/dist").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiDistHandler,
		}))

		routes.PathPrefix("/ui").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiHandler,
		}))
	} else {
		log.Info("ui: statik mode (from statik.go)")
		routes.PathPrefix("/dist").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiDistStatikHandler,
		}))

		routes.PathPrefix("/ui").Handler(a.MWHandler.Handle([]rye.Handler{
			rye.MiddlewareRouteLogger(),
			a.uiStatikHandler,
		}))
	}

	http.ListenAndServe(a.Config.ListenAddress, routes)
}

// appends an apache style logger to each route. also dry up some boiler plate
func (a *Api) setupHandler(path string, ryeStack []rye.Handler) (string, http.Handler) {
	return path, handlers.LoggingHandler(os.Stdout, a.MWHandler.Handle(ryeStack))
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 The Android Open Source Project
//
// 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 main

import (
	"bufio"
	"compress/gzip"
	"encoding/json"
	"errors"
	"flag"
	"github.com/robfig/soy"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

var (
	restHost = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
	port     = flag.String("port", ":8081", "Port to serve HTTP requests on")
	prod     = flag.Bool("prod", false, "Serve production assets")
	scheme   = flag.String("scheme", "https", "URL scheme")
	plugins  = flag.String("plugins", "", "comma seperated plugin paths to serve")

	tofu, _ = soy.NewBundle().
		AddTemplateFile("../resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy").
		CompileToTofu()
)

func main() {
	flag.Parse()

	http.HandleFunc("/index.html", handleIndex)

	if *prod {
		http.Handle("/", http.FileServer(http.Dir("dist")))
	} else {
		http.Handle("/", http.FileServer(http.Dir("app")))
	}

	http.HandleFunc("/changes/", handleRESTProxy)
	http.HandleFunc("/accounts/", handleRESTProxy)
	http.HandleFunc("/config/", handleRESTProxy)
	http.HandleFunc("/projects/", handleRESTProxy)
	http.HandleFunc("/accounts/self/detail", handleAccountDetail)
	if len(*plugins) > 0 {
		http.Handle("/plugins/", http.StripPrefix("/plugins/",
			http.FileServer(http.Dir("../plugins"))))
		log.Println("Local plugins from", "../plugins")
	} else {
		http.HandleFunc("/plugins/", handleRESTProxy)
	}
	log.Println("Serving on port", *port)
	log.Fatal(http.ListenAndServe(*port, &server{}))
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	var obj = map[string]interface{}{
		"canonicalPath":      "",
		"staticResourcePath": "",
	}
	w.Header().Set("Content-Type", "text/html")
	tofu.Render(w, "com.google.gerrit.httpd.raw.Index", obj)
}

func handleRESTProxy(w http.ResponseWriter, r *http.Request) {
	if strings.HasSuffix(r.URL.Path, ".html") {
		w.Header().Set("Content-Type", "text/html")
	} else if strings.HasSuffix(r.URL.Path, ".css") {
		w.Header().Set("Content-Type", "text/css")
	} else {
		w.Header().Set("Content-Type", "application/json")
	}
	req := &http.Request{
		Method: "GET",
		URL: &url.URL{
			Scheme:   *scheme,
			Host:     *restHost,
			Opaque:   r.URL.EscapedPath(),
			RawQuery: r.URL.RawQuery,
		},
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer res.Body.Close()
	w.WriteHeader(res.StatusCode)
	if _, err := io.Copy(w, patchResponse(r, res)); err != nil {
		log.Println("Error copying response to ResponseWriter:", err)
		return
	}
}

func getJsonPropByPath(json map[string]interface{}, path []string) interface{} {
	prop, path := path[0], path[1:]
	if json[prop] == nil {
		return nil
	}
	switch json[prop].(type) {
	case map[string]interface{}: // map
		return getJsonPropByPath(json[prop].(map[string]interface{}), path)
	case []interface{}: // array
		return json[prop].([]interface{})
	default:
		return json[prop].(interface{})
	}
}

func setJsonPropByPath(json map[string]interface{}, path []string, value interface{}) {
	prop, path := path[0], path[1:]
	if json[prop] == nil {
		return // path not found
	}
	if len(path) > 0 {
		setJsonPropByPath(json[prop].(map[string]interface{}), path, value)
	} else {
		json[prop] = value
	}
}

func patchResponse(r *http.Request, res *http.Response) io.Reader {
	switch r.URL.EscapedPath() {
	case "/config/server/info":
		return injectLocalPlugins(res.Body)
	default:
		return res.Body
	}
}

func injectLocalPlugins(r io.Reader) io.Reader {
	if len(*plugins) == 0 {
		return r
	}
	// Skip escape prefix
	io.CopyN(ioutil.Discard, r, 5)
	dec := json.NewDecoder(r)

	var response map[string]interface{}
	err := dec.Decode(&response)
	if err != nil {
		log.Fatal(err)
	}

	// Configuration path in the JSON server response
	jsPluginsPath := []string{"plugin", "js_resource_paths"}
	htmlPluginsPath := []string{"plugin", "html_resource_paths"}
	htmlResources := getJsonPropByPath(response, htmlPluginsPath).([]interface{})
	jsResources := getJsonPropByPath(response, jsPluginsPath).([]interface{})

	for _, p := range strings.Split(*plugins, ",") {
		if strings.HasSuffix(p, ".html") {
			htmlResources = append(htmlResources, p)
		}

		if strings.HasSuffix(p, ".js") {
			jsResources = append(jsResources, p)
		}
	}

	setJsonPropByPath(response, jsPluginsPath, jsResources)
	setJsonPropByPath(response, htmlPluginsPath, htmlResources)

	reader, writer := io.Pipe()
	go func() {
		defer writer.Close()
		io.WriteString(writer, ")]}'") // Write escape prefix
		err := json.NewEncoder(writer).Encode(&response)
		if err != nil {
			log.Fatal(err)
		}
	}()
	return reader
}

func handleAccountDetail(w http.ResponseWriter, r *http.Request) {
	http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

type gzipResponseWriter struct {
	io.WriteCloser
	http.ResponseWriter
}

func newGzipResponseWriter(w http.ResponseWriter) *gzipResponseWriter {
	gz := gzip.NewWriter(w)
	return &gzipResponseWriter{WriteCloser: gz, ResponseWriter: w}
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
	return w.WriteCloser.Write(b)
}

func (w gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h, ok := w.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, errors.New("gzipResponseWriter: ResponseWriter does not satisfy http.Hijacker interface")
	}
	return h.Hijack()
}

type server struct{}

// Any path prefixes that should resolve to index.html.
var (
	fePaths    = []string{"/q/", "/c/", "/p/", "/x/", "/dashboard/", "/admin/"}
	issueNumRE = regexp.MustCompile(`^\/\d+\/?$`)
)

func (_ *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s %s %s\n", r.Proto, r.Method, r.RemoteAddr, r.URL)
	for _, prefix := range fePaths {
		if strings.HasPrefix(r.URL.Path, prefix) || r.URL.Path == "/" {
			r.URL.Path = "/index.html"
			log.Println("Redirecting to /index.html")
			break
		} else if match := issueNumRE.Find([]byte(r.URL.Path)); match != nil {
			r.URL.Path = "/index.html"
			log.Println("Redirecting to /index.html")
			break
		}
	}
	if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		http.DefaultServeMux.ServeHTTP(w, r)
		return
	}
	w.Header().Set("Content-Encoding", "gzip")
	gzw := newGzipResponseWriter(w)
	defer gzw.Close()
	http.DefaultServeMux.ServeHTTP(gzw, r)
}
<commit_msg>Proxy headers for server.go<commit_after>// Copyright (C) 2015 The Android Open Source Project
//
// 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 main

import (
	"bufio"
	"compress/gzip"
	"encoding/json"
	"errors"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/robfig/soy"
)

var (
	restHost = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
	port     = flag.String("port", ":8081", "Port to serve HTTP requests on")
	prod     = flag.Bool("prod", false, "Serve production assets")
	scheme   = flag.String("scheme", "https", "URL scheme")
	plugins  = flag.String("plugins", "", "comma seperated plugin paths to serve")

	tofu, _ = soy.NewBundle().
		AddTemplateFile("../resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy").
		CompileToTofu()
)

func main() {
	flag.Parse()

	http.HandleFunc("/index.html", handleIndex)

	if *prod {
		http.Handle("/", http.FileServer(http.Dir("dist")))
	} else {
		http.Handle("/", http.FileServer(http.Dir("app")))
	}

	http.HandleFunc("/changes/", handleRESTProxy)
	http.HandleFunc("/accounts/", handleRESTProxy)
	http.HandleFunc("/config/", handleRESTProxy)
	http.HandleFunc("/projects/", handleRESTProxy)
	http.HandleFunc("/accounts/self/detail", handleAccountDetail)
	if len(*plugins) > 0 {
		http.Handle("/plugins/", http.StripPrefix("/plugins/",
			http.FileServer(http.Dir("../plugins"))))
		log.Println("Local plugins from", "../plugins")
	} else {
		http.HandleFunc("/plugins/", handleRESTProxy)
	}
	log.Println("Serving on port", *port)
	log.Fatal(http.ListenAndServe(*port, &server{}))
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	var obj = map[string]interface{}{
		"canonicalPath":      "",
		"staticResourcePath": "",
	}
	w.Header().Set("Content-Type", "text/html")
	tofu.Render(w, "com.google.gerrit.httpd.raw.Index", obj)
}

func handleRESTProxy(w http.ResponseWriter, r *http.Request) {
	req := &http.Request{
		Method: "GET",
		URL: &url.URL{
			Scheme:   *scheme,
			Host:     *restHost,
			Opaque:   r.URL.EscapedPath(),
			RawQuery: r.URL.RawQuery,
		},
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer res.Body.Close()
	for name, values := range res.Header {
		for _, value := range values {
			if name != "Content-Length" {
				w.Header().Add(name, value)
			}
		}
	}
	w.WriteHeader(res.StatusCode)
	if _, err := io.Copy(w, patchResponse(r, res)); err != nil {
		log.Println("Error copying response to ResponseWriter:", err)
		return
	}
}

func getJsonPropByPath(json map[string]interface{}, path []string) interface{} {
	prop, path := path[0], path[1:]
	if json[prop] == nil {
		return nil
	}
	switch json[prop].(type) {
	case map[string]interface{}: // map
		return getJsonPropByPath(json[prop].(map[string]interface{}), path)
	case []interface{}: // array
		return json[prop].([]interface{})
	default:
		return json[prop].(interface{})
	}
}

func setJsonPropByPath(json map[string]interface{}, path []string, value interface{}) {
	prop, path := path[0], path[1:]
	if json[prop] == nil {
		return // path not found
	}
	if len(path) > 0 {
		setJsonPropByPath(json[prop].(map[string]interface{}), path, value)
	} else {
		json[prop] = value
	}
}

func patchResponse(r *http.Request, res *http.Response) io.Reader {
	switch r.URL.EscapedPath() {
	case "/config/server/info":
		return injectLocalPlugins(res.Body)
	default:
		return res.Body
	}
}

func injectLocalPlugins(r io.Reader) io.Reader {
	if len(*plugins) == 0 {
		return r
	}
	// Skip escape prefix
	io.CopyN(ioutil.Discard, r, 5)
	dec := json.NewDecoder(r)

	var response map[string]interface{}
	err := dec.Decode(&response)
	if err != nil {
		log.Fatal(err)
	}

	// Configuration path in the JSON server response
	jsPluginsPath := []string{"plugin", "js_resource_paths"}
	htmlPluginsPath := []string{"plugin", "html_resource_paths"}
	htmlResources := getJsonPropByPath(response, htmlPluginsPath).([]interface{})
	jsResources := getJsonPropByPath(response, jsPluginsPath).([]interface{})

	for _, p := range strings.Split(*plugins, ",") {
		if strings.HasSuffix(p, ".html") {
			htmlResources = append(htmlResources, p)
		}

		if strings.HasSuffix(p, ".js") {
			jsResources = append(jsResources, p)
		}
	}

	setJsonPropByPath(response, jsPluginsPath, jsResources)
	setJsonPropByPath(response, htmlPluginsPath, htmlResources)

	reader, writer := io.Pipe()
	go func() {
		defer writer.Close()
		io.WriteString(writer, ")]}'") // Write escape prefix
		err := json.NewEncoder(writer).Encode(&response)
		if err != nil {
			log.Fatal(err)
		}
	}()
	return reader
}

func handleAccountDetail(w http.ResponseWriter, r *http.Request) {
	http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

type gzipResponseWriter struct {
	io.WriteCloser
	http.ResponseWriter
}

func newGzipResponseWriter(w http.ResponseWriter) *gzipResponseWriter {
	gz := gzip.NewWriter(w)
	return &gzipResponseWriter{WriteCloser: gz, ResponseWriter: w}
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
	return w.WriteCloser.Write(b)
}

func (w gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h, ok := w.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, errors.New("gzipResponseWriter: ResponseWriter does not satisfy http.Hijacker interface")
	}
	return h.Hijack()
}

type server struct{}

// Any path prefixes that should resolve to index.html.
var (
	fePaths    = []string{"/q/", "/c/", "/p/", "/x/", "/dashboard/", "/admin/"}
	issueNumRE = regexp.MustCompile(`^\/\d+\/?$`)
)

func (_ *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s %s %s\n", r.Proto, r.Method, r.RemoteAddr, r.URL)
	for _, prefix := range fePaths {
		if strings.HasPrefix(r.URL.Path, prefix) || r.URL.Path == "/" {
			r.URL.Path = "/index.html"
			log.Println("Redirecting to /index.html")
			break
		} else if match := issueNumRE.Find([]byte(r.URL.Path)); match != nil {
			r.URL.Path = "/index.html"
			log.Println("Redirecting to /index.html")
			break
		}
	}
	if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		http.DefaultServeMux.ServeHTTP(w, r)
		return
	}
	w.Header().Set("Content-Encoding", "gzip")
	gzw := newGzipResponseWriter(w)
	defer gzw.Close()
	http.DefaultServeMux.ServeHTTP(gzw, r)
}
<|endoftext|>


<commit_before>package api

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"strings"
)

type Git struct {
	GitExec   string
	Cmd       *exec.Cmd
	Path      string
	Env       []string
	Args      []string
	Success   bool
	Pid       int
	Duration  int
	Errors    []string
	Log       string
	Overwrite bool
	Out       bytes.Buffer
	OutPipe   io.ReadCloser
	Err       bytes.Buffer
	ErrPipe   io.ReadCloser
}

func NewGit(args string) *Git {
	return &Git{GitExec: "git", Args: strings.Fields(args)}
}

func (g *Git) Stream(l *os.File) (<-chan string, <-chan error) {

	lines := make(chan string)
	errc := make(chan error, 1)

	scanner := bufio.NewScanner(g.OutPipe)

	// Register for interrupts so that we can catch it and immediately
	// return...
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	go func() {
		defer close(lines)
		var err error
		var line string

		errc <- func() error {
			for {
				if !scanner.Scan() {
					err = io.EOF
					break
				}
				line = scanner.Text()
				select {
				case lines <- line:
				}
			}
			return err
		}()

	}()

	return lines, errc
}

func (g *Git) Reader(lines <-chan string, errc <-chan error) {
	go func() {
		for {
			select {
			case line := <-lines:
				fmt.Println(line)
			case <-errc:
				return
			}
		}
	}()

}

func (g *Git) Streamer() error {
	g.Cmd = exec.Command(g.GitExec, g.Args...)

	g.OutPipe, _ = g.Cmd.StdoutPipe()
	err := g.Cmd.Start()
	if err != nil {
		return err
	}
	g.Pid = g.Cmd.Process.Pid
	g.Reader(g.Stream(os.Stdout))

	return g.Cmd.Wait()
}

func (g *Git) Run() error {
	g.Cmd = exec.Command(g.GitExec, g.Args...)
	g.Cmd.Stdout = &g.Out
	g.Cmd.Stderr = &g.Err
	return g.Cmd.Run()
}
<commit_msg>Refactor<commit_after>package api

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"strings"
)

type Git struct {
	GitExec   string
	Cmd       *exec.Cmd
	Path      string
	Env       []string
	Args      []string
	Success   bool
	Pid       int
	Duration  int
	Errors    []string
	Log       string
	Overwrite bool
	Out       bytes.Buffer
	OutPipe   io.ReadCloser
	Err       bytes.Buffer
	ErrPipe   io.ReadCloser
}

func NewGit(a string) *Git {
	args := strings.Fields(a)
	return &Git{GitExec: "git", Args: args, Cmd: exec.Command("git", args...)}
}

func (g *Git) Stream(l *os.File) (<-chan string, <-chan error) {

	lines := make(chan string)
	errc := make(chan error, 1)

	scanner := bufio.NewScanner(g.OutPipe)

	// Register for interrupts so that we can catch it and immediately
	// return...
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	go func() {
		defer close(lines)
		var err error
		var line string

		errc <- func() error {
			for {
				if !scanner.Scan() {
					err = io.EOF
					break
				}
				line = scanner.Text()
				select {
				case lines <- line:
				}
			}
			return err
		}()

	}()

	return lines, errc
}

func (g *Git) Reader(lines <-chan string, errc <-chan error) {
	go func() {
		for {
			select {
			case line := <-lines:
				fmt.Println(line)
			case <-errc:
				return
			}
		}
	}()

}

func (g *Git) Streamer() error {
	g.OutPipe, _ = g.Cmd.StdoutPipe()
	err := g.Cmd.Start()
	if err != nil {
		return err
	}
	g.Pid = g.Cmd.Process.Pid
	g.Reader(g.Stream(os.Stdout))

	return g.Cmd.Wait()
}

func (g *Git) Run() error {
	g.Cmd.Stdout = &g.Out
	g.Cmd.Stderr = &g.Err
	return g.Cmd.Run()
}
<|endoftext|>


<commit_before>//
// Treasure Data API client for Go
//
// Copyright (C) 2014 Treasure Data, 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 td_client

import (
	"fmt"
	"io"
	"net/url"
	"strconv"
	"time"
)

type ListJobsResultElement struct {
	Id         string
	Type       string
	Database   string
	Status     string
	Query      string
	Duration   int
	CreatedAt  time.Time
	UpdatedAt  time.Time
	StartAt    time.Time
	EndAt      time.Time
	CpuTime    float64
	ResultSize int
	NumRecords int
	ResultUrl  string
	Priority   int
	RetryLimit int
}

type ListJobsResultElements []ListJobsResultElement

type ListJobsResult struct {
	ListJobsResultElements ListJobsResultElements
	Count                  int
	From                   string
	To                     string
}

var listJobsSchema = map[string]interface{}{
	"jobs": []map[string]interface{}{
		{
			"job_id":                      "",
			"type":                        Optional{"", "?"},
			"database":                    "",
			"status":                      "",
			"query":                       "",
			"start_at":                    Optional{time.Time{}, time.Time{}},
			"end_at":                      Optional{time.Time{}, time.Time{}},
			"created_at":                  time.Time{},
			"updated_at":                  time.Time{},
			"duration":                    Optional{0., 0.},
			"cpu_time":                    Optional{0., 0.},
			"result_size":                 Optional{0, 0},
			"num_records":                 Optional{0, 0},
			"user_name":                   "",
			"result":                      "",
			"url":                         "",
			"hive_result_schema":          Optional{"", "?"},
			"organization":                Optional{"", "?"},
			"priority":                    0,
			"retry_limit":                 0,
			"result_export_target_job_id": Optional{0., 0.},
			"linked_result_export_job_id": Optional{0., 0.},
		},
	},
	"count": Optional{0, 0},
	"to":    Optional{"", "?"},
	"from":  Optional{"", "?"},
}

var jobStatusSchema = map[string]interface{}{
	"status":      "",
	"job_id":      "",
	"start_at":    Optional{time.Time{}, time.Time{}},
	"created_at":  time.Time{},
	"updated_at":  time.Time{},
	"end_at":      Optional{time.Time{}, time.Time{}},
	"duration":    Optional{0., 0.},
	"cpu_time":    Optional{0., 0.},
	"result_size": Optional{0, 0},
	"num_records": Optional{0, 0},
}

type ShowJobResultDebugElement struct {
	CmdOut string
	StdErr string
}

// ShowJobResult stores the result of `ShowJobResult` API call.
type ShowJobResult struct {
	Id               string
	Type             string
	Database         string
	UserName         string
	Status           string
	Query            string
	Debug            ShowJobResultDebugElement
	Url              string
	Duration         int
	CreatedAt        time.Time
	UpdatedAt        time.Time
	StartAt          time.Time
	EndAt            time.Time
	CpuTime          float64
	ResultSize       int
	NumRecords       int
	ResultUrl        string
	Priority         int
	RetryLimit       int
	HiveResultSchema []interface{}
}

var showJobSchema = map[string]interface{}{
	"job_id":       "",
	"type":         Optional{"", "?"},
	"organization": Optional{"", ""},
	"user_name":    "",
	"database":     "",
	"status":       "",
	"query":        "",
	"debug": map[string]interface{}{
		"cmdout": Optional{"", ""},
		"stderr": Optional{"", ""},
	},
	"url":                         "",
	"duration":                    Optional{0, 0},
	"created_at":                  time.Time{},
	"updated_at":                  time.Time{},
	"start_at":                    Optional{time.Time{}, time.Time{}},
	"end_at":                      Optional{time.Time{}, time.Time{}},
	"cpu_time":                    Optional{0., 0.},
	"result_size":                 Optional{0, 0},
	"num_records":                 Optional{0, 0},
	"result":                      "",
	"priority":                    0,
	"retry_limit":                 0,
	"hive_result_schema":          Optional{EmbeddedJSON([]interface{}{}), nil},
	"result_export_target_job_id": Optional{0., 0.},
	"linked_result_export_job_id": Optional{0., 0.},
}

type Query struct {
	Type       string
	Query      string
	ResultUrl  string
	Priority   int
	RetryLimit int
}

var submitJobSchema = map[string]interface{}{
	"job":      "",
	"job_id":   "",
	"database": "",
}

var submitPartialDeleteJobSchema = map[string]interface{}{
	"job_id":   0,
	"database": "",
	"table":    "",
	"from":     0,
	"to":       0,
}

func (client *TDClient) ListJobs() (*ListJobsResult, error) {
	resp, err := client.get("/v3/job/list", nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, client.buildError(resp, -1, "List jobs failed", nil)
	}
	js, err := client.checkedJson(resp, listJobsSchema)
	if err != nil {
		return nil, err
	}
	jobs := js["jobs"].([]map[string]interface{})
	listJobsResult := ListJobsResult{}
	retval := make(ListJobsResultElements, len(jobs))
	for i, v := range jobs {
		retval[i] = ListJobsResultElement{
			Id:         v["job_id"].(string),
			Type:       v["type"].(string),
			Database:   v["database"].(string),
			Status:     v["status"].(string),
			Query:      v["query"].(string),
			StartAt:    v["start_at"].(time.Time),
			EndAt:      v["end_at"].(time.Time),
			CpuTime:    v["cpu_time"].(float64),
			ResultSize: v["result_size"].(int),
			NumRecords: v["num_records"].(int),
			ResultUrl:  v["result"].(string),
			Priority:   v["priority"].(int),
			RetryLimit: v["retry_limit"].(int),
		}
	}
	listJobsResult.ListJobsResultElements = retval
	listJobsResult.Count = js["count"].(int)
	listJobsResult.From = js["from"].(string)
	listJobsResult.To = js["to"].(string)
	return &listJobsResult, nil
}

func (client *TDClient) ShowJob(jobId string) (*ShowJobResult, error) {
	resp, err := client.get(fmt.Sprintf("/v3/job/show/%s", url.QueryEscape(jobId)), nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, client.buildError(resp, -1, "Show job failed", nil)
	}
	js, err := client.checkedJson(resp, showJobSchema)
	if err != nil {
		return nil, err
	}
	typeStr := js["type"].(string)
	hiveResultSchema, _ := js["hive_result_schema"].([]interface{})
	return &ShowJobResult{
		Id:       js["job_id"].(string),
		Type:     typeStr,
		Database: js["database"].(string),
		UserName: js["user_name"].(string),
		Status:   js["status"].(string),
		Query:    js["query"].(string),
		Debug: ShowJobResultDebugElement{
			CmdOut: js["debug"].(map[string]interface{})["cmdout"].(string),
			StdErr: js["debug"].(map[string]interface{})["stderr"].(string),
		},
		Url:              js["url"].(string),
		CreatedAt:        js["created_at"].(time.Time),
		UpdatedAt:        js["updated_at"].(time.Time),
		StartAt:          js["start_at"].(time.Time),
		EndAt:            js["end_at"].(time.Time),
		CpuTime:          js["cpu_time"].(float64),
		ResultSize:       js["result_size"].(int),
		NumRecords:       js["num_records"].(int),
		ResultUrl:        js["result"].(string),
		Priority:         js["priority"].(int),
		RetryLimit:       js["retry_limit"].(int),
		HiveResultSchema: hiveResultSchema,
	}, nil
}

func (client *TDClient) JobStatus(jobId string) (string, error) {
	resp, err := client.get(fmt.Sprintf("/v3/job/status/%s", url.QueryEscape(jobId)), nil)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Get job status failed", nil)
	}
	js, err := client.checkedJson(resp, jobStatusSchema)
	if err != nil {
		return "", err
	}
	return js["status"].(string), nil
}

func (client *TDClient) JobResult(jobId string, format string, reader func(io.Reader) error) error {
	resp, err := client.get(fmt.Sprintf("/v3/job/result/%s", url.QueryEscape(jobId)), url.Values{"format": {format}})
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return client.buildError(resp, -1, "Get job result failed", nil)
	}
	return reader(resp.Body)
}

func (client *TDClient) JobResultEach(jobId string, reader func(interface{}) error) error {
	return client.JobResult(jobId, "msgpack", func(r io.Reader) error {
		dec := client.getMessagePackDecoder(r)
		for {
			v := (interface{})(nil)
			err := dec.Decode(&v)
			if err != nil {
				if err == io.EOF {
					break
				}
				return &APIError{
					Type:    GenericError,
					Message: "Invalid MessagePack stream",
					Cause:   err,
				}
			}
			err = reader(v)
			if err != nil {
				return &APIError{
					Type:    GenericError,
					Message: "Reader returned error status",
					Cause:   err,
				}
			}
		}
		return nil
	})
}

func (client *TDClient) KillJob(jobId string) error {
	resp, err := client.post(fmt.Sprintf("/v3/job/kill/%s", url.QueryEscape(jobId)), nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return client.buildError(resp, -1, "Kill job failed", nil)
	}
	return nil
}

func (client *TDClient) SubmitQuery(db string, q Query) (string, error) {
	params := url.Values{}
	params.Set("query", q.Query)
	if q.ResultUrl != "" {
		params.Set("result", q.ResultUrl)
	}
	if q.Priority >= 0 {
		params.Set("priority", strconv.Itoa(q.Priority))
	}
	if q.RetryLimit >= 0 {
		params.Set("retry_limit", strconv.Itoa(q.RetryLimit))
	}
	resp, err := client.post(fmt.Sprintf("/v3/job/issue/%s/%s", url.QueryEscape(q.Type), url.QueryEscape(db)), params)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Query failed", nil)
	}
	js, err := client.checkedJson(resp, submitJobSchema)
	if err != nil {
		return "", err
	}
	return js["job_id"].(string), nil
}

func (client *TDClient) SubmitExportJob(db string, table string, storageType string, options map[string]string) (string, error) {
	params := dictToValues(options)
	params.Set("storage_type", storageType)
	resp, err := client.post(fmt.Sprintf("/v3/export/run/%s/%s", url.QueryEscape(db), url.QueryEscape(table)), params)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Export failed", nil)
	}
	js, err := client.checkedJson(resp, submitJobSchema)
	if err != nil {
		return "", err
	}
	return js["job_id"].(string), nil
}

func (client *TDClient) SubmitPartialDeleteJob(db string, table string, to time.Time, from time.Time, options map[string]string) (string, error) {
	params := dictToValues(options)
	if !to.IsZero() {
		params.Set("to", to.UTC().Format(TDAPIDateTime))
	}
	if !from.IsZero() {
		params.Set("from", from.UTC().Format(TDAPIDateTime))
	}
	resp, err := client.post(fmt.Sprintf("/v3/table/partialdelete/%s/%s", url.QueryEscape(db), url.QueryEscape(table)), params)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Partial delete failed", nil)
	}
	js, err := client.checkedJson(resp, submitPartialDeleteJobSchema)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(js["job_id"].(int)), nil
}
<commit_msg>Add options to ListJobs func.<commit_after>//
// Treasure Data API client for Go
//
// Copyright (C) 2014 Treasure Data, 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 td_client

import (
	"fmt"
	"io"
	"net/url"
	"strconv"
	"time"
)

type ListJobsResultElement struct {
	Id         string
	Type       string
	Database   string
	Status     string
	Query      string
	Duration   int
	CreatedAt  time.Time
	UpdatedAt  time.Time
	StartAt    time.Time
	EndAt      time.Time
	CpuTime    float64
	ResultSize int
	NumRecords int
	ResultUrl  string
	Priority   int
	RetryLimit int
}

type ListJobsResultElements []ListJobsResultElement

type ListJobsResult struct {
	ListJobsResultElements ListJobsResultElements
	Count                  int
	From                   float64
	To                     float64
}

var listJobsSchema = map[string]interface{}{
	"jobs": []map[string]interface{}{
		{
			"job_id":                      "",
			"type":                        Optional{"", "?"},
			"database":                    "",
			"status":                      "",
			"query":                       "",
			"start_at":                    Optional{time.Time{}, time.Time{}},
			"end_at":                      Optional{time.Time{}, time.Time{}},
			"created_at":                  time.Time{},
			"updated_at":                  time.Time{},
			"duration":                    Optional{0., 0.},
			"cpu_time":                    Optional{0., 0.},
			"result_size":                 Optional{0, 0},
			"num_records":                 Optional{0, 0},
			"user_name":                   "",
			"result":                      "",
			"url":                         "",
			"hive_result_schema":          Optional{"", "?"},
			"organization":                Optional{"", "?"},
			"priority":                    0,
			"retry_limit":                 0,
			"result_export_target_job_id": Optional{0., 0.},
			"linked_result_export_job_id": Optional{0., 0.},
		},
	},
	"count": Optional{0, 0},
	"to":    Optional{0., 0.},
	"from":  Optional{0., 0.},
}

var jobStatusSchema = map[string]interface{}{
	"status":      "",
	"job_id":      "",
	"start_at":    Optional{time.Time{}, time.Time{}},
	"created_at":  time.Time{},
	"updated_at":  time.Time{},
	"end_at":      Optional{time.Time{}, time.Time{}},
	"duration":    Optional{0., 0.},
	"cpu_time":    Optional{0., 0.},
	"result_size": Optional{0, 0},
	"num_records": Optional{0, 0},
}

type ShowJobResultDebugElement struct {
	CmdOut string
	StdErr string
}

// ShowJobResult stores the result of `ShowJobResult` API call.
type ShowJobResult struct {
	Id               string
	Type             string
	Database         string
	UserName         string
	Status           string
	Query            string
	Debug            ShowJobResultDebugElement
	Url              string
	Duration         int
	CreatedAt        time.Time
	UpdatedAt        time.Time
	StartAt          time.Time
	EndAt            time.Time
	CpuTime          float64
	ResultSize       int
	NumRecords       int
	ResultUrl        string
	Priority         int
	RetryLimit       int
	HiveResultSchema []interface{}
}

var showJobSchema = map[string]interface{}{
	"job_id":       "",
	"type":         Optional{"", "?"},
	"organization": Optional{"", ""},
	"user_name":    "",
	"database":     "",
	"status":       "",
	"query":        "",
	"debug": map[string]interface{}{
		"cmdout": Optional{"", ""},
		"stderr": Optional{"", ""},
	},
	"url":                         "",
	"duration":                    Optional{0, 0},
	"created_at":                  time.Time{},
	"updated_at":                  time.Time{},
	"start_at":                    Optional{time.Time{}, time.Time{}},
	"end_at":                      Optional{time.Time{}, time.Time{}},
	"cpu_time":                    Optional{0., 0.},
	"result_size":                 Optional{0, 0},
	"num_records":                 Optional{0, 0},
	"result":                      "",
	"priority":                    0,
	"retry_limit":                 0,
	"hive_result_schema":          Optional{EmbeddedJSON([]interface{}{}), nil},
	"result_export_target_job_id": Optional{0., 0.},
	"linked_result_export_job_id": Optional{0., 0.},
}

type Query struct {
	Type       string
	Query      string
	ResultUrl  string
	Priority   int
	RetryLimit int
}

var submitJobSchema = map[string]interface{}{
	"job":      "",
	"job_id":   "",
	"database": "",
}

var submitPartialDeleteJobSchema = map[string]interface{}{
	"job_id":   0,
	"database": "",
	"table":    "",
	"from":     0,
	"to":       0,
}

type ListJobsOptions struct {
	from   string
	to     string
	status string
}

func (options *ListJobsOptions) WithFrom(from int) *ListJobsOptions {
	options.from = strconv.Itoa(from)
	return options
}

func (options *ListJobsOptions) WithTo(to int) *ListJobsOptions {
	options.to = strconv.Itoa(to)
	return options
}

func (options *ListJobsOptions) WithStatus(status string) *ListJobsOptions {
	if status == "running" || status == "queued" || status == "success" || status == "error" {
     	options.status = status
	}
	return options
}

func (client *TDClient) ListJobsWithOptions(options *ListJobsOptions) (*ListJobsResult, error) {
	requestUri := "/v3/job/list"
	u, err := url.Parse(requestUri)
	if err != nil {
		return nil, err
	}

	queryString := u.Query()
	if options.from != "" {
		queryString.Set("from", options.from)
	}
	if options.to != "" {
		queryString.Set("to", options.to)
	}
	if options.status != "" {
		queryString.Set("status", options.status)
	}

	resp, err := client.get(requestUri, queryString)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, client.buildError(resp, -1, "List jobs failed", nil)
	}
	js, err := client.checkedJson(resp, listJobsSchema)
	if err != nil {
		return nil, err
	}
	jobs := js["jobs"].([]map[string]interface{})
	listJobsResult := ListJobsResult{}
	retval := make(ListJobsResultElements, len(jobs))
	for i, v := range jobs {
		retval[i] = ListJobsResultElement{
			Id:         v["job_id"].(string),
			Type:       v["type"].(string),
			Database:   v["database"].(string),
			Status:     v["status"].(string),
			Query:      v["query"].(string),
			StartAt:    v["start_at"].(time.Time),
			EndAt:      v["end_at"].(time.Time),
			CpuTime:    v["cpu_time"].(float64),
			ResultSize: v["result_size"].(int),
			NumRecords: v["num_records"].(int),
			ResultUrl:  v["result"].(string),
			Priority:   v["priority"].(int),
			RetryLimit: v["retry_limit"].(int),
		}
	}
	listJobsResult.ListJobsResultElements = retval
	listJobsResult.Count = js["count"].(int)
	listJobsResult.From = js["from"].(float64)
	listJobsResult.To = js["to"].(float64)
	return &listJobsResult, nil
}

func (client *TDClient) ListJobs() (*ListJobsResult, error) {
	return client.ListJobsWithOptions(&ListJobsOptions{})
}

func (client *TDClient) ShowJob(jobId string) (*ShowJobResult, error) {
	resp, err := client.get(fmt.Sprintf("/v3/job/show/%s", url.QueryEscape(jobId)), nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, client.buildError(resp, -1, "Show job failed", nil)
	}
	js, err := client.checkedJson(resp, showJobSchema)
	if err != nil {
		return nil, err
	}
	typeStr := js["type"].(string)
	hiveResultSchema, _ := js["hive_result_schema"].([]interface{})
	return &ShowJobResult{
		Id:       js["job_id"].(string),
		Type:     typeStr,
		Database: js["database"].(string),
		UserName: js["user_name"].(string),
		Status:   js["status"].(string),
		Query:    js["query"].(string),
		Debug: ShowJobResultDebugElement{
			CmdOut: js["debug"].(map[string]interface{})["cmdout"].(string),
			StdErr: js["debug"].(map[string]interface{})["stderr"].(string),
		},
		Url:              js["url"].(string),
		CreatedAt:        js["created_at"].(time.Time),
		UpdatedAt:        js["updated_at"].(time.Time),
		StartAt:          js["start_at"].(time.Time),
		EndAt:            js["end_at"].(time.Time),
		CpuTime:          js["cpu_time"].(float64),
		ResultSize:       js["result_size"].(int),
		NumRecords:       js["num_records"].(int),
		ResultUrl:        js["result"].(string),
		Priority:         js["priority"].(int),
		RetryLimit:       js["retry_limit"].(int),
		HiveResultSchema: hiveResultSchema,
	}, nil
}

func (client *TDClient) JobStatus(jobId string) (string, error) {
	resp, err := client.get(fmt.Sprintf("/v3/job/status/%s", url.QueryEscape(jobId)), nil)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Get job status failed", nil)
	}
	js, err := client.checkedJson(resp, jobStatusSchema)
	if err != nil {
		return "", err
	}
	return js["status"].(string), nil
}

func (client *TDClient) JobResult(jobId string, format string, reader func(io.Reader) error) error {
	resp, err := client.get(fmt.Sprintf("/v3/job/result/%s", url.QueryEscape(jobId)), url.Values{"format": {format}})
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return client.buildError(resp, -1, "Get job result failed", nil)
	}
	return reader(resp.Body)
}

func (client *TDClient) JobResultEach(jobId string, reader func(interface{}) error) error {
	return client.JobResult(jobId, "msgpack", func(r io.Reader) error {
		dec := client.getMessagePackDecoder(r)
		for {
			v := (interface{})(nil)
			err := dec.Decode(&v)
			if err != nil {
				if err == io.EOF {
					break
				}
				return &APIError{
					Type:    GenericError,
					Message: "Invalid MessagePack stream",
					Cause:   err,
				}
			}
			err = reader(v)
			if err != nil {
				return &APIError{
					Type:    GenericError,
					Message: "Reader returned error status",
					Cause:   err,
				}
			}
		}
		return nil
	})
}

func (client *TDClient) KillJob(jobId string) error {
	resp, err := client.post(fmt.Sprintf("/v3/job/kill/%s", url.QueryEscape(jobId)), nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return client.buildError(resp, -1, "Kill job failed", nil)
	}
	return nil
}

func (client *TDClient) SubmitQuery(db string, q Query) (string, error) {
	params := url.Values{}
	params.Set("query", q.Query)
	if q.ResultUrl != "" {
		params.Set("result", q.ResultUrl)
	}
	if q.Priority >= 0 {
		params.Set("priority", strconv.Itoa(q.Priority))
	}
	if q.RetryLimit >= 0 {
		params.Set("retry_limit", strconv.Itoa(q.RetryLimit))
	}
	resp, err := client.post(fmt.Sprintf("/v3/job/issue/%s/%s", url.QueryEscape(q.Type), url.QueryEscape(db)), params)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Query failed", nil)
	}
	js, err := client.checkedJson(resp, submitJobSchema)
	if err != nil {
		return "", err
	}
	return js["job_id"].(string), nil
}

func (client *TDClient) SubmitExportJob(db string, table string, storageType string, options map[string]string) (string, error) {
	params := dictToValues(options)
	params.Set("storage_type", storageType)
	resp, err := client.post(fmt.Sprintf("/v3/export/run/%s/%s", url.QueryEscape(db), url.QueryEscape(table)), params)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Export failed", nil)
	}
	js, err := client.checkedJson(resp, submitJobSchema)
	if err != nil {
		return "", err
	}
	return js["job_id"].(string), nil
}

func (client *TDClient) SubmitPartialDeleteJob(db string, table string, to time.Time, from time.Time, options map[string]string) (string, error) {
	params := dictToValues(options)
	if !to.IsZero() {
		params.Set("to", to.UTC().Format(TDAPIDateTime))
	}
	if !from.IsZero() {
		params.Set("from", from.UTC().Format(TDAPIDateTime))
	}
	resp, err := client.post(fmt.Sprintf("/v3/table/partialdelete/%s/%s", url.QueryEscape(db), url.QueryEscape(table)), params)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", client.buildError(resp, -1, "Partial delete failed", nil)
	}
	js, err := client.checkedJson(resp, submitPartialDeleteJobSchema)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(js["job_id"].(int)), nil
}
<|endoftext|>