<commit_before>package main

import (
	"fmt"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"html/template"
	"net/http"
	"os"
	"time"
)

var upgrader websocket.Upgrader

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	t, err := template.ParseFiles("templates/index.go.html")
	ensureNil(err)
	err = t.Execute(w, nil)
	ensureNil(err)
}

type SimilarPostRequest struct {
	PostUri string
}

type TumblrCredentials struct {
	Key    string
	Secret string
}

func getCredentials() *TumblrCredentials {
	return &TumblrCredentials{
		Key:    os.Getenv("ALIKER_KEY"),
		Secret: os.Getenv("ALIKER_SECRET"),
	}
}

type beginNotification struct {
	BaseHostname string
	PostID       int64
	MsgType      string
}

func sendBeginNotification(c *websocket.Conn, bh string, pid int64) error {
	msg := &beginNotification{
		BaseHostname: bh,
		PostID:       pid,
		MsgType:      "begin-notification",
	}
	return c.WriteJSON(msg)
}

func sendErrorNotification(c *websocket.Conn, err error) error {
	return c.WriteJSON(&struct{
		MsgType string
		Message string
	}{
		"error",
		err.Error(),
	})
}

func SimilarHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	ensureNil(err)

	spr := &SimilarPostRequest{}
	err = conn.ReadJSON(spr)
	ensureNil(err)

	bh, _, err := extractPostId(spr.PostUri)
	if err != nil {
		msg := fmt.Sprintf("invalid post uri: %s", spr.PostUri)
		fmt.Println(msg)
		conn.WriteJSON(map[string]string{
			"error": msg,
		})
		conn.Close()
	}

	for {
		err = conn.WriteJSON(map[string]string{
			"bh": bh,
		})
		if err != nil {
			break
		}
		<-time.After(5 * time.Second)
	}
}

func ensureNil(x interface{}) {
	if x != nil {
		panic(x)
	}
}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/", HomeHandler)
	router.HandleFunc("/post", SimilarHandler)

	n := negroni.New()
	n.UseHandler(router)
	n.Run(fmt.Sprintf(":%s", os.Getenv("PORT")))
}
<commit_msg>Make the post ID always 64 bits (they're very large).  Send errors using new method. Make sure connection is always closed at end of this function<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"html/template"
	"net/http"
	"os"
	"time"
)

var upgrader websocket.Upgrader

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	t, err := template.ParseFiles("templates/index.go.html")
	ensureNil(err)
	err = t.Execute(w, nil)
	ensureNil(err)
}

type SimilarPostRequest struct {
	PostUri string
}

type TumblrCredentials struct {
	Key    string
	Secret string
}

func getCredentials() *TumblrCredentials {
	return &TumblrCredentials{
		Key:    os.Getenv("ALIKER_KEY"),
		Secret: os.Getenv("ALIKER_SECRET"),
	}
}

type beginNotification struct {
	BaseHostname string
	PostID       int64
	MsgType      string
}

func sendBeginNotification(c *websocket.Conn, bh string, pid int64) error {
	msg := &beginNotification{
		BaseHostname: bh,
		PostID:       pid,
		MsgType:      "begin-notification",
	}
	return c.WriteJSON(msg)
}

func sendErrorNotification(c *websocket.Conn, err error) error {
	return c.WriteJSON(&struct{
		MsgType string
		Message string
	}{
		"error",
		err.Error(),
	})
}

func SimilarHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	ensureNil(err)
	defer conn.Close()

	// Figure out what post they want and extract the details we need
	spr := &SimilarPostRequest{}
	err = conn.ReadJSON(spr)
	ensureNil(err)
	bh, pid, err := extractPostId(spr.PostUri)
	if err != nil {
		msg := fmt.Errorf("invalid post uri: %s", spr.PostUri)
		sendErrorNotification(conn, msg)
		return
	}

	for {
		err = conn.WriteJSON(map[string]string{
			"bh": bh,
		})
		if err != nil {
			break
		}
		<-time.After(5 * time.Second)
	}
}

func ensureNil(x interface{}) {
	if x != nil {
		panic(x)
	}
}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/", HomeHandler)
	router.HandleFunc("/post", SimilarHandler)

	n := negroni.New()
	n.UseHandler(router)
	n.Run(fmt.Sprintf(":%s", os.Getenv("PORT")))
}
<|endoftext|>


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

package hdiutil

import (
	"fmt"
	"os/exec"
	"regexp"
	"strconv"
)

// attachFlag implements a hdiutil attach command flag interface.
type attachFlag interface {
	attachFlag() []string
}

type rwType int

const (
	// Readonly force the resulting device to be read-only.
	readonly rwType = 1 << iota
	// Readwrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	readwrite
)

func (rw rwType) attachFlag() string {
	switch rw {
	case readonly:
		return "-readonly"
	case readwrite:
		return "-readwrite"
	default:
		return ""
	}
}

type kernel bool

func (k kernel) attachFlag() []string { return boolNoFlag(bool(k), "kernel") }

type notRemovable bool

func (n notRemovable) attachFlag() []string { return boolFlag(bool(n), "notremovable") }

type mount string

func (m mount) attachFlag() []string { return stringFlag(string(m), "mount") }

type noMount bool

func (n noMount) attachFlag() []string { return boolFlag(bool(n), "nomount") }

// AttachMountRoot mount volumes on subdirectories of path instead of under /Volumes. path must exist.
//
// Full mount point paths must be less than MNAMELEN characters (increased from 90 to 1024 in Mac OS X 10.6).
type AttachMountRoot string

func (a AttachMountRoot) attachFlag() []string { return stringFlag(string(a), "mountroot") }

// AttachMountRandom like -mountroot, but mount point directory names are randomized with mkdtemp(3).
type AttachMountRandom string

func (a AttachMountRandom) attachFlag() []string { return stringFlag(string(a), "mountrandom") }

// AttachMountPoint assuming only one volume, mount it at path instead of in /Volumes.
//
// See fstab(5) for ways a system administrator can make particular volumes automatically mount in particular filesystem locations by editing the file /etc/fstab.
type AttachMountPoint string

func (a AttachMountPoint) attachFlag() []string { return stringFlag(string(a), "mountpoint") }

type noBrowse bool

func (n noBrowse) attachFlag() []string { return boolFlag(bool(n), "nobrowse") }

type owners string

const (
	ownersOn  owners = "on"
	ownersOff owners = "off"
)

func (o owners) attachFlag() []string { return stringFlag(string(o), "owners") }

// AttachDrivekey specify a key/value pair to be set on the device in the IOKit registry.
type AttachDrivekey [2]string

func (a AttachDrivekey) attachFlag() []string {
	return stringFlag(a[0]+"="+a[1], "drivekey")
}

// AttachSection attach a subsection of a disk image.
// subspec is any of <offset>, <first-last>, or <start,count> in 0-based sectors.
// Ranges are inclusive.
type AttachSection [2]int

func (a AttachSection) attachFlag() []string {
	var arg string
	for v := range a {
		arg = arg + strconv.Itoa(v)
	}
	return stringFlag(arg, "section")
}

type verify bool

func (v verify) attachFlag() []string { return boolNoFlag(bool(v), "verify") }

type ignoreBadChecksums bool

func (i ignoreBadChecksums) attachFlag() []string { return boolNoFlag(bool(i), "ignoreBadChecksums") }

type idme bool

func (i idme) attachFlag() []string { return boolNoFlag(bool(i), "idme") }

type idmeReveal bool

func (i idmeReveal) attachFlag() []string { return boolNoFlag(bool(i), "idmereveal") }

type idmeTrash bool

func (i idmeTrash) attachFlag() []string { return boolNoFlag(bool(i), "idmetrash") }

type autoOpen bool

func (a autoOpen) attachFlag() []string { return boolNoFlag(bool(a), "autoopen") }

type autoOpenRO bool

func (a autoOpenRO) attachFlag() []string { return boolNoFlag(bool(a), "autoopenro") }

type autoOpenRW bool

func (a autoOpenRW) attachFlag() []string { return boolNoFlag(bool(a), "autoopenrw") }

type autoFsck bool

func (a autoFsck) attachFlag() []string { return boolNoFlag(bool(a), "autofsck") }

const (
	// AttachReadonly force the resulting device to be read-only.
	AttachReadonly rwType = readonly

	// AttachReadWrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	//
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	AttachReadWrite rwType = readwrite

	// AttachKernel attempt to attach this image without a helper process; fail if unsupported.
	//
	// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel. Encryption and HTTP are supported by the kernel driver.
	AttachKernel kernel = true

	// AttachNoKernel attach with a helper process.  This is (again) the default as of Mac OS X 10.5.
	AttachNoKernel kernel = false

	// AttachNotRemovable prevent this image from being detached. Only root can use this option.
	//
	// A reboot is necessary to cleanly detach an image attached with -notremovable.
	AttachNotRemovable notRemovable = true

	// AttachMountRequired indicate to -mount required.
	AttachMountRequired mount = "required"

	// AttachMountOptional indicate to -mount optional.
	AttachMountOptional mount = "optional"

	// AttachMountSuppressed indicate to -mount suppressed.
	AttachMountSuppressed mount = "suppressed"

	// AttachNoMount identical to -mount suppressed
	AttachNoMount noMount = true

	// AttachNoBrowse render any volumes invisible in applications such as the macOS Finder.
	AttachNoBrowse noBrowse = true

	// AttachOwnersOn owners on any filesystems be honored.
	AttachOwnersOn owners = ownersOn

	// AttachOwnersOff owners on any filesystems be not honored.
	AttachOwnersOff owners = ownersOff

	// AttachVerify do verify the image.
	AttachVerify verify = true

	// AttachNoVerify do not verify the image.
	AttachNoVerify verify = false

	// AttachIgnoreBadChecksums bad checksums should be ignored.
	AttachIgnoreBadChecksums ignoreBadChecksums = true

	// AttachNoIgnoreBadChecksums bad checksums should be not ignored.
	AttachNoIgnoreBadChecksums ignoreBadChecksums = false

	// AttachIdme do perform IDME actions on IDME images.
	AttachIdme idme = true

	// AttachNoIdme do not perform IDME actions on IDME images.
	AttachNoIdme idme = false

	// AttachIdmeReveal do reveal (in the Finder) the results of IDME processing.
	AttachIdmeReveal idmeReveal = true

	// AttachNoIdmeReveal do not reveal (in the Finder) the results of IDME processing.
	AttachNoIdmeReveal idmeReveal = false

	// AttachIdmeTrash do put IDME images in the trash after processing.
	AttachIdmeTrash idmeTrash = true
	// AttachNoIdmeTrash do not put IDME images in the trash after processing.
	AttachNoIdmeTrash idmeTrash = false

	// AttachAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachAutoOpen autoOpen = true

	// AttachNoAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachNoAutoOpen autoOpen = false

	// AttachAutoOpenRO do auto-open read-only volumes.
	AttachAutoOpenRO autoOpenRO = true

	// AttachNoAutoOpenRO do not auto-open read-only volumes.
	AttachNoAutoOpenRO autoOpenRO = false

	// AttachAutoOpenRW do auto-open read/write volumes.
	AttachAutoOpenRW autoOpenRW = true

	// AttachNoAutoOpenRW do not auto-open read/write volumes.
	AttachNoAutoOpenRW autoOpenRW = false

	// AttachAutoFsck do force automatic file system checking before mounting a disk image.
	AttachAutoFsck autoFsck = true

	// AttachNoAutoFsck do not force automatic file system checking before mounting a disk image.
	AttachNoAutoFsck autoFsck = false
)

var attachRe = regexp.MustCompile(`/dev/disk[\d]+`)

// Attach attach the image file. The returns device node path and error.
func Attach(image string, flags ...attachFlag) (string, error) {
	cmd := exec.Command(hdiutilPath, "attach", image)

	if len(flags) != 0 {
		for _, f := range flags {
			cmd.Args = append(cmd.Args, f.attachFlag()...)
		}
	}

	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%v: %s", err, out)
	}

	return string(attachRe.Find(out)), nil
}
<commit_msg>attach: add 'attach' prefix to non exported types<commit_after>// Copyright 2017 The go-darwin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package hdiutil

import (
	"fmt"
	"os/exec"
	"regexp"
	"strconv"
)

// attachFlag implements a hdiutil attach command flag interface.
type attachFlag interface {
	attachFlag() []string
}

type attachRWType int

const (
	// Readonly force the resulting device to be read-only.
	readonly attachRWType = 1 << iota
	// Readwrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	readwrite
)

func (rw attachRWType) attachFlag() string {
	switch rw {
	case readonly:
		return "-readonly"
	case readwrite:
		return "-readwrite"
	default:
		return ""
	}
}

type attachKernel bool

func (k attachKernel) attachFlag() []string { return boolNoFlag(bool(k), "kernel") }

type attachNotRemovable bool

func (n attachNotRemovable) attachFlag() []string { return boolFlag(bool(n), "notremovable") }

type attachMount string

func (m attachMount) attachFlag() []string { return stringFlag(string(m), "mount") }

type attachNoMount bool

func (n attachNoMount) attachFlag() []string { return boolFlag(bool(n), "nomount") }

// AttachMountRoot mount volumes on subdirectories of path instead of under /Volumes. path must exist.
//
// Full mount point paths must be less than MNAMELEN characters (increased from 90 to 1024 in Mac OS X 10.6).
type AttachMountRoot string

func (a AttachMountRoot) attachFlag() []string { return stringFlag(string(a), "mountroot") }

// AttachMountRandom like -mountroot, but mount point directory names are randomized with mkdtemp(3).
type AttachMountRandom string

func (a AttachMountRandom) attachFlag() []string { return stringFlag(string(a), "mountrandom") }

// AttachMountPoint assuming only one volume, mount it at path instead of in /Volumes.
//
// See fstab(5) for ways a system administrator can make particular volumes automatically mount in particular filesystem locations by editing the file /etc/fstab.
type AttachMountPoint string

func (a AttachMountPoint) attachFlag() []string { return stringFlag(string(a), "mountpoint") }

type attachNoBrowse bool

func (n attachNoBrowse) attachFlag() []string { return boolFlag(bool(n), "nobrowse") }

type attachOwners string

const (
	ownersOn  attachOwners = "on"
	ownersOff attachOwners = "off"
)

func (o attachOwners) attachFlag() []string { return stringFlag(string(o), "owners") }

// AttachDrivekey specify a key/value pair to be set on the device in the IOKit registry.
type AttachDrivekey [2]string

func (a AttachDrivekey) attachFlag() []string {
	return stringFlag(a[0]+"="+a[1], "drivekey")
}

// AttachSection attach a subsection of a disk image.
// subspec is any of <offset>, <first-last>, or <start,count> in 0-based sectors.
// Ranges are inclusive.
type AttachSection [2]int

func (a AttachSection) attachFlag() []string {
	var arg string
	for v := range a {
		arg = arg + strconv.Itoa(v)
	}
	return stringFlag(arg, "section")
}

type attachVerify bool

func (v attachVerify) attachFlag() []string { return boolNoFlag(bool(v), "verify") }

type attachIgnoreBadChecksums bool

func (i attachIgnoreBadChecksums) attachFlag() []string {
	return boolNoFlag(bool(i), "ignoreBadChecksums")
}

type attachIdme bool

func (i attachIdme) attachFlag() []string { return boolNoFlag(bool(i), "idme") }

type atachIdmeReveal bool

func (i atachIdmeReveal) attachFlag() []string { return boolNoFlag(bool(i), "idmereveal") }

type attachIdmeTrash bool

func (i attachIdmeTrash) attachFlag() []string { return boolNoFlag(bool(i), "idmetrash") }

type attachAutoOpen bool

func (a attachAutoOpen) attachFlag() []string { return boolNoFlag(bool(a), "autoopen") }

type attachAutoOpenRO bool

func (a attachAutoOpenRO) attachFlag() []string { return boolNoFlag(bool(a), "autoopenro") }

type attachAutoOpenRW bool

func (a attachAutoOpenRW) attachFlag() []string { return boolNoFlag(bool(a), "autoopenrw") }

type attachAutoFsck bool

func (a attachAutoFsck) attachFlag() []string { return boolNoFlag(bool(a), "autofsck") }

const (
	// AttachReadonly force the resulting device to be read-only.
	AttachReadonly attachRWType = readonly

	// AttachReadWrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	//
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	AttachReadWrite attachRWType = readwrite

	// AttachKernel attempt to attach this image without a helper process; fail if unsupported.
	//
	// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel. Encryption and HTTP are supported by the kernel driver.
	AttachKernel attachKernel = true

	// AttachNoKernel attach with a helper process.  This is (again) the default as of Mac OS X 10.5.
	AttachNoKernel attachKernel = false

	// AttachNotRemovable prevent this image from being detached. Only root can use this option.
	//
	// A reboot is necessary to cleanly detach an image attached with -notremovable.
	AttachNotRemovable attachNotRemovable = true

	// AttachMountRequired indicate to -mount required.
	AttachMountRequired attachMount = "required"

	// AttachMountOptional indicate to -mount optional.
	AttachMountOptional attachMount = "optional"

	// AttachMountSuppressed indicate to -mount suppressed.
	AttachMountSuppressed attachMount = "suppressed"

	// AttachNoMount identical to -mount suppressed
	AttachNoMount attachNoMount = true

	// AttachNoBrowse render any volumes invisible in applications such as the macOS Finder.
	AttachNoBrowse attachNoBrowse = true

	// AttachOwnersOn owners on any filesystems be honored.
	AttachOwnersOn attachOwners = ownersOn

	// AttachOwnersOff owners on any filesystems be not honored.
	AttachOwnersOff attachOwners = ownersOff

	// AttachVerify do verify the image.
	AttachVerify attachVerify = true

	// AttachNoVerify do not verify the image.
	AttachNoVerify attachVerify = false

	// AttachIgnoreBadChecksums bad checksums should be ignored.
	AttachIgnoreBadChecksums attachIgnoreBadChecksums = true

	// AttachNoIgnoreBadChecksums bad checksums should be not ignored.
	AttachNoIgnoreBadChecksums attachIgnoreBadChecksums = false

	// AttachIdme do perform IDME actions on IDME images.
	AttachIdme attachIdme = true

	// AttachNoIdme do not perform IDME actions on IDME images.
	AttachNoIdme attachIdme = false

	// AttachIdmeReveal do reveal (in the Finder) the results of IDME processing.
	AttachIdmeReveal atachIdmeReveal = true

	// AttachNoIdmeReveal do not reveal (in the Finder) the results of IDME processing.
	AttachNoIdmeReveal atachIdmeReveal = false

	// AttachIdmeTrash do put IDME images in the trash after processing.
	AttachIdmeTrash attachIdmeTrash = true
	// AttachNoIdmeTrash do not put IDME images in the trash after processing.
	AttachNoIdmeTrash attachIdmeTrash = false

	// AttachAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachAutoOpen attachAutoOpen = true

	// AttachNoAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachNoAutoOpen attachAutoOpen = false

	// AttachAutoOpenRO do auto-open read-only volumes.
	AttachAutoOpenRO attachAutoOpenRO = true

	// AttachNoAutoOpenRO do not auto-open read-only volumes.
	AttachNoAutoOpenRO attachAutoOpenRO = false

	// AttachAutoOpenRW do auto-open read/write volumes.
	AttachAutoOpenRW attachAutoOpenRW = true

	// AttachNoAutoOpenRW do not auto-open read/write volumes.
	AttachNoAutoOpenRW attachAutoOpenRW = false

	// AttachAutoFsck do force automatic file system checking before mounting a disk image.
	AttachAutoFsck attachAutoFsck = true

	// AttachNoAutoFsck do not force automatic file system checking before mounting a disk image.
	AttachNoAutoFsck attachAutoFsck = false
)

var attachRe = regexp.MustCompile(`/dev/disk[\d]+`)

// Attach attach the image file. The returns device node path and error.
func Attach(image string, flags ...attachFlag) (string, error) {
	cmd := exec.Command(hdiutilPath, "attach", image)

	if len(flags) != 0 {
		for _, f := range flags {
			cmd.Args = append(cmd.Args, f.attachFlag()...)
		}
	}

	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%v: %s", err, out)
	}

	return string(attachRe.Find(out)), nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcutil

import (
	"math/big"
	"strings"
)

// Alphabet used by BTC
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

var bigRadix = big.NewInt(58)
var bigZero = big.NewInt(0)

// Shared function to Base58 decode to a []byte
func Base58Decode(b string) []byte {
	answer := big.NewInt(0)
	j := big.NewInt(1)

	for i := len(b) - 1; i >= 0; i-- {
		tmp := strings.IndexAny(alphabet, string(b[i]))
		if tmp == -1 {
			return []byte("")
		}
		idx := big.NewInt(int64(tmp))
		tmp1 := big.NewInt(0)
		tmp1.Mul(j, idx)

		answer.Add(answer, tmp1)
		j.Mul(j, bigRadix)
	}

	tmpval := answer.Bytes()

	var numZeros int
	for numZeros = 0; numZeros < len(b); numZeros++ {
		if b[numZeros] != alphabet[0] {
			break
		}
	}
	flen := numZeros + len(tmpval)
	val := make([]byte, flen, flen)
	copy(val[numZeros:], tmpval)

	return val
}

// Shared function to Base58 encode a []byte
func Base58Encode(b []byte) string {
	x := new(big.Int)
	x.SetBytes(b)

	answer := make([]byte, 0)
	for x.Cmp(bigZero) > 0 {
		mod := new(big.Int)
		x.DivMod(x, bigRadix, mod)
		answer = append(answer, alphabet[mod.Int64()])
	}

	// leading zero bytes
	for _, i := range b {
		if i != 0 {
			break
		}
		answer = append(answer, alphabet[0])
	}

	// reverse
	alen := len(answer)
	for i := 0; i < alen/2; i++ {
		answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
	}

	return string(answer)
}
<commit_msg>Update comments to use proper form.<commit_after>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcutil

import (
	"math/big"
	"strings"
)

// alphabet is the modified base58 alphabet used by Bitcoin.
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

var bigRadix = big.NewInt(58)
var bigZero = big.NewInt(0)

// Base58Decode decodes a modified base58 string to a byte slice.
func Base58Decode(b string) []byte {
	answer := big.NewInt(0)
	j := big.NewInt(1)

	for i := len(b) - 1; i >= 0; i-- {
		tmp := strings.IndexAny(alphabet, string(b[i]))
		if tmp == -1 {
			return []byte("")
		}
		idx := big.NewInt(int64(tmp))
		tmp1 := big.NewInt(0)
		tmp1.Mul(j, idx)

		answer.Add(answer, tmp1)
		j.Mul(j, bigRadix)
	}

	tmpval := answer.Bytes()

	var numZeros int
	for numZeros = 0; numZeros < len(b); numZeros++ {
		if b[numZeros] != alphabet[0] {
			break
		}
	}
	flen := numZeros + len(tmpval)
	val := make([]byte, flen, flen)
	copy(val[numZeros:], tmpval)

	return val
}

// Base58Encode encodes a byte slice to a modified base58 string.
func Base58Encode(b []byte) string {
	x := new(big.Int)
	x.SetBytes(b)

	answer := make([]byte, 0)
	for x.Cmp(bigZero) > 0 {
		mod := new(big.Int)
		x.DivMod(x, bigRadix, mod)
		answer = append(answer, alphabet[mod.Int64()])
	}

	// leading zero bytes
	for _, i := range b {
		if i != 0 {
			break
		}
		answer = append(answer, alphabet[0])
	}

	// reverse
	alen := len(answer)
	for i := 0; i < alen/2; i++ {
		answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
	}

	return string(answer)
}
<|endoftext|>


<commit_before>package prom2cloudwatch

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
)

const (
	batchSize = 100

	cwHighResLabel = "__cw_high_res"
	cwUnitLabel    = "__cw_unit"
)

// Config defines configuration options for Bridge
type Config struct {
	// Required. The Prometheus namespace/prefix to scrape. Each Bridge only supports 1 prefix.
	// If multiple prefixes are required, multiple Bridges must be used.
	PrometheusNamespace string

	// Required. The CloudWatch namespace under which metrics should be published
	CloudWatchNamespace string

	// The frequency with which metrics should be published to Cloudwatch. Default: 15s
	Interval time.Duration

	// Timeout for sending metrics to Cloudwatch. Default: 1s
	Timeout time.Duration

	// Custom HTTP Client to use with the Cloudwatch API. Default: http.Client{}
	// If Config.Timeout is supplied, it will override any timeout defined on
	// the supplied http.Client
	Client *http.Client

	// Logger that messages are written to. Default: nil
	Logger Logger

	// The Gatherer to use for metrics. Default: prometheus.DefaultGatherer
	Gatherer prometheus.Gatherer

	// Only publish whitelisted metrics
	WhitelistOnly bool

	// List of metrics that should be published, causing all others to be ignored.
	// Config.WhitelistOnly must be set to true for this to take effect.
	Whitelist []string

	// List of metrics that should never be published. This setting overrides entries in Config.Whitelist
	Blacklist []string
}

// Bridge pushes metrics to AWS Cloudwatch
type Bridge struct {
	interval time.Duration
	timeout  time.Duration

	promNamespace string
	cwNamespace   string

	useWhitelist bool
	whitelist    map[string]struct{}
	blacklist    map[string]struct{}

	logger Logger
	g      prometheus.Gatherer
	cw     *cloudwatch.CloudWatch
}

// NewBridge initializes and returns a pointer to a Bridge using the
// supplied configuration, or an error if there is a problem with
// the configuration
func NewBridge(c *Config) (*Bridge, error) {
	b := &Bridge{}

	if c.PrometheusNamespace == "" {
		return nil, errors.New("PrometheusNamespace must not be empty")
	} else {
		b.promNamespace = c.PrometheusNamespace
	}

	if c.CloudWatchNamespace == "" {
		return nil, errors.New("CloudWatchNamespace must not be empty")
	} else {
		b.cwNamespace = c.CloudWatchNamespace
	}

	if c.Interval > 0 {
		b.interval = c.Interval
	} else {
		b.interval = 15 * time.Second
	}

	var client *http.Client
	if c.Client != nil {
		client = c.Client
	} else {
		client = &http.Client{}
	}

	if c.Timeout > 0 {
		client.Timeout = c.Timeout
	} else {
		client.Timeout = time.Second
	}

	if c.Logger != nil {
		b.logger = c.Logger
	}

	if c.Gatherer != nil {
		b.g = c.Gatherer
	} else {
		b.g = prometheus.DefaultGatherer
	}

	b.useWhitelist = c.WhitelistOnly
	b.whitelist = make(map[string]struct{}, len(c.Whitelist))
	for _, v := range c.Whitelist {
		b.whitelist[v] = struct{}{}
	}

	b.blacklist = make(map[string]struct{}, len(c.Blacklist))
	for _, v := range c.Blacklist {
		b.blacklist[v] = struct{}{}
	}

	// Use default credential provider, which I belive supports the standard
	// AWS_* environment variables, and the shared credential file under ~/.aws
	sess, err := session.NewSession(&aws.Config{HTTPClient: client})
	if err != nil {
		return nil, err
	}

	b.cw = cloudwatch.New(sess)
	return b, nil
}

// From https://github.com/prometheus/client_golang/blob/master/prometheus/graphite/bridge.go
// Logger is the minimal interface Bridge needs for logging. Note that
// log.Logger from the standard library implements this interface, and it is
// easy to implement by custom loggers, if they don't do so already anyway.
type Logger interface {
	Println(v ...interface{})
}

// Run starts a loop that will push metrics to Cloudwatch at the
// configured interval. Run accepts a context.Context to support
// cancellation.
func (b *Bridge) Run(ctx context.Context) {
	ticker := time.NewTicker(b.interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := b.Publish(); err != nil && b.logger != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
		case <-ctx.Done():
			if b.logger != nil {
				b.logger.Println("stopping Cloudwatch publisher")
			}
			return
		}
	}
}

// Publish publishes the Prometheus metrics to Cloudwatch
func (b *Bridge) Publish() error {
	mfs, err := b.g.Gather()
	if err != nil {
		return err
	}

	return b.publishMetrics(mfs)
}

// NOTE: The CloudWatch API has the following limitations:
//		- Max 40kb request size
//		- Single namespace per request
//		- Max 10 dimensions per metric
func (b *Bridge) publishMetrics(mfs []*dto.MetricFamily) error {
	vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
		Timestamp: model.Now(),
	}, mfs...)
	if err != nil {
		return err
	}

	data := make([]*cloudwatch.MetricDatum, 0, batchSize)
	for _, s := range vec {
		name := getName(s.Metric)
		if b.isWhitelisted(name) {
			data = appendDatum(data, name, s)
			//} else {
			//fmt.Printf("discarding metric: name:[%q] [%+v]\n", name, s)
		}

		// punt on the 40KB size limitation. Will see how this works out in practice
		if len(data) == batchSize {
			if err := b.flush(data); err != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
			data = make([]*cloudwatch.MetricDatum, 0, batchSize)
		}
	}

	return b.flush(data)
}

func (b *Bridge) flush(data []*cloudwatch.MetricDatum) error {
	if len(data) > 0 {
		// TODO: implement me
		fmt.Printf("Publishing data: %+v\n", data)
		//in := &cloudwatch.PutMetricDataInput{
		//	MetricData: data,
		//	Namespace:  b.cwNamespace,
		//}
		//_, err := b.cw.PutMetricData(in)
		//return err
	}
	return nil
}

func (b *Bridge) isWhitelisted(name string) bool {
	if !strings.HasPrefix(name, b.promNamespace) {
		return false
	} else if _, ok := b.blacklist[name]; ok {
		return false
	}

	if b.useWhitelist {
		if name == "" {
			return false
		}
		_, ok := b.whitelist[name]
		return ok
	}
	return true
}

func appendDatum(data []*cloudwatch.MetricDatum, name string, s *model.Sample) []*cloudwatch.MetricDatum {
	d := &cloudwatch.MetricDatum{}
	d.SetMetricName(name).
		SetValue(float64(s.Value)).
		SetTimestamp(s.Timestamp.Time()).
		SetDimensions(getDimensions(s.Metric)).
		SetStorageResolution(getResolution(s.Metric)).
		SetUnit(getUnit(s.Metric))
	return append(data, d)
}

func getName(m model.Metric) string {
	if n, ok := m[model.MetricNameLabel]; ok {
		return string(n)
	}
	return ""
}

// getDimensions returns up to 10 dimensions for the provided metric - one for each label (except the __name__ label)
//
// If a metric has more than 10 labels, it attempts to behave determinically by sorting the labels lexicographically,
// and returning the first 10 labels as dimensions
func getDimensions(m model.Metric) []*cloudwatch.Dimension {
	if len(m) == 0 {
		return make([]*cloudwatch.Dimension, 0)
	} else if _, ok := m[model.MetricNameLabel]; len(m) == 1 && ok {
		return make([]*cloudwatch.Dimension, 0)
	}
	names := make([]string, 0, len(m))
	for k := range m {
		if !(k == model.MetricNameLabel || k == cwHighResLabel || k == cwUnitLabel) {
			names = append(names, string(k))
		}
	}

	sort.Strings(names)
	if len(names) > 10 {
		names = names[:10]
	}
	dims := make([]*cloudwatch.Dimension, 0, len(names))
	for _, k := range names {
		dims = append(dims, new(cloudwatch.Dimension).SetName(k).SetValue(string(m[model.LabelName(k)])))
	}
	return dims
}

// Returns 1 if the metric contains a __cw_high_res label, otherwise it return 60
func getResolution(m model.Metric) int64 {
	if _, ok := m[cwHighResLabel]; ok {
		return 1
	}
	return 60
}

// TODO: can we infer the proper unit based on the metric name?
func getUnit(m model.Metric) string {
	if u, ok := m[cwUnitLabel]; ok {
		return string(u)
	}
	return ""
}
<commit_msg>fix typos<commit_after>package prom2cloudwatch

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
)

const (
	batchSize = 100

	cwHighResLabel = "__cw_high_res"
	cwUnitLabel    = "__cw_unit"
)

// Config defines configuration options for Bridge
type Config struct {
	// Required. The Prometheus namespace/prefix to scrape. Each Bridge only supports 1 prefix.
	// If multiple prefixes are required, multiple Bridges must be used.
	PrometheusNamespace string

	// Required. The CloudWatch namespace under which metrics should be published
	CloudWatchNamespace string

	// The frequency with which metrics should be published to Cloudwatch. Default: 15s
	Interval time.Duration

	// Timeout for sending metrics to Cloudwatch. Default: 1s
	Timeout time.Duration

	// Custom HTTP Client to use with the Cloudwatch API. Default: http.Client{}
	// If Config.Timeout is supplied, it will override any timeout defined on
	// the supplied http.Client
	Client *http.Client

	// Logger that messages are written to. Default: nil
	Logger Logger

	// The Gatherer to use for metrics. Default: prometheus.DefaultGatherer
	Gatherer prometheus.Gatherer

	// Only publish whitelisted metrics
	WhitelistOnly bool

	// List of metrics that should be published, causing all others to be ignored.
	// Config.WhitelistOnly must be set to true for this to take effect.
	Whitelist []string

	// List of metrics that should never be published. This setting overrides entries in Config.Whitelist
	Blacklist []string
}

// Bridge pushes metrics to AWS Cloudwatch
type Bridge struct {
	interval time.Duration
	timeout  time.Duration

	promNamespace string
	cwNamespace   string

	useWhitelist bool
	whitelist    map[string]struct{}
	blacklist    map[string]struct{}

	logger Logger
	g      prometheus.Gatherer
	cw     *cloudwatch.CloudWatch
}

// NewBridge initializes and returns a pointer to a Bridge using the
// supplied configuration, or an error if there is a problem with
// the configuration
func NewBridge(c *Config) (*Bridge, error) {
	b := &Bridge{}

	if c.PrometheusNamespace == "" {
		return nil, errors.New("PrometheusNamespace must not be empty")
	} else {
		b.promNamespace = c.PrometheusNamespace
	}

	if c.CloudWatchNamespace == "" {
		return nil, errors.New("CloudWatchNamespace must not be empty")
	} else {
		b.cwNamespace = c.CloudWatchNamespace
	}

	if c.Interval > 0 {
		b.interval = c.Interval
	} else {
		b.interval = 15 * time.Second
	}

	var client *http.Client
	if c.Client != nil {
		client = c.Client
	} else {
		client = &http.Client{}
	}

	if c.Timeout > 0 {
		client.Timeout = c.Timeout
	} else {
		client.Timeout = time.Second
	}

	if c.Logger != nil {
		b.logger = c.Logger
	}

	if c.Gatherer != nil {
		b.g = c.Gatherer
	} else {
		b.g = prometheus.DefaultGatherer
	}

	b.useWhitelist = c.WhitelistOnly
	b.whitelist = make(map[string]struct{}, len(c.Whitelist))
	for _, v := range c.Whitelist {
		b.whitelist[v] = struct{}{}
	}

	b.blacklist = make(map[string]struct{}, len(c.Blacklist))
	for _, v := range c.Blacklist {
		b.blacklist[v] = struct{}{}
	}

	// Use default credential provider, which I believe supports the standard
	// AWS_* environment variables, and the shared credential file under ~/.aws
	sess, err := session.NewSession(&aws.Config{HTTPClient: client})
	if err != nil {
		return nil, err
	}

	b.cw = cloudwatch.New(sess)
	return b, nil
}

// From https://github.com/prometheus/client_golang/blob/master/prometheus/graphite/bridge.go
// Logger is the minimal interface Bridge needs for logging. Note that
// log.Logger from the standard library implements this interface, and it is
// easy to implement by custom loggers, if they don't do so already anyway.
type Logger interface {
	Println(v ...interface{})
}

// Run starts a loop that will push metrics to Cloudwatch at the
// configured interval. Run accepts a context.Context to support
// cancellation.
func (b *Bridge) Run(ctx context.Context) {
	ticker := time.NewTicker(b.interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := b.Publish(); err != nil && b.logger != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
		case <-ctx.Done():
			if b.logger != nil {
				b.logger.Println("stopping Cloudwatch publisher")
			}
			return
		}
	}
}

// Publish publishes the Prometheus metrics to Cloudwatch
func (b *Bridge) Publish() error {
	mfs, err := b.g.Gather()
	if err != nil {
		return err
	}

	return b.publishMetrics(mfs)
}

// NOTE: The CloudWatch API has the following limitations:
//		- Max 40kb request size
//		- Single namespace per request
//		- Max 10 dimensions per metric
func (b *Bridge) publishMetrics(mfs []*dto.MetricFamily) error {
	vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
		Timestamp: model.Now(),
	}, mfs...)
	if err != nil {
		return err
	}

	data := make([]*cloudwatch.MetricDatum, 0, batchSize)
	for _, s := range vec {
		name := getName(s.Metric)
		if b.isWhitelisted(name) {
			data = appendDatum(data, name, s)
			//} else {
			//fmt.Printf("discarding metric: name:[%q] [%+v]\n", name, s)
		}

		// punt on the 40KB size limitation. Will see how this works out in practice
		if len(data) == batchSize {
			if err := b.flush(data); err != nil {
				b.logger.Println("error publishing to Cloudwatch:", err)
			}
			data = make([]*cloudwatch.MetricDatum, 0, batchSize)
		}
	}

	return b.flush(data)
}

func (b *Bridge) flush(data []*cloudwatch.MetricDatum) error {
	if len(data) > 0 {
		// TODO: implement me
		fmt.Printf("Publishing data: %+v\n", data)
		//in := &cloudwatch.PutMetricDataInput{
		//	MetricData: data,
		//	Namespace:  b.cwNamespace,
		//}
		//_, err := b.cw.PutMetricData(in)
		//return err
	}
	return nil
}

func (b *Bridge) isWhitelisted(name string) bool {
	if !strings.HasPrefix(name, b.promNamespace) {
		return false
	} else if _, ok := b.blacklist[name]; ok {
		return false
	}

	if b.useWhitelist {
		if name == "" {
			return false
		}
		_, ok := b.whitelist[name]
		return ok
	}
	return true
}

func appendDatum(data []*cloudwatch.MetricDatum, name string, s *model.Sample) []*cloudwatch.MetricDatum {
	d := &cloudwatch.MetricDatum{}
	d.SetMetricName(name).
		SetValue(float64(s.Value)).
		SetTimestamp(s.Timestamp.Time()).
		SetDimensions(getDimensions(s.Metric)).
		SetStorageResolution(getResolution(s.Metric)).
		SetUnit(getUnit(s.Metric))
	return append(data, d)
}

func getName(m model.Metric) string {
	if n, ok := m[model.MetricNameLabel]; ok {
		return string(n)
	}
	return ""
}

// getDimensions returns up to 10 dimensions for the provided metric - one for each label (except the __name__ label)
//
// If a metric has more than 10 labels, it attempts to behave deterministically by sorting the labels lexicographically,
// and returning the first 10 labels as dimensions
func getDimensions(m model.Metric) []*cloudwatch.Dimension {
	if len(m) == 0 {
		return make([]*cloudwatch.Dimension, 0)
	} else if _, ok := m[model.MetricNameLabel]; len(m) == 1 && ok {
		return make([]*cloudwatch.Dimension, 0)
	}
	names := make([]string, 0, len(m))
	for k := range m {
		if !(k == model.MetricNameLabel || k == cwHighResLabel || k == cwUnitLabel) {
			names = append(names, string(k))
		}
	}

	sort.Strings(names)
	if len(names) > 10 {
		names = names[:10]
	}
	dims := make([]*cloudwatch.Dimension, 0, len(names))
	for _, k := range names {
		dims = append(dims, new(cloudwatch.Dimension).SetName(k).SetValue(string(m[model.LabelName(k)])))
	}
	return dims
}

// Returns 1 if the metric contains a __cw_high_res label, otherwise it return 60
func getResolution(m model.Metric) int64 {
	if _, ok := m[cwHighResLabel]; ok {
		return 1
	}
	return 60
}

// TODO: can we infer the proper unit based on the metric name?
func getUnit(m model.Metric) string {
	if u, ok := m[cwUnitLabel]; ok {
		return string(u)
	}
	return ""
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"
)

var ROOT = ""

type FileInfoJSON struct {
	Name        string `json:"name"`
	Size        int64  `json:"size"`
	ModifiedAt  string `json:"modified_at"`
	MIMEType    string `json:"mime_type"`
	IsDirectory bool   `json:"is_directory"`
	IsHidden    bool   `json:"is_hidden"`
}

// returns a pair of (filename, MIME type) strings given a `file` output line
func parseMIMEType(fileOutputLine string) (string, string, error) {
	// parse the file program output into a bare MIME type
	mimeString := strings.TrimSpace(fileOutputLine)
	splitIndex := strings.LastIndex(mimeString, ":")

	if len(fileOutputLine) <= 1 || splitIndex <= 0 {
		return "", "", fmt.Errorf("Invalid MIME string: '%s'", fileOutputLine)
	}

	return mimeString[0:splitIndex], strings.TrimSpace(mimeString[splitIndex+1:]), nil
}

func writeJSONResponse(w http.ResponseWriter, data interface{}) {
	w.Header().Add("Content-Type", "application/json")
	w.Header().Add("Cache-Control", "no-cache")

	json, err := json.Marshal(data)
	if err != nil {
		http.Error(w, "Failed to generate JSON response", 500)
		return
	}
	w.Write(json)
}

// given a path, returns a map of child name to MIME type
func getChildMIMETypes(parentPath string) map[string]string {
	result := make(map[string]string)

	// get all the children in the given directory
	children, err := filepath.Glob(path.Join(parentPath, "*"))
	if err != nil {
		return result
	}

	args := []string{"--mime-type", "--dereference", "--preserve-date"}
	args = append(args, children...)

	// call `file` for a newline-delimited list of "filename: MIME-type" pairs
	fileOutput, err := exec.Command("file", args...).Output()

	if err != nil {
		return result
	}

	for _, line := range strings.Split(string(fileOutput), "\n") {
		fileName, mimeType, err := parseMIMEType(line)
		if err == nil {
			result[fileName] = mimeType
		}
	}

	return result
}

func getMIMEType(filePath string) string {
	fileOutput, err := exec.Command(
		"file",
		"--mime-type",
		"--dereference",
		"--preserve-date",
		"--brief",
		filePath,
	).Output()

	if err != nil {
		return ""
	}

	return strings.TrimSpace(string(fileOutput))
}

// given a root and a relative child path, returns the normalized, absolute path
// of the child. if the path is not a child of the root or is otherwise invalid,
// returns an error.
func normalizePathUnderRoot(root, child string) (string, error) {
	// clean the path, resolving any ".."s in it
	requestPath := path.Clean(path.Join(root, child))

	// if the path exited the root directory, fail
	relPath, err := filepath.Rel(root, requestPath)
	if err != nil || strings.Index(relPath, "..") >= 0 {
		// keep things vague since someone's probably trying to be sneaky anyway
		return "", fmt.Errorf("Invalid path")
	}

	return requestPath, nil
}

// this returns the info for the specified files _or_ directory, not just files
func getInfo(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can return its info
	fileInfo, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	mimeType := getMIMEType(normalizedPath)

	writeJSONResponse(w, FileInfoJSON{
		fileInfo.Name(),
		fileInfo.Size(),
		fileInfo.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
		mimeType,
		fileInfo.IsDir(),
		strings.HasPrefix(fileInfo.Name(), "."),
	})
}

func download(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can set appropriate response headers, and so we can
	// ensure it's a regular file and not a directory.
	file, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// return different responses depending on file type
	if file.IsDir() {
		downloadDirectory(w, r, normalizedPath)
	} else {
		downloadFile(w, r, normalizedPath, file)
	}
}

func downloadFile(w http.ResponseWriter, r *http.Request, filePath string, file os.FileInfo) {
	mimeType := getMIMEType(filePath)
	w.Header().Add("Content-Type", mimeType)
	w.Header().Add("Content-Disposition", file.Name())
	w.Header().Add("Cache-Control", "no-cache")

	http.ServeFile(w, r, filePath)
}

func getDirectory(w http.ResponseWriter, r *http.Request) {
	// ensure the directory actually exists
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	children, err := ioutil.ReadDir(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// get a map of all the MIME types for the directory
	mimeTypes := getChildMIMETypes(normalizedPath)

	// list the directory to a JSON response
	var files []FileInfoJSON
	for _, file := range children {
		fileName := file.Name()
		mimeType := mimeTypes[path.Join(normalizedPath, fileName)]

		files = append(files, FileInfoJSON{
			fileName,
			file.Size(),
			file.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
			mimeType,
			file.IsDir(),
			strings.HasPrefix(fileName, "."), // hidden?
		})
	}

	writeJSONResponse(w, files)
}

// zip up a directory and write it to the response stream
func downloadDirectory(w http.ResponseWriter, r *http.Request, dirPath string) {
	// give the file a nice name, but replace the root directory name with
	// something generic.
	var downloadName string
	if dirPath == ROOT {
		downloadName = "files.zip"
	} else {
		downloadName = path.Base(dirPath) + ".zip"
	}

	w.Header().Add("Content-Type", "application/zip")
	w.Header().Add("Content-Disposition", downloadName)
	w.Header().Add("Cache-Control", "no-cache")

	z := zip.NewWriter(w)
	defer z.Close()

	// walk the directory and add each file to the zip file, giving up (returning
	// an error) if we encounter an error anywhere along the line.
	filepath.Walk(dirPath, func(fullFilePath string, file os.FileInfo, err error) error {
		if err != nil {
			// don't say what failed since doing so might leak the full path
			http.Error(w, "Failed to generate archive", 500)
			return err
		}

		// use the relative file path so we don't accidentally leak the full path
		// anywhere. we only use the full path to read the file from disk. we know
		// it's relative so we can ignore the error.
		filePath, _ := filepath.Rel(dirPath, fullFilePath)

		// build a header we can use to generate a ZIP archive entry
		header, err := zip.FileInfoHeader(file)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to generate archive header for %s", filePath), 500)
			return err
		}

		// ensure the name is set to relative path within this directory so we'll
		// preserve the directory's structure within the archive.
		header.Name = filePath

		// add a directory entry for true directories so they'll show up even if
		// they have no children. adding a trailing `/` does this for us,
		// apparently.
		fileIsSymlink := file.Mode() & os.ModeSymlink == os.ModeSymlink
		if file.IsDir() && !fileIsSymlink {
			header.Name += "/"
		}

		// generate an archive entry for this file/directory/symlink
		zf, err := z.CreateHeader(header)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to add %s to archive", filePath), 500)
			return err
		}

		// if the file is a symlink, preserve it as such
		if fileIsSymlink {
			// according to the ZIP format, symlinks must have the namesake file mode
			// with sole body content of the string path of the link's destination.
			dest, err := os.Readlink(fullFilePath)
			if err != nil {
				http.Error(w, fmt.Sprintf("Failed to resolve %s", filePath), 500)
				return err
			}

			zf.Write([]byte(dest))
		} else if file.IsDir() {
			// NOTE: do nothing since all we have to do for directories is create
			// their header entry, which has already been done.
		} else {
			// open the file for reading
			f, err := os.Open(fullFilePath)
			if err != nil {
				http.Error(w, fmt.Sprintf("Failed to read %s", filePath), 500)
				return err
			}
			defer f.Close()

			// write the file contents to the archive
			written, err := io.Copy(zf, f)
			if err != nil {
				http.Error(w, fmt.Sprintf("Failed to write %s to archive after %d bytes", filePath, written), 500)
				return err
			}
		}

		// flush what we've written so far to the client so the download will be as
		// incremental as possible. doing flushes after every file also ensures that
		// our memory usage doesn't balloon to the entire size of the zipped
		// directory, just the size of one file (which is better than nothing...).
		err = z.Flush()
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to flush data for %s", filePath), 500)
			return err
		}

		return nil
	})
}

// retrieves/caches/updates a thumbnail file given a path, or returns an error
// if no thumbnail could be geneated.
func getThumbnail(w http.ResponseWriter, r *http.Request) {
	// TODO:
	// * look up the existing file to get its modtime and ensure it exists
	// * see if we have a cached file with the same modtime
	// ** if so, use it
	// ** otherwise, generate a preview
	// *** use graphicsmagick/ffmpeg to generate a preview thumbnail
	// *** store the new file to a mirroed path with the filename plus the modtime
	// * read the cached file and return its contents

	// cache preview thumbnails for a good while to lower load on this tiny
	// server, even if we are caching the preview thumbnails on-disk too.
	w.Header().Add("Cache-Control", "max-age=3600")
	w.Header().Add("Content-Type", "image/jpeg")
}

func main() {
	// ensure we have all the binaries we need
	requiredBinaries := []string{"file"}
	for _, binary := range requiredBinaries {
		if _, err := exec.LookPath(binary); err != nil {
			log.Panicf("'%s' must be installed and in the PATH\n", binary)
		}
	}

	if len(os.Args) <= 1 {
		panic("A root directory argument is required")
	}

	ROOT = path.Clean(os.Args[1])

	router := mux.NewRouter()

	// /files
	// anything with a trailing `/` indicates a directory; anything that ends
	// without a trailing slash indicates a file.
	filesJSON := router.Headers("Content-Type", "application/json").Subrouter()
	filesJSON.HandleFunc("/files/{path:.*[^/]$}", getInfo).
		Headers("Content-Type", "application/json").
		Methods("GET")
	filesJSON.HandleFunc("/files{path:.*}/", getDirectory).
		Headers("Content-Type", "application/json").
		Methods("GET")

	router.HandleFunc("/files/{path:.*}", download).
		Methods("GET")

	// /thumbnails
	router.HandleFunc("/thumbnails/{path:.*[^/]$}", getThumbnail).
		Methods("GET")

	addr := "127.0.0.1:3000"
	fmt.Printf("Serving %s to %s...\n", ROOT, addr)
	http.ListenAndServe(addr, router)
}
<commit_msg>Add `is_symlink` stat to file info<commit_after>package main

import (
	"archive/zip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"
)

var ROOT = ""

type FileInfoJSON struct {
	Name        string `json:"name"`
	Size        int64  `json:"size"`
	ModifiedAt  string `json:"modified_at"`
	MIMEType    string `json:"mime_type"`
	IsDirectory bool   `json:"is_directory"`
	IsHidden    bool   `json:"is_hidden"`
	IsSymlink   bool   `json:"is_symlink"`
}

// returns a pair of (filename, MIME type) strings given a `file` output line
func parseMIMEType(fileOutputLine string) (string, string, error) {
	// parse the file program output into a bare MIME type
	mimeString := strings.TrimSpace(fileOutputLine)
	splitIndex := strings.LastIndex(mimeString, ":")

	if len(fileOutputLine) <= 1 || splitIndex <= 0 {
		return "", "", fmt.Errorf("Invalid MIME string: '%s'", fileOutputLine)
	}

	return mimeString[0:splitIndex], strings.TrimSpace(mimeString[splitIndex+1:]), nil
}

func writeJSONResponse(w http.ResponseWriter, data interface{}) {
	w.Header().Add("Content-Type", "application/json")
	w.Header().Add("Cache-Control", "no-cache")

	json, err := json.Marshal(data)
	if err != nil {
		http.Error(w, "Failed to generate JSON response", 500)
		return
	}
	w.Write(json)
}

// given a path, returns a map of child name to MIME type
func getChildMIMETypes(parentPath string) map[string]string {
	result := make(map[string]string)

	// get all the children in the given directory
	children, err := filepath.Glob(path.Join(parentPath, "*"))
	if err != nil {
		return result
	}

	args := []string{"--mime-type", "--dereference", "--preserve-date"}
	args = append(args, children...)

	// call `file` for a newline-delimited list of "filename: MIME-type" pairs
	fileOutput, err := exec.Command("file", args...).Output()

	if err != nil {
		return result
	}

	for _, line := range strings.Split(string(fileOutput), "\n") {
		fileName, mimeType, err := parseMIMEType(line)
		if err == nil {
			result[fileName] = mimeType
		}
	}

	return result
}

func getMIMEType(filePath string) string {
	fileOutput, err := exec.Command(
		"file",
		"--mime-type",
		// "--dereference",
		"--preserve-date",
		"--brief",
		filePath,
	).Output()

	if err != nil {
		return ""
	}

	return strings.TrimSpace(string(fileOutput))
}

// given a root and a relative child path, returns the normalized, absolute path
// of the child. if the path is not a child of the root or is otherwise invalid,
// returns an error.
func normalizePathUnderRoot(root, child string) (string, error) {
	// clean the path, resolving any ".."s in it
	requestPath := path.Clean(path.Join(root, child))

	// if the path exited the root directory, fail
	relPath, err := filepath.Rel(root, requestPath)
	if err != nil || strings.Index(relPath, "..") >= 0 {
		// keep things vague since someone's probably trying to be sneaky anyway
		return "", fmt.Errorf("Invalid path")
	}

	return requestPath, nil
}

// this returns the info for the specified files _or_ directory, not just files
func getInfo(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can return its info
	fileInfo, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	mimeType := getMIMEType(normalizedPath)

	writeJSONResponse(w, FileInfoJSON{
		fileInfo.Name(),
		fileInfo.Size(),
		fileInfo.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
		mimeType,
		fileInfo.IsDir(),
		strings.HasPrefix(fileInfo.Name(), "."),
		fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink,
	})
}

func download(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can set appropriate response headers, and so we can
	// ensure it's a regular file and not a directory.
	file, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// return different responses depending on file type
	if file.IsDir() {
		downloadDirectory(w, r, normalizedPath)
	} else {
		downloadFile(w, r, normalizedPath, file)
	}
}

func downloadFile(w http.ResponseWriter, r *http.Request, filePath string, file os.FileInfo) {
	mimeType := getMIMEType(filePath)
	w.Header().Add("Content-Type", mimeType)
	w.Header().Add("Content-Disposition", file.Name())
	w.Header().Add("Cache-Control", "no-cache")

	http.ServeFile(w, r, filePath)
}

func getDirectory(w http.ResponseWriter, r *http.Request) {
	// ensure the directory actually exists
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	children, err := ioutil.ReadDir(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// get a map of all the MIME types for the directory
	mimeTypes := getChildMIMETypes(normalizedPath)

	// list the directory to a JSON response
	var files []FileInfoJSON
	for _, file := range children {
		fileName := file.Name()
		mimeType := mimeTypes[path.Join(normalizedPath, fileName)]

		files = append(files, FileInfoJSON{
			fileName,
			file.Size(),
			file.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
			mimeType,
			file.IsDir(),
			strings.HasPrefix(fileName, "."), // hidden?
			file.Mode()&os.ModeSymlink == os.ModeSymlink,
		})
	}

	writeJSONResponse(w, files)
}

// zip up a directory and write it to the response stream
func downloadDirectory(w http.ResponseWriter, r *http.Request, dirPath string) {
	// give the file a nice name, but replace the root directory name with
	// something generic.
	var downloadName string
	if dirPath == ROOT {
		downloadName = "files.zip"
	} else {
		downloadName = path.Base(dirPath) + ".zip"
	}

	w.Header().Add("Content-Type", "application/zip")
	w.Header().Add("Content-Disposition", downloadName)
	w.Header().Add("Cache-Control", "no-cache")

	z := zip.NewWriter(w)
	defer z.Close()

	// walk the directory and add each file to the zip file, giving up (returning
	// an error) if we encounter an error anywhere along the line.
	filepath.Walk(dirPath, func(fullFilePath string, file os.FileInfo, err error) error {
		if err != nil {
			// don't say what failed since doing so might leak the full path
			http.Error(w, "Failed to generate archive", 500)
			return err
		}

		// use the relative file path so we don't accidentally leak the full path
		// anywhere. we only use the full path to read the file from disk. we know
		// it's relative so we can ignore the error.
		filePath, _ := filepath.Rel(dirPath, fullFilePath)

		// build a header we can use to generate a ZIP archive entry
		header, err := zip.FileInfoHeader(file)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to generate archive header for %s", filePath), 500)
			return err
		}

		// ensure the name is set to relative path within this directory so we'll
		// preserve the directory's structure within the archive.
		header.Name = filePath

		// add a directory entry for true directories so they'll show up even if
		// they have no children. adding a trailing `/` does this for us,
		// apparently.
		fileIsSymlink := file.Mode()&os.ModeSymlink == os.ModeSymlink
		if file.IsDir() && !fileIsSymlink {
			header.Name += "/"
		}

		// generate an archive entry for this file/directory/symlink
		zf, err := z.CreateHeader(header)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to add %s to archive", filePath), 500)
			return err
		}

		// if the file is a symlink, preserve it as such
		if fileIsSymlink {
			// according to the ZIP format, symlinks must have the namesake file mode
			// with sole body content of the string path of the link's destination.
			dest, err := os.Readlink(fullFilePath)
			if err != nil {
				http.Error(w, fmt.Sprintf("Failed to resolve %s", filePath), 500)
				return err
			}

			zf.Write([]byte(dest))
		} else if file.IsDir() {
			// NOTE: do nothing since all we have to do for directories is create
			// their header entry, which has already been done.
		} else {
			// open the file for reading
			f, err := os.Open(fullFilePath)
			if err != nil {
				http.Error(w, fmt.Sprintf("Failed to read %s", filePath), 500)
				return err
			}
			defer f.Close()

			// write the file contents to the archive
			written, err := io.Copy(zf, f)
			if err != nil {
				http.Error(w, fmt.Sprintf("Failed to write %s to archive after %d bytes", filePath, written), 500)
				return err
			}
		}

		// flush what we've written so far to the client so the download will be as
		// incremental as possible. doing flushes after every file also ensures that
		// our memory usage doesn't balloon to the entire size of the zipped
		// directory, just the size of one file (which is better than nothing...).
		err = z.Flush()
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to flush data for %s", filePath), 500)
			return err
		}

		return nil
	})
}

// retrieves/caches/updates a thumbnail file given a path, or returns an error
// if no thumbnail could be geneated.
func getThumbnail(w http.ResponseWriter, r *http.Request) {
	// TODO:
	// * look up the parent file to get its modtime and ensure it exists
	// * see if we have a cached file with the same modtime
	// ** if so, use it
	// ** otherwise, generate a preview
	// *** use graphicsmagick/ffmpeg to generate a preview thumbnail
	// *** store the thumbnail to a mirrored path with the filename plus the modtime
	// * read the cached file and return its contents

	// cache preview thumbnails for a good while to lower load on this tiny
	// server, even if we are caching the preview thumbnails on-disk too.
	w.Header().Add("Cache-Control", "max-age=3600")
	w.Header().Add("Content-Type", "image/jpeg")
}

func main() {
	// ensure we have all the binaries we need
	requiredBinaries := []string{"file"}
	for _, binary := range requiredBinaries {
		if _, err := exec.LookPath(binary); err != nil {
			log.Panicf("'%s' must be installed and in the PATH\n", binary)
		}
	}

	if len(os.Args) <= 1 {
		panic("A root directory argument is required")
	}

	ROOT = path.Clean(os.Args[1])

	router := mux.NewRouter()

	// /files
	// anything with a trailing `/` indicates a directory; anything that ends
	// without a trailing slash indicates a file.
	filesJSON := router.Headers("Content-Type", "application/json").Subrouter()
	filesJSON.HandleFunc("/files/{path:.*[^/]$}", getInfo).
		Headers("Content-Type", "application/json").
		Methods("GET")
	filesJSON.HandleFunc("/files{path:.*}/", getDirectory).
		Headers("Content-Type", "application/json").
		Methods("GET")

	router.HandleFunc("/files/{path:.*}", download).
		Methods("GET")

	// /thumbnails
	router.HandleFunc("/thumbnails/{path:.*[^/]$}", getThumbnail).
		Methods("GET")

	addr := "127.0.0.1:3000"
	fmt.Printf("Serving %s to %s...\n", ROOT, addr)
	http.ListenAndServe(addr, router)
}
<|endoftext|>


<commit_before>/*
Package threshold contains predicates that can define various request thresholds

Examples:

* RequestMethod() == "GET" triggers action when request method equals "GET"
* IsNetworkError() - triggers action on network errors
* RequestMethod() == "GET" && Attempts <= 2 && (IsNetworkError() || ResponseCode() == 408)
  This predicate triggers for GET requests with maximum 2 attempts
  on network errors or when upstream returns special http response code 408
*/
package threshold

import (
	"fmt"

	"github.com/mailgun/vulcan/request"
)

// Predicate that defines what request can fail over in case of error or http response
type Predicate func(request.Request) bool

// RequestToString defines mapper function that maps a request to some string (e.g extracts method name)
type RequestToString func(req request.Request) string

// RequestToInt defines mapper function that maps a request to some int (e.g extracts response code)
type RequestToInt func(req request.Request) int

// RequestMethod returns mapper of the request to it's method e.g. POST
func RequestMethod() RequestToString {
	return func(r request.Request) string {
		return r.GetHttpRequest().Method
	}
}

// Attempts returns mapper of the request to the number of proxy attempts
func Attempts() RequestToInt {
	return func(r request.Request) int {
		return len(r.GetAttempts())
	}
}

// ResponseCode returns mapper of the request to the last response code, returns 0 if there was no response code.
func ResponseCode() RequestToInt {
	return func(r request.Request) int {
		attempts := len(r.GetAttempts())
		if attempts == 0 {
			return 0
		}
		lastResponse := r.GetAttempts()[attempts-1].GetResponse()
		if lastResponse == nil {
			return 0
		}
		return lastResponse.StatusCode
	}
}

// IsNetworkError returns a predicate that returns true if last attempt ended with network error.
func IsNetworkError() Predicate {
	return func(r request.Request) bool {
		attempts := len(r.GetAttempts())
		return attempts != 0 && r.GetAttempts()[attempts-1].GetError() != nil
	}
}

// Function that returns predicate by joining the passed predicates with AND
func AND(fns ...Predicate) Predicate {
	return func(req request.Request) bool {
		for _, fn := range fns {
			if !fn(req) {
				return false
			}
		}
		return true
	}
}

// Function that returns predicate by joining the passed predicates with OR
func OR(fns ...Predicate) Predicate {
	return func(req request.Request) bool {
		for _, fn := range fns {
			if fn(req) {
				return true
			}
		}
		return false
	}
}

// NOT creates negation of the passed predicate
func NOT(p Predicate) Predicate {
	return func(r request.Request) bool {
		return !p(r)
	}
}

// EQ returns predicate that tests for equality of the value of the mapper and the constant
func EQ(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToString:
		return stringEQ(mapper, value)
	case RequestToInt:
		return intEQ(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// EQ returns predicate that tests for inequality of the value of the mapper and the constant
func NEQ(m interface{}, value interface{}) (Predicate, error) {
	p, err := EQ(m, value)
	if err != nil {
		return nil, err
	}
	return NOT(p), nil
}

// LT returns predicate that tests that value of the mapper function is less than the constant
func LT(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intLT(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// GT returns predicate that tests that value of the mapper function is greater than the constant
func GT(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intGT(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// LT returns predicate that tests that value of the mapper function is less or equal to the constant
func LE(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intLE(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// GE returns predicate that tests that value of the mapper function is less or equal to the constant
func GE(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intGE(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

func stringEQ(m RequestToString, val interface{}) (Predicate, error) {
	value, ok := val.(string)
	if !ok {
		return nil, fmt.Errorf("expected string, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) == value
	}, nil
}

func intEQ(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) == value
	}, nil
}

func intLT(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) < value
	}, nil
}

func intGT(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) > value
	}, nil
}

func intLE(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) <= value
	}, nil
}

func intGE(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) >= value
	}, nil
}
<commit_msg>Address code review comments<commit_after>/*
Package threshold contains predicates that can define various request thresholds

Examples:

* RequestMethod() == "GET" triggers action when request method equals "GET"
* IsNetworkError() - triggers action on network errors
* RequestMethod() == "GET" && Attempts <= 2 && (IsNetworkError() || ResponseCode() == 408)
  This predicate triggers for GET requests with maximum 2 attempts
  on network errors or when upstream returns special http response code 408
*/
package threshold

import (
	"fmt"

	"github.com/mailgun/vulcan/request"
)

// Predicate that defines what request can fail over in case of error or http response
type Predicate func(request.Request) bool

// RequestToString defines mapper function that maps a request to some string (e.g extracts method name)
type RequestToString func(req request.Request) string

// RequestToInt defines mapper function that maps a request to some int (e.g extracts response code)
type RequestToInt func(req request.Request) int

// RequestMethod returns mapper of the request to its method e.g. POST
func RequestMethod() RequestToString {
	return func(r request.Request) string {
		return r.GetHttpRequest().Method
	}
}

// Attempts returns mapper of the request to the number of proxy attempts
func Attempts() RequestToInt {
	return func(r request.Request) int {
		return len(r.GetAttempts())
	}
}

// ResponseCode returns mapper of the request to the last response code, returns 0 if there was no response code.
func ResponseCode() RequestToInt {
	return func(r request.Request) int {
		attempts := len(r.GetAttempts())
		if attempts == 0 {
			return 0
		}
		lastResponse := r.GetAttempts()[attempts-1].GetResponse()
		if lastResponse == nil {
			return 0
		}
		return lastResponse.StatusCode
	}
}

// IsNetworkError returns a predicate that returns true if last attempt ended with network error.
func IsNetworkError() Predicate {
	return func(r request.Request) bool {
		attempts := len(r.GetAttempts())
		return attempts != 0 && r.GetAttempts()[attempts-1].GetError() != nil
	}
}

// AND returns predicate by joining the passed predicates with logical 'and'
func AND(fns ...Predicate) Predicate {
	return func(req request.Request) bool {
		for _, fn := range fns {
			if !fn(req) {
				return false
			}
		}
		return true
	}
}

// OR returns predicate by joining the passed predicates with logical 'or'
func OR(fns ...Predicate) Predicate {
	return func(req request.Request) bool {
		for _, fn := range fns {
			if fn(req) {
				return true
			}
		}
		return false
	}
}

// NOT creates negation of the passed predicate
func NOT(p Predicate) Predicate {
	return func(r request.Request) bool {
		return !p(r)
	}
}

// EQ returns predicate that tests for equality of the value of the mapper and the constant
func EQ(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToString:
		return stringEQ(mapper, value)
	case RequestToInt:
		return intEQ(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// NEQ returns predicate that tests for inequality of the value of the mapper and the constant
func NEQ(m interface{}, value interface{}) (Predicate, error) {
	p, err := EQ(m, value)
	if err != nil {
		return nil, err
	}
	return NOT(p), nil
}

// LT returns predicate that tests that value of the mapper function is less than the constant
func LT(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intLT(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// GT returns predicate that tests that value of the mapper function is greater than the constant
func GT(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intGT(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// LE returns predicate that tests that value of the mapper function is less than or equal to the constant
func LE(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intLE(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

// GE returns predicate that tests that value of the mapper function is greater than or equal to the constant
func GE(m interface{}, value interface{}) (Predicate, error) {
	switch mapper := m.(type) {
	case RequestToInt:
		return intGE(mapper, value)
	}
	return nil, fmt.Errorf("unsupported argument: %T", m)
}

func stringEQ(m RequestToString, val interface{}) (Predicate, error) {
	value, ok := val.(string)
	if !ok {
		return nil, fmt.Errorf("expected string, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) == value
	}, nil
}

func intEQ(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) == value
	}, nil
}

func intLT(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) < value
	}, nil
}

func intGT(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) > value
	}, nil
}

func intLE(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) <= value
	}, nil
}

func intGE(m RequestToInt, val interface{}) (Predicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(req request.Request) bool {
		return m(req) >= value
	}, nil
}
<|endoftext|>


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

package engi

// A rather basic camera
type Camera struct {
	pos, to  Point
	tracking *Entity // The entity that is currently being followed
}

func (cam *Camera) FollowEntity(entity *Entity) {
	cam.tracking = entity
	var space *SpaceComponent
	if !cam.tracking.GetComponent(&space) {
		return
	}

	cam.to = space.Position
	cam.centerCam(Width(), Height(), 1, space)
}

func (cam *Camera) Update(dt float32) {
	// maxPoints := 8
	if cam.tracking != nil {
		var space *SpaceComponent
		if !cam.tracking.GetComponent(&space) {
			return
		}
		cam.centerCam(Width(), Height(), 0.09, space)
	}
}

func (cam *Camera) centerCam(width, height, lerp float32, space *SpaceComponent) {
	lvl := Files.Level("test.tmx")

	maxX := float32(lvl.Width * lvl.TileWidth)
	//maxY := float32(lvl.Height * lvl.TileHeight)

	cam.to.X += ((space.Position.X + space.Width/2) - (cam.to.X + width/2)) * lerp
	cam.to.Y += ((space.Position.Y + space.Height) - (cam.to.Y + height)) * lerp

	if !(cam.to.X+width >= maxX) && !(cam.to.X <= 0) {
		cam.pos = cam.to
	}
}
<commit_msg>camera:added y axis tracking<commit_after>// Copyright 2014 Harrison Shoebridge. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package engi

import "math"

// A rather basic camera
type Camera struct {
	pos, to  Point
	tracking *Entity // The entity that is currently being followed
}

func (cam *Camera) FollowEntity(entity *Entity) {
	cam.tracking = entity
	var space *SpaceComponent
	if !cam.tracking.GetComponent(&space) {
		return
	}

	cam.to = space.Position
	cam.centerCam(Width(), Height(), 1, space)
}

func (cam *Camera) Update(dt float32) {
	// maxPoints := 8
	if cam.tracking != nil {
		var space *SpaceComponent
		if !cam.tracking.GetComponent(&space) {
			return
		}
		cam.centerCam(Width(), Height(), 0.09, space)
	}
}

func (cam *Camera) centerCam(width, height, lerp float32, space *SpaceComponent) {
	lvl := Files.Level("test.tmx")

	maxX := float32(lvl.Width * lvl.TileWidth)
	maxY := float32(lvl.Height * lvl.TileHeight)

	cam.to.X += ((space.Position.X + space.Width/2) - (cam.to.X + width/2)) * lerp
	cam.to.Y += ((space.Position.Y + space.Height/2) - (cam.to.Y + height/2)) * lerp

	if !(cam.to.X+width >= maxX) && !(cam.to.X <= 0) {
		cam.pos.X = cam.to.X
	}
	if !(float32(math.Abs(float64(cam.to.Y)))+height/2 >= maxY) {
		cam.pos.Y = cam.to.Y
	}
}
<|endoftext|>


<commit_before>// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package mysql

import (
	"database/sql/driver"
	"net"
	"strconv"
	"strings"
	"time"
)

type mysqlConn struct {
	buf              buffer
	netConn          net.Conn
	affectedRows     uint64
	insertId         uint64
	cfg              *Config
	maxPacketAllowed int
	maxWriteSize     int
	flags            clientFlag
	status           statusFlag
	sequence         uint8
	parseTime        bool
	strict           bool
}

// Handles parameters set in DSN after the connection is established
func (mc *mysqlConn) handleParams() (err error) {
	for param, val := range mc.cfg.Params {
		switch param {
		// Charset
		case "charset":
			charsets := strings.Split(val, ",")
			for i := range charsets {
				// ignore errors here - a charset may not exist
				err = mc.exec("SET NAMES " + charsets[i])
				if err == nil {
					break
				}
			}
			if err != nil {
				return
			}

		// System Vars
		default:
			err = mc.exec("SET " + param + "=" + val + "")
			if err != nil {
				return
			}
		}
	}

	return
}

func (mc *mysqlConn) Begin() (driver.Tx, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	err := mc.exec("START TRANSACTION")
	if err == nil {
		return &mysqlTx{mc}, err
	}

	return nil, err
}

func (mc *mysqlConn) Close() (err error) {
	// Makes Close idempotent
	if mc.netConn != nil {
		err = mc.writeCommandPacket(comQuit)
	}

	mc.cleanup()

	return
}

// Closes the network connection and unsets internal variables. Do not call this
// function after successfully authentication, call Close instead. This function
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {
	// Makes cleanup idempotent
	if mc.netConn != nil {
		if err := mc.netConn.Close(); err != nil {
			errLog.Print(err)
		}
		mc.netConn = nil
	}
	mc.cfg = nil
	mc.buf.rd = nil
}

func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	// Send command
	err := mc.writeCommandPacketStr(comStmtPrepare, query)
	if err != nil {
		return nil, err
	}

	stmt := &mysqlStmt{
		mc: mc,
	}

	// Read Result
	columnCount, err := stmt.readPrepareResultPacket()
	if err == nil {
		if stmt.paramCount > 0 {
			if err = mc.readUntilEOF(); err != nil {
				return nil, err
			}
		}

		if columnCount > 0 {
			err = mc.readUntilEOF()
		}
	}

	return stmt, err
}

func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
	buf := mc.buf.takeCompleteBuffer()
	if buf == nil {
		// can not take the buffer. Something must be wrong with the connection
		errLog.Print(ErrBusyBuffer)
		return "", driver.ErrBadConn
	}
	buf = buf[:0]
	argPos := 0

	for i := 0; i < len(query); i++ {
		q := strings.IndexByte(query[i:], '?')
		if q == -1 {
			buf = append(buf, query[i:]...)
			break
		}
		buf = append(buf, query[i:i+q]...)
		i += q

		arg := args[argPos]
		argPos++

		if arg == nil {
			buf = append(buf, "NULL"...)
			continue
		}

		switch v := arg.(type) {
		case int64:
			buf = strconv.AppendInt(buf, v, 10)
		case float64:
			buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
		case bool:
			if v {
				buf = append(buf, '1')
			} else {
				buf = append(buf, '0')
			}
		case time.Time:
			if v.IsZero() {
				buf = append(buf, "'0000-00-00'"...)
			} else {
				v := v.In(mc.cfg.Loc)
				v = v.Add(time.Nanosecond * 500) // To round under microsecond
				year := v.Year()
				year100 := year / 100
				year1 := year % 100
				month := v.Month()
				day := v.Day()
				hour := v.Hour()
				minute := v.Minute()
				second := v.Second()
				micro := v.Nanosecond() / 1000

				buf = append(buf, []byte{
					'\'',
					digits10[year100], digits01[year100],
					digits10[year1], digits01[year1],
					'-',
					digits10[month], digits01[month],
					'-',
					digits10[day], digits01[day],
					' ',
					digits10[hour], digits01[hour],
					':',
					digits10[minute], digits01[minute],
					':',
					digits10[second], digits01[second],
				}...)

				if micro != 0 {
					micro10000 := micro / 10000
					micro100 := micro / 100 % 100
					micro1 := micro % 100
					buf = append(buf, []byte{
						'.',
						digits10[micro10000], digits01[micro10000],
						digits10[micro100], digits01[micro100],
						digits10[micro1], digits01[micro1],
					}...)
				}
				buf = append(buf, '\'')
			}
		case []byte:
			if v == nil {
				buf = append(buf, "NULL"...)
			} else {
				buf = append(buf, "_binary'"...)
				if mc.status&statusNoBackslashEscapes == 0 {
					buf = escapeBytesBackslash(buf, v)
				} else {
					buf = escapeBytesQuotes(buf, v)
				}
				buf = append(buf, '\'')
			}
		case string:
			buf = append(buf, '\'')
			if mc.status&statusNoBackslashEscapes == 0 {
				buf = escapeStringBackslash(buf, v)
			} else {
				buf = escapeStringQuotes(buf, v)
			}
			buf = append(buf, '\'')
		default:
			return "", driver.ErrSkip
		}

		if len(buf)+4 > mc.maxPacketAllowed {
			return "", driver.ErrSkip
		}
	}
	if argPos != len(args) {
		return "", driver.ErrSkip
	}
	return string(buf), nil
}

func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	if len(args) != 0 {
		if !mc.cfg.InterpolateParams {
			return nil, driver.ErrSkip
		}
		// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
		prepared, err := mc.interpolateParams(query, args)
		if err != nil {
			return nil, err
		}
		query = prepared
		args = nil
	}
	mc.affectedRows = 0
	mc.insertId = 0

	err := mc.exec(query)
	if err == nil {
		return &mysqlResult{
			affectedRows: int64(mc.affectedRows),
			insertId:     int64(mc.insertId),
		}, err
	}
	return nil, err
}

// Internal function to execute commands
func (mc *mysqlConn) exec(query string) error {
	// Send command
	err := mc.writeCommandPacketStr(comQuery, query)
	if err != nil {
		return err
	}

	// Read Result
	resLen, err := mc.readResultSetHeaderPacket()
	if err == nil && resLen > 0 {
		if err = mc.readUntilEOF(); err != nil {
			return err
		}

		err = mc.readUntilEOF()
	}

	return err
}

func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	if len(args) != 0 {
		if !mc.cfg.InterpolateParams {
			return nil, driver.ErrSkip
		}
		// try client-side prepare to reduce roundtrip
		prepared, err := mc.interpolateParams(query, args)
		if err != nil {
			return nil, err
		}
		query = prepared
		args = nil
	}
	// Send command
	err := mc.writeCommandPacketStr(comQuery, query)
	if err == nil {
		// Read Result
		var resLen int
		resLen, err = mc.readResultSetHeaderPacket()
		if err == nil {
			rows := new(textRows)
			rows.mc = mc

			if resLen == 0 {
				// no columns, no more data
				return emptyRows{}, nil
			}
			// Columns
			rows.columns, err = mc.readColumns(resLen)
			return rows, err
		}
	}
	return nil, err
}

// Gets the value of the given MySQL System Variable
// The returned byte slice is only valid until the next read
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
	// Send command
	if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
		return nil, err
	}

	// Read Result
	resLen, err := mc.readResultSetHeaderPacket()
	if err == nil {
		rows := new(textRows)
		rows.mc = mc

		if resLen > 0 {
			// Columns
			if err := mc.readUntilEOF(); err != nil {
				return nil, err
			}
		}

		dest := make([]driver.Value, resLen)
		if err = rows.readRow(dest); err == nil {
			return dest[0].([]byte), mc.readUntilEOF()
		}
	}
	return nil, err
}
<commit_msg>Fix getSystemVar with parseTime=true<commit_after>// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package mysql

import (
	"database/sql/driver"
	"net"
	"strconv"
	"strings"
	"time"
)

type mysqlConn struct {
	buf              buffer
	netConn          net.Conn
	affectedRows     uint64
	insertId         uint64
	cfg              *Config
	maxPacketAllowed int
	maxWriteSize     int
	flags            clientFlag
	status           statusFlag
	sequence         uint8
	parseTime        bool
	strict           bool
}

// Handles parameters set in DSN after the connection is established
func (mc *mysqlConn) handleParams() (err error) {
	for param, val := range mc.cfg.Params {
		switch param {
		// Charset
		case "charset":
			charsets := strings.Split(val, ",")
			for i := range charsets {
				// ignore errors here - a charset may not exist
				err = mc.exec("SET NAMES " + charsets[i])
				if err == nil {
					break
				}
			}
			if err != nil {
				return
			}

		// System Vars
		default:
			err = mc.exec("SET " + param + "=" + val + "")
			if err != nil {
				return
			}
		}
	}

	return
}

func (mc *mysqlConn) Begin() (driver.Tx, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	err := mc.exec("START TRANSACTION")
	if err == nil {
		return &mysqlTx{mc}, err
	}

	return nil, err
}

func (mc *mysqlConn) Close() (err error) {
	// Makes Close idempotent
	if mc.netConn != nil {
		err = mc.writeCommandPacket(comQuit)
	}

	mc.cleanup()

	return
}

// Closes the network connection and unsets internal variables. Do not call this
// function after successfully authentication, call Close instead. This function
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {
	// Makes cleanup idempotent
	if mc.netConn != nil {
		if err := mc.netConn.Close(); err != nil {
			errLog.Print(err)
		}
		mc.netConn = nil
	}
	mc.cfg = nil
	mc.buf.rd = nil
}

func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	// Send command
	err := mc.writeCommandPacketStr(comStmtPrepare, query)
	if err != nil {
		return nil, err
	}

	stmt := &mysqlStmt{
		mc: mc,
	}

	// Read Result
	columnCount, err := stmt.readPrepareResultPacket()
	if err == nil {
		if stmt.paramCount > 0 {
			if err = mc.readUntilEOF(); err != nil {
				return nil, err
			}
		}

		if columnCount > 0 {
			err = mc.readUntilEOF()
		}
	}

	return stmt, err
}

func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
	buf := mc.buf.takeCompleteBuffer()
	if buf == nil {
		// can not take the buffer. Something must be wrong with the connection
		errLog.Print(ErrBusyBuffer)
		return "", driver.ErrBadConn
	}
	buf = buf[:0]
	argPos := 0

	for i := 0; i < len(query); i++ {
		q := strings.IndexByte(query[i:], '?')
		if q == -1 {
			buf = append(buf, query[i:]...)
			break
		}
		buf = append(buf, query[i:i+q]...)
		i += q

		arg := args[argPos]
		argPos++

		if arg == nil {
			buf = append(buf, "NULL"...)
			continue
		}

		switch v := arg.(type) {
		case int64:
			buf = strconv.AppendInt(buf, v, 10)
		case float64:
			buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
		case bool:
			if v {
				buf = append(buf, '1')
			} else {
				buf = append(buf, '0')
			}
		case time.Time:
			if v.IsZero() {
				buf = append(buf, "'0000-00-00'"...)
			} else {
				v := v.In(mc.cfg.Loc)
				v = v.Add(time.Nanosecond * 500) // To round under microsecond
				year := v.Year()
				year100 := year / 100
				year1 := year % 100
				month := v.Month()
				day := v.Day()
				hour := v.Hour()
				minute := v.Minute()
				second := v.Second()
				micro := v.Nanosecond() / 1000

				buf = append(buf, []byte{
					'\'',
					digits10[year100], digits01[year100],
					digits10[year1], digits01[year1],
					'-',
					digits10[month], digits01[month],
					'-',
					digits10[day], digits01[day],
					' ',
					digits10[hour], digits01[hour],
					':',
					digits10[minute], digits01[minute],
					':',
					digits10[second], digits01[second],
				}...)

				if micro != 0 {
					micro10000 := micro / 10000
					micro100 := micro / 100 % 100
					micro1 := micro % 100
					buf = append(buf, []byte{
						'.',
						digits10[micro10000], digits01[micro10000],
						digits10[micro100], digits01[micro100],
						digits10[micro1], digits01[micro1],
					}...)
				}
				buf = append(buf, '\'')
			}
		case []byte:
			if v == nil {
				buf = append(buf, "NULL"...)
			} else {
				buf = append(buf, "_binary'"...)
				if mc.status&statusNoBackslashEscapes == 0 {
					buf = escapeBytesBackslash(buf, v)
				} else {
					buf = escapeBytesQuotes(buf, v)
				}
				buf = append(buf, '\'')
			}
		case string:
			buf = append(buf, '\'')
			if mc.status&statusNoBackslashEscapes == 0 {
				buf = escapeStringBackslash(buf, v)
			} else {
				buf = escapeStringQuotes(buf, v)
			}
			buf = append(buf, '\'')
		default:
			return "", driver.ErrSkip
		}

		if len(buf)+4 > mc.maxPacketAllowed {
			return "", driver.ErrSkip
		}
	}
	if argPos != len(args) {
		return "", driver.ErrSkip
	}
	return string(buf), nil
}

func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	if len(args) != 0 {
		if !mc.cfg.InterpolateParams {
			return nil, driver.ErrSkip
		}
		// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
		prepared, err := mc.interpolateParams(query, args)
		if err != nil {
			return nil, err
		}
		query = prepared
		args = nil
	}
	mc.affectedRows = 0
	mc.insertId = 0

	err := mc.exec(query)
	if err == nil {
		return &mysqlResult{
			affectedRows: int64(mc.affectedRows),
			insertId:     int64(mc.insertId),
		}, err
	}
	return nil, err
}

// Internal function to execute commands
func (mc *mysqlConn) exec(query string) error {
	// Send command
	err := mc.writeCommandPacketStr(comQuery, query)
	if err != nil {
		return err
	}

	// Read Result
	resLen, err := mc.readResultSetHeaderPacket()
	if err == nil && resLen > 0 {
		if err = mc.readUntilEOF(); err != nil {
			return err
		}

		err = mc.readUntilEOF()
	}

	return err
}

func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
	if mc.netConn == nil {
		errLog.Print(ErrInvalidConn)
		return nil, driver.ErrBadConn
	}
	if len(args) != 0 {
		if !mc.cfg.InterpolateParams {
			return nil, driver.ErrSkip
		}
		// try client-side prepare to reduce roundtrip
		prepared, err := mc.interpolateParams(query, args)
		if err != nil {
			return nil, err
		}
		query = prepared
		args = nil
	}
	// Send command
	err := mc.writeCommandPacketStr(comQuery, query)
	if err == nil {
		// Read Result
		var resLen int
		resLen, err = mc.readResultSetHeaderPacket()
		if err == nil {
			rows := new(textRows)
			rows.mc = mc

			if resLen == 0 {
				// no columns, no more data
				return emptyRows{}, nil
			}
			// Columns
			rows.columns, err = mc.readColumns(resLen)
			return rows, err
		}
	}
	return nil, err
}

// Gets the value of the given MySQL System Variable
// The returned byte slice is only valid until the next read
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
	// Send command
	if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
		return nil, err
	}

	// Read Result
	resLen, err := mc.readResultSetHeaderPacket()
	if err == nil {
		rows := new(textRows)
		rows.mc = mc
		rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}

		if resLen > 0 {
			// Columns
			if err := mc.readUntilEOF(); err != nil {
				return nil, err
			}
		}

		dest := make([]driver.Value, resLen)
		if err = rows.readRow(dest); err == nil {
			return dest[0].([]byte), mc.readUntilEOF()
		}
	}
	return nil, err
}
<|endoftext|>