<commit_before>/*
* Copyright (c) 2014 Mark Samman <https://github.com/marksamman/gotorrent>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
 */

package main

import (
	"bytes"
	"crypto/sha1"
	"fmt"
	"net/http"
	"net/url"
	"os"
)

type Torrent struct {
	Data      map[string]interface{}
	InfoHash  []byte
	Peers     []Peer
	Handshake []byte
}

func (torrent *Torrent) open(filename string) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	torrent.Data = BencodeDecode(file)
	file.Close()

	hasher := sha1.New()
	hasher.Write(BencodeEncode(torrent.getInfo()))
	torrent.InfoHash = hasher.Sum(nil)

	var buffer bytes.Buffer
	buffer.WriteByte(19) // length of the string "BitTorrent Protocol"
	buffer.WriteString("BitTorrent Protocol")
	buffer.WriteString("\x00\x00\x00\x00\x00\x00\x00\x00") // reserved
	buffer.Write(torrent.InfoHash)
	buffer.Write(client.PeerId)
	torrent.Handshake = buffer.Bytes()

	return nil
}

func (torrent *Torrent) sendTrackerRequest(params map[string]string) (*http.Response, error) {
	var paramBuf bytes.Buffer
	for k, v := range params {
		paramBuf.WriteString(k)
		paramBuf.WriteByte('=')
		paramBuf.WriteString(v)
		paramBuf.WriteByte('&')
	}
	return http.Get(
		fmt.Sprintf("%s?%speer_id=%s&info_hash=%s&left=%d",
			torrent.getAnnounceURL(), paramBuf.String(),
			url.QueryEscape(string(client.PeerId)),
			url.QueryEscape(string(torrent.InfoHash)),
			torrent.getTotalSize()-torrent.getDownloadedSize()))
}

func (torrent *Torrent) getAnnounceURL() string {
	return torrent.Data["announce"].(string)
}

func (torrent *Torrent) getName() string {
	return torrent.getInfo()["name"].(string)
}

func (torrent *Torrent) getInfo() map[string]interface{} {
	return torrent.Data["info"].(map[string]interface{})
}

func (torrent *Torrent) getComment() string {
	return torrent.Data["comment"].(string)
}

func (torrent *Torrent) getDownloadedSize() int {
	return 0
}

func (torrent *Torrent) getTotalSize() int {
	size := 0
	for _, v := range torrent.getInfo()["files"].([]interface{}) {
		elem := v.(map[string]interface{})
		size += elem["length"].(int)
	}
	return size
}

func (torrent *Torrent) parsePeers(peers interface{}) {
	switch peers.(type) {
	case string:
		peers := peers.(string)
		for pos := 0; pos < len(peers); pos += 6 {
			// 4 bytes ip
			var ipv4_addr uint32
			ipv4_addr = uint32(peers[pos])<<24 | uint32(peers[pos+1])<<16 | uint32(peers[pos+2])<<8 | uint32(peers[pos+3])

			// 2 bytes port
			var port uint16
			port = uint16(peers[pos+4])<<8 | uint16(peers[pos+5])

			torrent.Peers = append(torrent.Peers, Peer{ipv4_addr, port, torrent})
		}
	case map[string]interface{}:
		// TODO: dict model
		// peer_id: string
		// ip: hexed ipv6, dotted quad ipv4, dns name string
		// port: int
	}
}
<commit_msg>Fix torrent.getTotalSize() for single file torrents<commit_after>/*
* Copyright (c) 2014 Mark Samman <https://github.com/marksamman/gotorrent>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
 */

package main

import (
	"bytes"
	"crypto/sha1"
	"fmt"
	"net/http"
	"net/url"
	"os"
)

type Torrent struct {
	Data      map[string]interface{}
	InfoHash  []byte
	Peers     []Peer
	Handshake []byte
}

func (torrent *Torrent) open(filename string) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	torrent.Data = BencodeDecode(file)
	file.Close()

	hasher := sha1.New()
	hasher.Write(BencodeEncode(torrent.getInfo()))
	torrent.InfoHash = hasher.Sum(nil)

	var buffer bytes.Buffer
	buffer.WriteByte(19) // length of the string "BitTorrent Protocol"
	buffer.WriteString("BitTorrent Protocol")
	buffer.WriteString("\x00\x00\x00\x00\x00\x00\x00\x00") // reserved
	buffer.Write(torrent.InfoHash)
	buffer.Write(client.PeerId)
	torrent.Handshake = buffer.Bytes()

	return nil
}

func (torrent *Torrent) sendTrackerRequest(params map[string]string) (*http.Response, error) {
	var paramBuf bytes.Buffer
	for k, v := range params {
		paramBuf.WriteString(k)
		paramBuf.WriteByte('=')
		paramBuf.WriteString(v)
		paramBuf.WriteByte('&')
	}
	return http.Get(
		fmt.Sprintf("%s?%speer_id=%s&info_hash=%s&left=%d",
			torrent.getAnnounceURL(), paramBuf.String(),
			url.QueryEscape(string(client.PeerId)),
			url.QueryEscape(string(torrent.InfoHash)),
			torrent.getTotalSize()-torrent.getDownloadedSize()))
}

func (torrent *Torrent) getAnnounceURL() string {
	return torrent.Data["announce"].(string)
}

func (torrent *Torrent) getName() string {
	return torrent.getInfo()["name"].(string)
}

func (torrent *Torrent) getInfo() map[string]interface{} {
	return torrent.Data["info"].(map[string]interface{})
}

func (torrent *Torrent) getComment() string {
	return torrent.Data["comment"].(string)
}

func (torrent *Torrent) getDownloadedSize() int {
	return 0
}

func (torrent *Torrent) getTotalSize() int {
	info := torrent.getInfo()
	length, exists := info["length"]
	if exists {
		// Single file
		return length.(int)
	} else {
		// Multiple files
		size := 0
		for _, v := range torrent.getInfo()["files"].([]interface{}) {
			elem := v.(map[string]interface{})
			size += elem["length"].(int)
		}
		return size
	}
}

func (torrent *Torrent) parsePeers(peers interface{}) {
	switch peers.(type) {
	case string:
		peers := peers.(string)
		for pos := 0; pos < len(peers); pos += 6 {
			// 4 bytes ip
			var ipv4_addr uint32
			ipv4_addr = uint32(peers[pos])<<24 | uint32(peers[pos+1])<<16 | uint32(peers[pos+2])<<8 | uint32(peers[pos+3])

			// 2 bytes port
			var port uint16
			port = uint16(peers[pos+4])<<8 | uint16(peers[pos+5])

			torrent.Peers = append(torrent.Peers, Peer{ipv4_addr, port, torrent})
		}
	case map[string]interface{}:
		// TODO: dict model
		// peer_id: string
		// ip: hexed ipv6, dotted quad ipv4, dns name string
		// port: int
	}
}
<|endoftext|>


<commit_before>package nexus

import (
	"fmt"
	"strings"
	"time"

	"github.com/hanjos/nexus/util"
)

// Artifact is a Maven coordinate to a single artifact, plus the repository where it came from.
type Artifact struct {
	GroupID      string // e.g. org.springframework
	ArtifactID   string // e.g. spring-core
	Version      string // e.g. 4.1.3.RELEASE
	Classifier   string // e.g. sources, javadoc, <the empty string>...
	Extension    string // e.g. jar
	RepositoryID string // e.g. releases
}

// String implements the fmt.Stringer interface, as per Maven docs (http://maven.apache.org/pom.html#Maven_Coordinates).
func (a Artifact) String() string {
	var parts = []string{a.GroupID, a.ArtifactID, a.Extension}

	if a.Classifier != "" {
		parts = append(parts, a.Classifier)
	}

	return strings.Join(append(parts, a.Version), ":") + "@" + a.RepositoryID
}

// used for the artifact set.
func (a *Artifact) hash() string {
	return a.GroupID + ":" + a.ArtifactID + ":" + a.Version + ":" +
		a.Extension + ":" + a.Classifier + "@" + a.RepositoryID
}

// since Go doesn't have a built-in set implementation, a make-shift one follows, using a map for the heavy duty.
// Artifact's hash method is used to distinguish between artifacts; there's no Java-like Equals contract to follow.
type artifactSet struct {
	// piles up the artifacts
	data []*Artifact

	// the set behavior
	hashMap map[string]bool
}

// creates and initializes a new set of artifacts.
func newArtifactSet() *artifactSet {
	return &artifactSet{
		data:    []*Artifact{},
		hashMap: make(map[string]bool),
	}
}

// adds a bunch of artifacts to this set.
func (set *artifactSet) add(artifacts []*Artifact) {
	for _, artifact := range artifacts {
		hash := artifact.hash()
		_, contains := set.hashMap[hash]

		set.hashMap[hash] = true
		if !contains {
			set.data = append(set.data, artifact)
		}
	}
}

// ArtifactInfo holds extra information about the given artifact.
type ArtifactInfo struct {
	*Artifact

	Uploader    string
	Uploaded    time.Time
	LastChanged time.Time
	Sha1        string
	Size        util.ByteSize
	MimeType    string
	URL         string
}

// String implements the fmt.Stringer interface.
func (info ArtifactInfo) String() string {
	return fmt.Sprintf("%v [SHA1 %v, Mime-Type %v, %v]", info.Artifact, info.Sha1, info.MimeType, info.Size)
}

// A make-shift map-reducer, distributes an artifact search in multiple goroutines. Expects an array of strings and a
// query function. There will be one goroutine for every element of data. Each goroutine will call query with its
// respective datum.
func concurrentArtifactSearch(data []string, query func(string) ([]*Artifact, error)) ([]*Artifact, error) {
	// search for the artifacts in each element of data
	artifacts := make(chan []*Artifact)
	errors := make(chan error)
	for _, datum := range data {
		go func(datum string) {
			a, err := query(datum)
			if err != nil {
				errors <- err
				return
			}

			artifacts <- a
		}(datum)
	}

	// pile 'em up
	result := newArtifactSet()
	for i := 0; i < len(data); i++ {
		select {
		case a := <-artifacts:
			result.add(a)
		case err := <-errors:
			return nil, err
		}
	}

	return result.data, nil
}
<commit_msg>Using struct{} instead of bool for the artifact set.<commit_after>package nexus

import (
	"fmt"
	"strings"
	"time"

	"github.com/hanjos/nexus/util"
)

// Artifact is a Maven coordinate to a single artifact, plus the repository where it came from.
type Artifact struct {
	GroupID      string // e.g. org.springframework
	ArtifactID   string // e.g. spring-core
	Version      string // e.g. 4.1.3.RELEASE
	Classifier   string // e.g. sources, javadoc, <the empty string>...
	Extension    string // e.g. jar
	RepositoryID string // e.g. releases
}

// String implements the fmt.Stringer interface, as per Maven docs (http://maven.apache.org/pom.html#Maven_Coordinates).
func (a Artifact) String() string {
	var parts = []string{a.GroupID, a.ArtifactID, a.Extension}

	if a.Classifier != "" {
		parts = append(parts, a.Classifier)
	}

	return strings.Join(append(parts, a.Version), ":") + "@" + a.RepositoryID
}

// used for the artifact set.
func (a *Artifact) hash() string {
	return a.GroupID + ":" + a.ArtifactID + ":" + a.Version + ":" +
		a.Extension + ":" + a.Classifier + "@" + a.RepositoryID
}

// a zero-byte placeholder. No point in wasting bytes unnecessarily :)
var empty struct{}

// since Go doesn't have a built-in set implementation, a make-shift one follows, using a map for the heavy duty.
// Artifact's hash method is used to distinguish between artifacts, since there's no Java-like Equals contract to follow.
type artifactSet struct {
	// piles up the artifacts
	data []*Artifact

	// the set behavior
	hashMap map[string]struct{}
}

// creates and initializes a new set of artifacts.
func newArtifactSet() *artifactSet {
	return &artifactSet{
		data:    []*Artifact{},
		hashMap: make(map[string]struct{}),
	}
}

// adds a bunch of artifacts to this set.
func (set *artifactSet) add(artifacts []*Artifact) {
	for _, artifact := range artifacts {
		hash := artifact.hash()
		_, contains := set.hashMap[hash]

		set.hashMap[hash] = empty
		if !contains {
			set.data = append(set.data, artifact)
		}
	}
}

// ArtifactInfo holds extra information about the given artifact.
type ArtifactInfo struct {
	*Artifact

	Uploader    string
	Uploaded    time.Time
	LastChanged time.Time
	Sha1        string
	Size        util.ByteSize
	MimeType    string
	URL         string
}

// String implements the fmt.Stringer interface.
func (info ArtifactInfo) String() string {
	return fmt.Sprintf("%v [SHA1 %v, Mime-Type %v, %v]", info.Artifact, info.Sha1, info.MimeType, info.Size)
}

// A make-shift map-reducer, distributes an artifact search in multiple goroutines. Expects an array of strings and a
// query function. There will be one goroutine for every element of data. Each goroutine will call query with its
// respective datum.
func concurrentArtifactSearch(data []string, query func(string) ([]*Artifact, error)) ([]*Artifact, error) {
	// search for the artifacts in each element of data
	artifacts := make(chan []*Artifact)
	errors := make(chan error)
	for _, datum := range data {
		go func(datum string) {
			a, err := query(datum)
			if err != nil {
				errors <- err
				return
			}

			artifacts <- a
		}(datum)
	}

	// pile 'em up
	result := newArtifactSet()
	for i := 0; i < len(data); i++ {
		select {
		case a := <-artifacts:
			result.add(a)
		case err := <-errors:
			return nil, err
		}
	}

	return result.data, nil
}
<|endoftext|>


<commit_before>package models

type Direction int

const (
	Up Direction = iota
	Down
	Left
	Right
)

type Coordinate struct {
	X int `json:"X"`
	Y int `json:"Y"`
}

type Gopher struct {
	// Current direction
	Direction Direction
	X, Y      int
	Path      []Coordinate
	Score     int
	Paths     chan map[string]GopherInfo
	Close     chan bool
}

func NewGopher() *Gopher {
	return &Gopher{
		Paths: make(chan map[string]GopherInfo),
		Close: make(chan bool),
	}
}
<commit_msg>Make Paths a gopherinfo map, add lcose, notify<commit_after>package models

type Direction int

const (
	Up Direction = iota
	Down
	Left
	Right
)

type Coordinate struct {
	X int `json:"X"`
	Y int `json:"Y"`
}

type Gopher struct {
	// Current direction
	Direction Direction
	X, Y      int
	Path      []Coordinate
	Score     int
	Paths     chan map[string]GopherInfo
	Notify    chan struct{}
	Close     chan bool
}

func NewGopher() *Gopher {
	return &Gopher{
		Paths:  make(chan map[string]GopherInfo),
		Close:  make(chan bool),
		Notify: make(chan struct{}),
	}
}
<|endoftext|>


<commit_before>package transloadit

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

type Assembly struct {
	client *Client
	// Notify url to send a request to once the assembly finishes.
	// See https://transloadit.com/docs#notifications.
	NotifyUrl string
	// Optional template id to use instead of adding steps.
	TemplateId string
	// Wait until the assembly completes (or is canceled).
	Blocking bool
	steps    map[string]map[string]interface{}
	readers  []*upload
}

type upload struct {
	Field  string
	Name   string
	Reader io.ReadCloser
}

type AssemblyReplay struct {
	assemblyId string
	client     *Client
	// Notify url to send a request to once the assembly finishes.
	// See https://transloadit.com/docs#notifications.
	NotifyUrl string
	// Wait until the assembly completes (or is canceled).
	Blocking bool
	// Reparse the template when replaying. Useful if the template has changed
	// since the orignal assembly was created.
	ReparseTemplate bool
	steps           map[string]map[string]interface{}
}

type AssemblyList struct {
	Assemblies []*AssemblyListItem `json:"items"`
	Count      int                 `json:"count"`
}

type AssemblyListItem struct {
	AssemblyId        string    `json:"id"`
	AccountId         string    `json:"account_id"`
	TemplateId        string    `json:"template_id"`
	Instance          string    `json:"instance"`
	NotifyUrl         string    `json:"notify_url"`
	RedirectUrl       string    `json:"redirect_url"`
	ExecutionDuration float32   `json:"execution_duration"`
	ExecutionStart    time.Time `json:"execution_start"`
	Created           time.Time `json:"created"`
	Ok                string    `json:"ok"`
	Error             string    `json:"error"`
	Files             string    `json:"files"`
}

type AssemblyInfo struct {
	AssemblyId             string                 `json:"assembly_id"`
	ParentId               string                 `json:"parent_id"`
	AssemblyUrl            string                 `json:"assembly_url"`
	AssemblySslUrl         string                 `json:"assembly_ssl_url"`
	BytesReceived          int                    `json:"bytes_received"`
	BytesExpected          int                    `json:"bytes_expected"`
	ClientAgent            string                 `json:"client_agent"`
	ClientIp               string                 `json:"client_ip"`
	ClientReferer          string                 `json:"client_referer"`
	StartDate              string                 `json:"start_date"`
	IsInfinite             bool                   `json:"is_infinite"`
	HasDupeJobs            bool                   `json:"has_dupe_jobs"`
	UploadDuration         float32                `json:"upload_duration"`
	NotifyUrl              string                 `json:"notify_url"`
	NotifyStart            string                 `json:"notify_start"`
	NotifyStatus           string                 `json:"notify_status"`
	NotifyDuation          float32                `json:"notify_duration"`
	LastJobCompleted       string                 `json:"last_job_completed"`
	ExecutionDuration      float32                `json:"execution_duration"`
	ExecutionStart         string                 `json:"execution_start"`
	Created                string                 `json:"created"`
	Ok                     string                 `json:"ok"`
	Message                string                 `json:"message"`
	Files                  string                 `json:"files"`
	Fields                 map[string]interface{} `json:"fields"`
	BytesUsage             int                    `json:"bytes_usage"`
	FilesToStoreOnS3       int                    `json:"files_to_store_on_s3"`
	QueuedFilesToStoreOnS3 int                    `json:"queued_files_to_store_on_s3"`
	ExecutingJobs          []string               `json:"executing_jobs"`
	StartedJobs            []string               `json:"started_jobs"`
	ParentAssemblyStatus   *AssemblyInfo          `json:"parent_assembly_status"`
	Uploads                []*FileInfo            `json:"uploads"`
	Results                map[string][]*FileInfo `json:"results"`
	Params                 string                 `json:"params"`
	Error                  string                 `json:"error"`
}

type FileInfo struct {
	Id               string                 `json:"id"`
	Name             string                 `json:"name"`
	Basename         string                 `json:"basename"`
	Ext              string                 `json:"ext"`
	Size             int                    `json:"size"`
	Mime             string                 `json:"mime"`
	Type             string                 `json:"type"`
	Field            string                 `json:"field"`
	Md5Hash          string                 `json:"md5hash"`
	OriginalMd5Hash  string                 `json:"original_md5hash"`
	OriginalId       string                 `json:"original_id"`
	OriginalBasename string                 `json:"original_basename"`
	Url              string                 `json:"url"`
	SslUrl           string                 `json:"ssl_url"`
	Meta             map[string]interface{} `json:"meta"`
}

// Create a new assembly instance which can be executed later.
func (client *Client) CreateAssembly() *Assembly {
	return &Assembly{
		client:  client,
		steps:   make(map[string]map[string]interface{}),
		readers: make([]*upload, 0),
	}
}

// Add another reader to upload later.
func (assembly *Assembly) AddReader(field, name string, reader io.ReadCloser) {
	assembly.readers = append(assembly.readers, &upload{
		Field:  field,
		Name:   name,
		Reader: reader,
	})
}

// Add another file to upload later.
func (assembly *Assembly) AddFile(field, name string) error {
	file, err := os.Open(name)
	if err != nil {
		return err
	}

	assembly.AddReader(field, name, file)
	return nil
}

// Add a step to the assembly.
func (assembly *Assembly) AddStep(name string, details map[string]interface{}) {
	assembly.steps[name] = details
}

// Start the assembly and upload all files.
// When an error is returned you should also check AssemblyInfo.Error for more
// information about the error. This happens when there is an error returned by
// the Transloadit API:
//  info, err := assembly.Upload()
//  if err != nil {
//  	if info != nil && info.Error != "" {
//  		// See info.Error
//  	}
//  	panic(err)
//  }
func (assembly *Assembly) Upload() (*AssemblyInfo, error) {
	req, err := assembly.makeRequest()
	if err != nil {
		return nil, fmt.Errorf("failed to create assembly request: %s", err)
	}

	var info AssemblyInfo
	// TODO: add context.Context
	if err = assembly.client.doRequest(req, &info); err != nil {
		return nil, err
	}

	if info.Error != "" {
		return &info, fmt.Errorf("failed to create assembly: %s", info.Error)
	}

	if !assembly.Blocking {
		return &info, err
	}

	watcher := assembly.client.WaitForAssembly(info.AssemblyUrl)

	select {
	case res := <-watcher.Response:
		// Assembly completed
		return res, nil
	case err := <-watcher.Error:
		// Error appeared
		return nil, err
	}
}

func (assembly *Assembly) makeRequest() (*http.Request, error) {
	// Get bored instance to upload files to
	bored, err := assembly.client.getBoredInstance()
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// TODO: test with huge files
	url := "http://api2-" + bored + "/assemblies"
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Add files to upload
	for _, reader := range assembly.readers {
		part, err := writer.CreateFormFile(reader.Field, reader.Name)
		if err != nil {
			return nil, fmt.Errorf("unable to create upload request: %s", err)
		}

		_, err = io.Copy(part, reader.Reader)
		if err != nil {
			return nil, fmt.Errorf("unable to create upload request: %s", err)
		}

		err = reader.Reader.Close()
		if err != nil {
			return nil, fmt.Errorf("unable to create upload request: %s", err)
		}
	}

	options := make(map[string]interface{})

	if len(assembly.steps) != 0 {
		options["steps"] = assembly.steps
	}

	if assembly.TemplateId != "" {
		options["template_id"] = assembly.TemplateId
	}

	if assembly.NotifyUrl != "" {
		options["notify_url"] = assembly.NotifyUrl
	}

	params, signature, err := assembly.client.sign(options)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// Add additional keys and values
	err = writer.WriteField("params", params)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}
	err = writer.WriteField("signature", signature)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// Close multipart writer
	err = writer.Close()
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// Create HTTP request
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	return req, nil
}

// Get information about an assembly using its url.
func (client *Client) GetAssembly(assemblyUrl string) (*AssemblyInfo, error) {
	var info AssemblyInfo
	err := client.request("GET", assemblyUrl, nil, &info)

	return &info, err
}

// Cancel an assembly using its URL. This function will return the updated
// information about the assembly after the cancellation.
func (client *Client) CancelAssembly(assemblyUrl string) (*AssemblyInfo, error) {
	var info AssemblyInfo
	err := client.request("DELETE", assemblyUrl, nil, &info)

	return &info, err
}

// Create a new AssemblyReplay instance.
func (client *Client) ReplayAssembly(assemblyId string) *AssemblyReplay {
	return &AssemblyReplay{
		client:     client,
		steps:      make(map[string]map[string]interface{}),
		assemblyId: assemblyId,
	}
}

// Add a step to override the original ones.
func (assembly *AssemblyReplay) AddStep(name string, details map[string]interface{}) {
	assembly.steps[name] = details
}

// Start the assembly replay.
func (assembly *AssemblyReplay) Start() (*AssemblyInfo, error) {
	options := map[string]interface{}{
		"steps": assembly.steps,
	}

	if assembly.ReparseTemplate {
		options["reparse_template"] = 1
	}

	if assembly.NotifyUrl != "" {
		options["notify_url"] = assembly.NotifyUrl
	}

	var info AssemblyInfo
	err := assembly.client.request("POST", "assemblies/"+assembly.assemblyId+"/replay", options, &info)
	if err != nil {
		return nil, err
	}

	if info.Error != "" {
		return &info, fmt.Errorf("failed to start assembly replay: %s", info.Error)
	}

	if !assembly.Blocking {
		return &info, nil
	}

	// Assembly replay response doesn't contains assembly url
	assemblyUrl := assembly.client.config.Endpoint + "/assemblies/" + info.AssemblyId
	watcher := assembly.client.WaitForAssembly(assemblyUrl)

	select {
	case res := <-watcher.Response:
		// Assembly completed
		return res, nil
	case err := <-watcher.Error:
		// Error appeared
		return nil, err
	}
}

// List all assemblies matching the criterias.
func (client *Client) ListAssemblies(options *ListOptions) (*AssemblyList, error) {
	var assemblies AssemblyList
	err := client.listRequest("assemblies", options, &assemblies)

	return &assemblies, err
}
<commit_msg>Ignore invalid date respones<commit_after>package transloadit

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

type Assembly struct {
	client *Client
	// Notify url to send a request to once the assembly finishes.
	// See https://transloadit.com/docs#notifications.
	NotifyUrl string
	// Optional template id to use instead of adding steps.
	TemplateId string
	// Wait until the assembly completes (or is canceled).
	Blocking bool
	steps    map[string]map[string]interface{}
	readers  []*upload
}

type upload struct {
	Field  string
	Name   string
	Reader io.ReadCloser
}

type AssemblyReplay struct {
	assemblyId string
	client     *Client
	// Notify url to send a request to once the assembly finishes.
	// See https://transloadit.com/docs#notifications.
	NotifyUrl string
	// Wait until the assembly completes (or is canceled).
	Blocking bool
	// Reparse the template when replaying. Useful if the template has changed
	// since the orignal assembly was created.
	ReparseTemplate bool
	steps           map[string]map[string]interface{}
}

type AssemblyList struct {
	Assemblies []*AssemblyListItem `json:"items"`
	Count      int                 `json:"count"`
}

type AssemblyListItem struct {
	AssemblyId        string    `json:"id"`
	AccountId         string    `json:"account_id"`
	TemplateId        string    `json:"template_id"`
	Instance          string    `json:"instance"`
	NotifyUrl         string    `json:"notify_url"`
	RedirectUrl       string    `json:"redirect_url"`
	ExecutionDuration float32   `json:"execution_duration"`
	ExecutionStart    *Time     `json:"execution_start"`
	Created           time.Time `json:"created"`
	Ok                string    `json:"ok"`
	Error             string    `json:"error"`
	Files             string    `json:"files"`
}

type Time struct {
	*time.Time
}

func (t *Time) UnmarshalJSON(b []byte) error {
	// The error is ignored intentionally, as the date returned by Transloadit
	// may be invalid, e.g. "0000-00-00". This invalid date value actually
	// represents the absence of a date and we therefore make t.Time as nil-pointer.
	_ = json.Unmarshal(b, &t.Time)
	return nil
}

type AssemblyInfo struct {
	AssemblyId             string                 `json:"assembly_id"`
	ParentId               string                 `json:"parent_id"`
	AssemblyUrl            string                 `json:"assembly_url"`
	AssemblySslUrl         string                 `json:"assembly_ssl_url"`
	BytesReceived          int                    `json:"bytes_received"`
	BytesExpected          int                    `json:"bytes_expected"`
	ClientAgent            string                 `json:"client_agent"`
	ClientIp               string                 `json:"client_ip"`
	ClientReferer          string                 `json:"client_referer"`
	StartDate              string                 `json:"start_date"`
	IsInfinite             bool                   `json:"is_infinite"`
	HasDupeJobs            bool                   `json:"has_dupe_jobs"`
	UploadDuration         float32                `json:"upload_duration"`
	NotifyUrl              string                 `json:"notify_url"`
	NotifyStart            string                 `json:"notify_start"`
	NotifyStatus           string                 `json:"notify_status"`
	NotifyDuation          float32                `json:"notify_duration"`
	LastJobCompleted       string                 `json:"last_job_completed"`
	ExecutionDuration      float32                `json:"execution_duration"`
	ExecutionStart         string                 `json:"execution_start"`
	Created                string                 `json:"created"`
	Ok                     string                 `json:"ok"`
	Message                string                 `json:"message"`
	Files                  string                 `json:"files"`
	Fields                 map[string]interface{} `json:"fields"`
	BytesUsage             int                    `json:"bytes_usage"`
	FilesToStoreOnS3       int                    `json:"files_to_store_on_s3"`
	QueuedFilesToStoreOnS3 int                    `json:"queued_files_to_store_on_s3"`
	ExecutingJobs          []string               `json:"executing_jobs"`
	StartedJobs            []string               `json:"started_jobs"`
	ParentAssemblyStatus   *AssemblyInfo          `json:"parent_assembly_status"`
	Uploads                []*FileInfo            `json:"uploads"`
	Results                map[string][]*FileInfo `json:"results"`
	Params                 string                 `json:"params"`
	Error                  string                 `json:"error"`
}

type FileInfo struct {
	Id               string                 `json:"id"`
	Name             string                 `json:"name"`
	Basename         string                 `json:"basename"`
	Ext              string                 `json:"ext"`
	Size             int                    `json:"size"`
	Mime             string                 `json:"mime"`
	Type             string                 `json:"type"`
	Field            string                 `json:"field"`
	Md5Hash          string                 `json:"md5hash"`
	OriginalMd5Hash  string                 `json:"original_md5hash"`
	OriginalId       string                 `json:"original_id"`
	OriginalBasename string                 `json:"original_basename"`
	Url              string                 `json:"url"`
	SslUrl           string                 `json:"ssl_url"`
	Meta             map[string]interface{} `json:"meta"`
}

// Create a new assembly instance which can be executed later.
func (client *Client) CreateAssembly() *Assembly {
	return &Assembly{
		client:  client,
		steps:   make(map[string]map[string]interface{}),
		readers: make([]*upload, 0),
	}
}

// Add another reader to upload later.
func (assembly *Assembly) AddReader(field, name string, reader io.ReadCloser) {
	assembly.readers = append(assembly.readers, &upload{
		Field:  field,
		Name:   name,
		Reader: reader,
	})
}

// Add another file to upload later.
func (assembly *Assembly) AddFile(field, name string) error {
	file, err := os.Open(name)
	if err != nil {
		return err
	}

	assembly.AddReader(field, name, file)
	return nil
}

// Add a step to the assembly.
func (assembly *Assembly) AddStep(name string, details map[string]interface{}) {
	assembly.steps[name] = details
}

// Start the assembly and upload all files.
// When an error is returned you should also check AssemblyInfo.Error for more
// information about the error. This happens when there is an error returned by
// the Transloadit API:
//  info, err := assembly.Upload()
//  if err != nil {
//  	if info != nil && info.Error != "" {
//  		// See info.Error
//  	}
//  	panic(err)
//  }
func (assembly *Assembly) Upload() (*AssemblyInfo, error) {
	req, err := assembly.makeRequest()
	if err != nil {
		return nil, fmt.Errorf("failed to create assembly request: %s", err)
	}

	var info AssemblyInfo
	// TODO: add context.Context
	if err = assembly.client.doRequest(req, &info); err != nil {
		return nil, err
	}

	if info.Error != "" {
		return &info, fmt.Errorf("failed to create assembly: %s", info.Error)
	}

	if !assembly.Blocking {
		return &info, err
	}

	watcher := assembly.client.WaitForAssembly(info.AssemblyUrl)

	select {
	case res := <-watcher.Response:
		// Assembly completed
		return res, nil
	case err := <-watcher.Error:
		// Error appeared
		return nil, err
	}
}

func (assembly *Assembly) makeRequest() (*http.Request, error) {
	// Get bored instance to upload files to
	bored, err := assembly.client.getBoredInstance()
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// TODO: test with huge files
	url := "http://api2-" + bored + "/assemblies"
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Add files to upload
	for _, reader := range assembly.readers {
		part, err := writer.CreateFormFile(reader.Field, reader.Name)
		if err != nil {
			return nil, fmt.Errorf("unable to create upload request: %s", err)
		}

		_, err = io.Copy(part, reader.Reader)
		if err != nil {
			return nil, fmt.Errorf("unable to create upload request: %s", err)
		}

		err = reader.Reader.Close()
		if err != nil {
			return nil, fmt.Errorf("unable to create upload request: %s", err)
		}
	}

	options := make(map[string]interface{})

	if len(assembly.steps) != 0 {
		options["steps"] = assembly.steps
	}

	if assembly.TemplateId != "" {
		options["template_id"] = assembly.TemplateId
	}

	if assembly.NotifyUrl != "" {
		options["notify_url"] = assembly.NotifyUrl
	}

	params, signature, err := assembly.client.sign(options)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// Add additional keys and values
	err = writer.WriteField("params", params)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}
	err = writer.WriteField("signature", signature)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// Close multipart writer
	err = writer.Close()
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	// Create HTTP request
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return nil, fmt.Errorf("unable to create upload request: %s", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	return req, nil
}

// Get information about an assembly using its url.
func (client *Client) GetAssembly(assemblyUrl string) (*AssemblyInfo, error) {
	var info AssemblyInfo
	err := client.request("GET", assemblyUrl, nil, &info)

	return &info, err
}

// Cancel an assembly using its URL. This function will return the updated
// information about the assembly after the cancellation.
func (client *Client) CancelAssembly(assemblyUrl string) (*AssemblyInfo, error) {
	var info AssemblyInfo
	err := client.request("DELETE", assemblyUrl, nil, &info)

	return &info, err
}

// Create a new AssemblyReplay instance.
func (client *Client) ReplayAssembly(assemblyId string) *AssemblyReplay {
	return &AssemblyReplay{
		client:     client,
		steps:      make(map[string]map[string]interface{}),
		assemblyId: assemblyId,
	}
}

// Add a step to override the original ones.
func (assembly *AssemblyReplay) AddStep(name string, details map[string]interface{}) {
	assembly.steps[name] = details
}

// Start the assembly replay.
func (assembly *AssemblyReplay) Start() (*AssemblyInfo, error) {
	options := map[string]interface{}{
		"steps": assembly.steps,
	}

	if assembly.ReparseTemplate {
		options["reparse_template"] = 1
	}

	if assembly.NotifyUrl != "" {
		options["notify_url"] = assembly.NotifyUrl
	}

	var info AssemblyInfo
	err := assembly.client.request("POST", "assemblies/"+assembly.assemblyId+"/replay", options, &info)
	if err != nil {
		return nil, err
	}

	if info.Error != "" {
		return &info, fmt.Errorf("failed to start assembly replay: %s", info.Error)
	}

	if !assembly.Blocking {
		return &info, nil
	}

	// Assembly replay response doesn't contains assembly url
	assemblyUrl := assembly.client.config.Endpoint + "/assemblies/" + info.AssemblyId
	watcher := assembly.client.WaitForAssembly(assemblyUrl)

	select {
	case res := <-watcher.Response:
		// Assembly completed
		return res, nil
	case err := <-watcher.Error:
		// Error appeared
		return nil, err
	}
}

// List all assemblies matching the criterias.
func (client *Client) ListAssemblies(options *ListOptions) (*AssemblyList, error) {
	var assemblies AssemblyList
	err := client.listRequest("assemblies", options, &assemblies)

	return &assemblies, err
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"path"
	"sort"
	"strings"
	"time"
)

// Context encapsulates the context of a client's connection to an AWS service.
type Context struct {
	Service     string
	Region      string
	Credentials Credentials
}

func (c *Context) sign(req *http.Request) error {
	req.Header.Set("host", req.Host) // host header must be included as a signed header
	payloadHash, err := payloadHash(req)
	if err != nil {
		return err
	}
	req.Header.Set("x-amz-content-sha256", payloadHash)

	t := requestTime(req)
	creq, err := canonicalRequest(req, payloadHash)
	if err != nil {
		return err
	}
	sts := c.stringToSign(t, creq)
	signature := c.signature(t, sts)
	auth := c.authorization(req.Header, t, signature)
	req.Header.Set("Authorization", auth)
	if s := c.Credentials.SecurityToken(); s != "" {
		req.Header.Set("X-Amz-Security-Token", s)
	}
	return nil
}

func (c *Context) stringToSign(t time.Time, creq string) string {
	w := new(bytes.Buffer)
	fmt.Fprint(w, "AWS4-HMAC-SHA256\n")
	fmt.Fprintf(w, "%s\n", t.Format(iso8601BasicFormat))
	fmt.Fprintf(w, "%s\n", c.credentialScope(t))
	fmt.Fprintf(w, "%s", hash(creq))
	return w.String()
}

func (c *Context) credentialScope(t time.Time) string {
	return fmt.Sprintf(
		"%s/%s/%s/aws4_request",
		t.Format(iso8601BasicFormatShort),
		c.Region,
		c.Service,
	)
}

func (c *Context) signature(t time.Time, sts string) string {
	h := mac(c.derivedKey(t), []byte(sts))
	return fmt.Sprintf("%x", h)
}

func (c *Context) derivedKey(t time.Time) []byte {
	h := mac(
		[]byte("AWS4"+c.Credentials.SecretAccessKey()),
		[]byte(t.Format(iso8601BasicFormatShort)),
	)
	h = mac(h, []byte(c.Region))
	h = mac(h, []byte(c.Service))
	h = mac(h, []byte("aws4_request"))
	return h
}

func (c *Context) authorization(header http.Header, t time.Time, signature string) string {
	w := new(bytes.Buffer)
	fmt.Fprint(w, "AWS4-HMAC-SHA256 ")
	fmt.Fprintf(w, "Credential=%s/%s, ", c.Credentials.AccessKeyID(), c.credentialScope(t))
	fmt.Fprintf(w, "SignedHeaders=%s, ", signedHeaders(header))
	fmt.Fprintf(w, "Signature=%s", signature)
	return w.String()
}

var currentTime = time.Now

const (
	iso8601BasicFormat      = "20060102T150405Z"
	iso8601BasicFormatShort = "20060102"
)

func requestTime(req *http.Request) time.Time {
	// Get "x-amz-date" header
	date := req.Header.Get("x-amz-date")

	// Attempt to parse as ISO8601BasicFormat
	t, err := time.Parse(iso8601BasicFormat, date)
	if err == nil {
		return t
	}

	// Attempt to parse as http.TimeFormat
	t, err = time.Parse(http.TimeFormat, date)
	if err == nil {
		req.Header.Set("x-amz-date", t.Format(iso8601BasicFormat))
		return t
	}

	// Get "date" header
	date = req.Header.Get("date")

	// Attempt to parse as http.TimeFormat
	t, err = time.Parse(http.TimeFormat, date)
	if err == nil {
		return t
	}

	// Create a current time header to be used
	t = currentTime().UTC()
	req.Header.Set("x-amz-date", t.Format(iso8601BasicFormat))
	return t
}

func canonicalRequest(req *http.Request, pHash string) (string, error) {
	c := new(bytes.Buffer)
	fmt.Fprintf(c, "%s\n", req.Method)
	fmt.Fprintf(c, "%s\n", canonicalURI(req.URL))
	fmt.Fprintf(c, "%s\n", canonicalQueryString(req.URL))
	fmt.Fprintf(c, "%s\n\n", canonicalHeaders(req.Header))
	fmt.Fprintf(c, "%s\n", signedHeaders(req.Header))
	fmt.Fprintf(c, "%s", pHash)
	return c.String(), nil
}

func canonicalURI(u *url.URL) string {
	u = &url.URL{Path: u.Path}
	canonicalPath := u.String()
	slash := strings.HasSuffix(canonicalPath, "/")
	canonicalPath = path.Clean(canonicalPath)
	if canonicalPath != "/" && slash {
		canonicalPath += "/"
	}

	return canonicalPath
}

func canonicalQueryString(u *url.URL) string {
	var a []string
	for k, vs := range u.Query() {
		k = url.QueryEscape(k)
		for _, v := range vs {
			if v == "" {
				a = append(a, k+"=")
			} else {
				v = url.QueryEscape(v)
				a = append(a, k+"="+v)
			}
		}
	}
	sort.Strings(a)
	return strings.Join(a, "&")
}

func canonicalHeaders(h http.Header) string {
	i, a := 0, make([]string, len(h))
	for k, v := range h {
		for j, w := range v {
			v[j] = strings.Trim(w, " ")
		}
		sort.Strings(v)
		a[i] = strings.ToLower(k) + ":" + strings.Join(v, ",")
		i++
	}
	sort.Strings(a)
	return strings.Join(a, "\n")
}

func signedHeaders(h http.Header) string {
	i, a := 0, make([]string, len(h))
	for k := range h {
		a[i] = strings.ToLower(k)
		i++
	}
	sort.Strings(a)
	return strings.Join(a, ";")
}

func payloadHash(req *http.Request) (string, error) {
	var b []byte
	if req.Body != nil {
		var err error
		b, err = ioutil.ReadAll(req.Body)
		if err != nil {
			return "", err
		}
	}
	req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
	return hash(string(b)), nil
}

func hash(in string) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s", in)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func mac(key, data []byte) []byte {
	h := hmac.New(sha256.New, key)
	_, _ = h.Write(data)
	return h.Sum(nil)
}
<commit_msg>Use a reader for signed rquests.<commit_after>package aws

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"path"
	"sort"
	"strings"
	"time"
)

// Context encapsulates the context of a client's connection to an AWS service.
type Context struct {
	Service     string
	Region      string
	Credentials Credentials
}

func (c *Context) sign(req *http.Request) error {
	req.Header.Set("host", req.Host) // host header must be included as a signed header
	payloadHash, err := payloadHash(req)
	if err != nil {
		return err
	}
	req.Header.Set("x-amz-content-sha256", payloadHash)

	t := requestTime(req)
	creq, err := canonicalRequest(req, payloadHash)
	if err != nil {
		return err
	}
	sts := c.stringToSign(t, creq)
	signature := c.signature(t, sts)
	auth := c.authorization(req.Header, t, signature)
	req.Header.Set("Authorization", auth)
	if s := c.Credentials.SecurityToken(); s != "" {
		req.Header.Set("X-Amz-Security-Token", s)
	}
	return nil
}

func (c *Context) stringToSign(t time.Time, creq string) string {
	w := new(bytes.Buffer)
	fmt.Fprint(w, "AWS4-HMAC-SHA256\n")
	fmt.Fprintf(w, "%s\n", t.Format(iso8601BasicFormat))
	fmt.Fprintf(w, "%s\n", c.credentialScope(t))
	fmt.Fprintf(w, "%s", hash(creq))
	return w.String()
}

func (c *Context) credentialScope(t time.Time) string {
	return fmt.Sprintf(
		"%s/%s/%s/aws4_request",
		t.Format(iso8601BasicFormatShort),
		c.Region,
		c.Service,
	)
}

func (c *Context) signature(t time.Time, sts string) string {
	h := mac(c.derivedKey(t), []byte(sts))
	return fmt.Sprintf("%x", h)
}

func (c *Context) derivedKey(t time.Time) []byte {
	h := mac(
		[]byte("AWS4"+c.Credentials.SecretAccessKey()),
		[]byte(t.Format(iso8601BasicFormatShort)),
	)
	h = mac(h, []byte(c.Region))
	h = mac(h, []byte(c.Service))
	h = mac(h, []byte("aws4_request"))
	return h
}

func (c *Context) authorization(header http.Header, t time.Time, signature string) string {
	w := new(bytes.Buffer)
	fmt.Fprint(w, "AWS4-HMAC-SHA256 ")
	fmt.Fprintf(w, "Credential=%s/%s, ", c.Credentials.AccessKeyID(), c.credentialScope(t))
	fmt.Fprintf(w, "SignedHeaders=%s, ", signedHeaders(header))
	fmt.Fprintf(w, "Signature=%s", signature)
	return w.String()
}

var currentTime = time.Now

const (
	iso8601BasicFormat      = "20060102T150405Z"
	iso8601BasicFormatShort = "20060102"
)

func requestTime(req *http.Request) time.Time {
	// Get "x-amz-date" header
	date := req.Header.Get("x-amz-date")

	// Attempt to parse as ISO8601BasicFormat
	t, err := time.Parse(iso8601BasicFormat, date)
	if err == nil {
		return t
	}

	// Attempt to parse as http.TimeFormat
	t, err = time.Parse(http.TimeFormat, date)
	if err == nil {
		req.Header.Set("x-amz-date", t.Format(iso8601BasicFormat))
		return t
	}

	// Get "date" header
	date = req.Header.Get("date")

	// Attempt to parse as http.TimeFormat
	t, err = time.Parse(http.TimeFormat, date)
	if err == nil {
		return t
	}

	// Create a current time header to be used
	t = currentTime().UTC()
	req.Header.Set("x-amz-date", t.Format(iso8601BasicFormat))
	return t
}

func canonicalRequest(req *http.Request, pHash string) (string, error) {
	c := new(bytes.Buffer)
	fmt.Fprintf(c, "%s\n", req.Method)
	fmt.Fprintf(c, "%s\n", canonicalURI(req.URL))
	fmt.Fprintf(c, "%s\n", canonicalQueryString(req.URL))
	fmt.Fprintf(c, "%s\n\n", canonicalHeaders(req.Header))
	fmt.Fprintf(c, "%s\n", signedHeaders(req.Header))
	fmt.Fprintf(c, "%s", pHash)
	return c.String(), nil
}

func canonicalURI(u *url.URL) string {
	u = &url.URL{Path: u.Path}
	canonicalPath := u.String()
	slash := strings.HasSuffix(canonicalPath, "/")
	canonicalPath = path.Clean(canonicalPath)
	if canonicalPath != "/" && slash {
		canonicalPath += "/"
	}

	return canonicalPath
}

func canonicalQueryString(u *url.URL) string {
	var a []string
	for k, vs := range u.Query() {
		k = url.QueryEscape(k)
		for _, v := range vs {
			if v == "" {
				a = append(a, k+"=")
			} else {
				v = url.QueryEscape(v)
				a = append(a, k+"="+v)
			}
		}
	}
	sort.Strings(a)
	return strings.Join(a, "&")
}

func canonicalHeaders(h http.Header) string {
	i, a := 0, make([]string, len(h))
	for k, v := range h {
		for j, w := range v {
			v[j] = strings.Trim(w, " ")
		}
		sort.Strings(v)
		a[i] = strings.ToLower(k) + ":" + strings.Join(v, ",")
		i++
	}
	sort.Strings(a)
	return strings.Join(a, "\n")
}

func signedHeaders(h http.Header) string {
	i, a := 0, make([]string, len(h))
	for k := range h {
		a[i] = strings.ToLower(k)
		i++
	}
	sort.Strings(a)
	return strings.Join(a, ";")
}

func payloadHash(req *http.Request) (string, error) {
	var b []byte
	if req.Body != nil {
		var err error
		b, err = ioutil.ReadAll(req.Body)
		if err != nil {
			return "", err
		}
	}
	req.Body = ioutil.NopCloser(bytes.NewReader(b))
	return hash(string(b)), nil
}

func hash(in string) string {
	h := sha256.New()
	fmt.Fprintf(h, "%s", in)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func mac(key, data []byte) []byte {
	h := hmac.New(sha256.New, key)
	_, _ = h.Write(data)
	return h.Sum(nil)
}
<|endoftext|>


<commit_before>package api

// StatusCode represents a valid LXD operation and container status.
type StatusCode int

// LXD status codes.
const (
	OperationCreated StatusCode = 100
	Started          StatusCode = 101
	Stopped          StatusCode = 102
	Running          StatusCode = 103
	Cancelling       StatusCode = 104
	Pending          StatusCode = 105
	Starting         StatusCode = 106
	Stopping         StatusCode = 107
	Aborting         StatusCode = 108
	Freezing         StatusCode = 109
	Frozen           StatusCode = 110
	Thawed           StatusCode = 111
	Error            StatusCode = 112
	Ready            StatusCode = 113

	Success StatusCode = 200

	Failure   StatusCode = 400
	Cancelled StatusCode = 401
)

// String returns a suitable string representation for the status code.
func (o StatusCode) String() string {
	return map[StatusCode]string{
		OperationCreated: "Operation created",
		Started:          "Started",
		Stopped:          "Stopped",
		Running:          "Running",
		Cancelling:       "Cancelling",
		Pending:          "Pending",
		Success:          "Success",
		Failure:          "Failure",
		Cancelled:        "Cancelled",
		Starting:         "Starting",
		Stopping:         "Stopping",
		Aborting:         "Aborting",
		Freezing:         "Freezing",
		Frozen:           "Frozen",
		Thawed:           "Thawed",
		Error:            "Error",
		Ready:            "Ready",
	}[o]
}

// IsFinal will return true if the status code indicates an end state.
func (o StatusCode) IsFinal() bool {
	return int(o) >= 200
}
<commit_msg>shared/api: Add function to get status code from status name<commit_after>package api

// StatusCode represents a valid LXD operation and container status.
type StatusCode int

// LXD status codes.
const (
	OperationCreated StatusCode = 100
	Started          StatusCode = 101
	Stopped          StatusCode = 102
	Running          StatusCode = 103
	Cancelling       StatusCode = 104
	Pending          StatusCode = 105
	Starting         StatusCode = 106
	Stopping         StatusCode = 107
	Aborting         StatusCode = 108
	Freezing         StatusCode = 109
	Frozen           StatusCode = 110
	Thawed           StatusCode = 111
	Error            StatusCode = 112
	Ready            StatusCode = 113

	Success StatusCode = 200

	Failure   StatusCode = 400
	Cancelled StatusCode = 401
)

// StatusCodeNames associates a status code to its name.
var StatusCodeNames = map[StatusCode]string{
	OperationCreated: "Operation created",
	Started:          "Started",
	Stopped:          "Stopped",
	Running:          "Running",
	Cancelling:       "Cancelling",
	Pending:          "Pending",
	Success:          "Success",
	Failure:          "Failure",
	Cancelled:        "Cancelled",
	Starting:         "Starting",
	Stopping:         "Stopping",
	Aborting:         "Aborting",
	Freezing:         "Freezing",
	Frozen:           "Frozen",
	Thawed:           "Thawed",
	Error:            "Error",
	Ready:            "Ready",
}

// String returns a suitable string representation for the status code.
func (o StatusCode) String() string {
	return StatusCodeNames[o]
}

// IsFinal will return true if the status code indicates an end state.
func (o StatusCode) IsFinal() bool {
	return int(o) >= 200
}

// StatusCodeFromString returns the status code of the giving status name.
func StatusCodeFromString(status string) StatusCode {
	for k, v := range StatusCodeNames {
		if v == status {
			return k
		}
	}

	return -1
}
<|endoftext|>


<commit_before>package handlers

import (
	"archive/tar"
	"bufio"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"github.com/deployithq/deployit/env"
	"github.com/deployithq/deployit/utils"
	"github.com/fatih/color"
	"gopkg.in/urfave/cli.v2"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"strconv"
	"strings"
)

// Main deploy it handler
// - Archive all files which is in folder
// - Send it to server

func DeployIt(c *cli.Context) error {

	env := NewEnv()

	var archiveName string = "tar.gz"
	var archivePath string = fmt.Sprintf("%s/.dit/%s", env.Path, archiveName)

	appInfo := new(AppInfo)
	err := appInfo.Read(env.Log, env.Path, env.Host)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	if appInfo.Name == "" {
		appInfo.Name = utils.AppName(env.Path)
		appInfo.Tag = Tag
		color.Cyan("Creating app: %s", appInfo.Name)
	} else {
		color.Cyan("Updating app: %s", appInfo.Name)
	}

	// Creating archive
	fw, err := os.Create(archivePath)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	gw := gzip.NewWriter(fw)
	tw := tar.NewWriter(gw)

	// Deleting archive after function ends
	defer func() {
		env.Log.Debug("Deleting archive: ", archivePath)

		fw.Close()
		gw.Close()
		tw.Close()

		// Deleting files
		err = os.Remove(archivePath)
		if err != nil {
			env.Log.Error(err)
			return
		}
	}()

	// Listing all files from database to know what files were deleted from previous run
	storedFiles, err := env.Storage.ListAllFiles(env.Log)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	// TODO Include deleted folders to deletedFiles like "nginx/"

	excludePatterns, err := utils.LoadDockerPatterns(env.Path)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	excludePatterns = append(excludePatterns, ".gitignore", ".dit", ".git")

	color.Cyan("Packing files")
	storedFiles, err = PackFiles(env, tw, env.Path, storedFiles, excludePatterns)
	if err != nil {
		return err
	}

	deletedFiles := []string{}

	for key, _ := range storedFiles {
		env.Log.Debug("Deleting: ", key)
		err = env.Storage.Delete(env.Log, key)
		if err != nil {
			return err
		}

		deletedFiles = append(deletedFiles, key)
	}

	tw.Close()
	gw.Close()
	fw.Close()

	bodyBuffer := new(bytes.Buffer)
	bodyWriter := multipart.NewWriter(bodyBuffer)

	// Adding deleted files to request
	if len(deletedFiles) > 0 {
		delFiles, err := json.Marshal(deletedFiles)
		if err != nil {
			env.Log.Error(err)
			return err
		}

		bodyWriter.WriteField("deleted", string(delFiles))
	}

	// Adding application info to request
	if appInfo.UUID == "" {
		bodyWriter.WriteField("name", appInfo.Name)
	} else {
		bodyWriter.WriteField("id", appInfo.UUID)
	}

	bodyWriter.WriteField("tag", appInfo.Name)

	archiveInfo, err := os.Stat(archivePath)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	// If archive size is 32 it means that it is empty and we don't need to send it
	if archiveInfo.Size() != 32 {
		fh, err := os.Open(archivePath)
		if err != nil {
			env.Log.Error(err)
			return err
		}

		fileWriter, err := bodyWriter.CreateFormFile("file", "tar.gz")
		if err != nil {
			env.Log.Error(err)
			return err
		}

		_, err = io.Copy(fileWriter, fh)
		if err != nil {
			env.Log.Error(err)
			return err
		}

		fh.Close()
	}

	bodyWriter.Close()

	env.Log.Debugf("%s/app/deploy", Host)

	// Creating response for file uploading with fields
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/app/deploy", env.HostUrl), bodyBuffer)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	req.Header.Set("Content-Type", bodyWriter.FormDataContentType())

	color.Cyan("Uploading sources")

	// TODO Show uploading progress

	client := new(http.Client)
	res, err := client.Do(req)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	if Log {
		color.Cyan("Logs: ")
		reader := bufio.NewReader(res.Body)
		for {
			line, err := reader.ReadBytes('\n')
			if err != nil {
				break
			}
			fmt.Println(string(line))
		}
	}

	appInfo.URL = res.Header.Get("x-deployit-url")

	color.Cyan(appInfo.URL)

	// TODO Handle errors from http - clear DB if was first run

	if appInfo.UUID == "" {
		appInfo.UUID = res.Header.Get("x-deployit-id")
	}

	err = appInfo.Write(env.Log, env.Path, env.Host, appInfo.UUID, appInfo.Name, appInfo.Tag, appInfo.URL)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	res.Body.Close()

	color.Cyan("Done")

	return nil
}

func PackFiles(env *env.Env, tw *tar.Writer, filesPath string, storedFiles map[string]string, excludePatterns []string) (map[string]string, error) {

	// Opening directory with files
	dir, err := os.Open(filesPath)

	if err != nil {
		env.Log.Error(err)
		return storedFiles, err
	}

	// Reading all files
	files, err := dir.Readdir(0)
	if err != nil {
		env.Log.Error(err)
		return storedFiles, err
	}

	for _, file := range files {

		fileName := file.Name()

		currentFilePath := fmt.Sprintf("%s/%s", filesPath, fileName)

		// Creating path, which will be inside of archive
		relativePath := strings.Replace(currentFilePath, env.Path, "", 1)[1:]

		// Ignoring files which is not needed for build to make archive smaller
		// TODO: create base .ditignore file on first application creation

		matches, err := utils.Matches(relativePath, excludePatterns)
		if err != nil {
			return storedFiles, err
		}
		if matches {
			continue
		}

		// If it was directory - calling this function again
		// In other case adding file to archive
		if file.IsDir() {
			storedFiles, err = PackFiles(env, tw, currentFilePath, storedFiles, excludePatterns)
			if err != nil {
				return storedFiles, err
			}
			continue
		}

		// Creating hash
		hash := utils.Hash(fmt.Sprintf("%s:%s:%s", file.Name(), strconv.FormatInt(file.Size(), 10), file.ModTime()))

		if storedFiles[relativePath] == hash {
			delete(storedFiles, relativePath)
			continue
		}

		delete(storedFiles, relativePath)

		// If hashes are not equal - add file to archive
		env.Log.Debug("Packing file: ", currentFilePath)

		err = env.Storage.Write(env.Log, relativePath, hash)
		if err != nil {
			return storedFiles, err
		}

		fr, err := os.Open(currentFilePath)
		if err != nil {
			env.Log.Error(err)
			return storedFiles, err
		}

		h := &tar.Header{
			Name:    relativePath,
			Size:    file.Size(),
			Mode:    int64(file.Mode()),
			ModTime: file.ModTime(),
		}

		err = tw.WriteHeader(h)
		if err != nil {
			env.Log.Error(err)
			return storedFiles, err
		}

		_, err = io.Copy(tw, fr)
		if err != nil {
			env.Log.Error(err)
			return storedFiles, err
		}

		fr.Close()

	}

	dir.Close()

	return storedFiles, err

}
<commit_msg>Fix parameter `tag` in cli<commit_after>package handlers

import (
	"archive/tar"
	"bufio"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"github.com/deployithq/deployit/env"
	"github.com/deployithq/deployit/utils"
	"github.com/fatih/color"
	"gopkg.in/urfave/cli.v2"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"strconv"
	"strings"
)

// Main deploy it handler
// - Archive all files which is in folder
// - Send it to server

func DeployIt(c *cli.Context) error {

	env := NewEnv()

	var archiveName string = "tar.gz"
	var archivePath string = fmt.Sprintf("%s/.dit/%s", env.Path, archiveName)

	appInfo := new(AppInfo)
	err := appInfo.Read(env.Log, env.Path, env.Host)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	if appInfo.Name == "" {
		appInfo.Name = utils.AppName(env.Path)
		appInfo.Tag = Tag
		color.Cyan("Creating app: %s", appInfo.Name)
	} else {
		color.Cyan("Updating app: %s", appInfo.Name)
	}

	// Creating archive
	fw, err := os.Create(archivePath)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	gw := gzip.NewWriter(fw)
	tw := tar.NewWriter(gw)

	// Deleting archive after function ends
	defer func() {
		env.Log.Debug("Deleting archive: ", archivePath)

		fw.Close()
		gw.Close()
		tw.Close()

		// Deleting files
		err = os.Remove(archivePath)
		if err != nil {
			env.Log.Error(err)
			return
		}
	}()

	// Listing all files from database to know what files were deleted from previous run
	storedFiles, err := env.Storage.ListAllFiles(env.Log)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	// TODO Include deleted folders to deletedFiles like "nginx/"

	excludePatterns, err := utils.LoadDockerPatterns(env.Path)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	excludePatterns = append(excludePatterns, ".gitignore", ".dit", ".git")

	color.Cyan("Packing files")
	storedFiles, err = PackFiles(env, tw, env.Path, storedFiles, excludePatterns)
	if err != nil {
		return err
	}

	deletedFiles := []string{}

	for key, _ := range storedFiles {
		env.Log.Debug("Deleting: ", key)
		err = env.Storage.Delete(env.Log, key)
		if err != nil {
			return err
		}

		deletedFiles = append(deletedFiles, key)
	}

	tw.Close()
	gw.Close()
	fw.Close()

	bodyBuffer := new(bytes.Buffer)
	bodyWriter := multipart.NewWriter(bodyBuffer)

	// Adding deleted files to request
	if len(deletedFiles) > 0 {
		delFiles, err := json.Marshal(deletedFiles)
		if err != nil {
			env.Log.Error(err)
			return err
		}

		bodyWriter.WriteField("deleted", string(delFiles))
	}

	// Adding application info to request
	if appInfo.UUID == "" {
		bodyWriter.WriteField("name", appInfo.Name)
	} else {
		bodyWriter.WriteField("id", appInfo.UUID)
	}

	bodyWriter.WriteField("tag", appInfo.Tag)

	archiveInfo, err := os.Stat(archivePath)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	// If archive size is 32 it means that it is empty and we don't need to send it
	if archiveInfo.Size() != 32 {
		fh, err := os.Open(archivePath)
		if err != nil {
			env.Log.Error(err)
			return err
		}

		fileWriter, err := bodyWriter.CreateFormFile("file", "tar.gz")
		if err != nil {
			env.Log.Error(err)
			return err
		}

		_, err = io.Copy(fileWriter, fh)
		if err != nil {
			env.Log.Error(err)
			return err
		}

		fh.Close()
	}

	bodyWriter.Close()

	env.Log.Debugf("%s/app/deploy", Host)

	// Creating response for file uploading with fields
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/app/deploy", env.HostUrl), bodyBuffer)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	req.Header.Set("Content-Type", bodyWriter.FormDataContentType())

	color.Cyan("Uploading sources")

	// TODO Show uploading progress

	client := new(http.Client)
	res, err := client.Do(req)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	if Log {
		color.Cyan("Logs: ")
		reader := bufio.NewReader(res.Body)
		for {
			line, err := reader.ReadBytes('\n')
			if err != nil {
				break
			}
			fmt.Println(string(line))
		}
	}

	appInfo.URL = res.Header.Get("x-deployit-url")

	color.Cyan(appInfo.URL)

	// TODO Handle errors from http - clear DB if was first run

	if appInfo.UUID == "" {
		appInfo.UUID = res.Header.Get("x-deployit-id")
	}

	err = appInfo.Write(env.Log, env.Path, env.Host, appInfo.UUID, appInfo.Name, appInfo.Tag, appInfo.URL)
	if err != nil {
		env.Log.Error(err)
		return err
	}

	res.Body.Close()

	color.Cyan("Done")

	return nil
}

func PackFiles(env *env.Env, tw *tar.Writer, filesPath string, storedFiles map[string]string, excludePatterns []string) (map[string]string, error) {

	// Opening directory with files
	dir, err := os.Open(filesPath)

	if err != nil {
		env.Log.Error(err)
		return storedFiles, err
	}

	// Reading all files
	files, err := dir.Readdir(0)
	if err != nil {
		env.Log.Error(err)
		return storedFiles, err
	}

	for _, file := range files {

		fileName := file.Name()

		currentFilePath := fmt.Sprintf("%s/%s", filesPath, fileName)

		// Creating path, which will be inside of archive
		relativePath := strings.Replace(currentFilePath, env.Path, "", 1)[1:]

		// Ignoring files which is not needed for build to make archive smaller
		// TODO: create base .ditignore file on first application creation

		matches, err := utils.Matches(relativePath, excludePatterns)
		if err != nil {
			return storedFiles, err
		}
		if matches {
			continue
		}

		// If it was directory - calling this function again
		// In other case adding file to archive
		if file.IsDir() {
			storedFiles, err = PackFiles(env, tw, currentFilePath, storedFiles, excludePatterns)
			if err != nil {
				return storedFiles, err
			}
			continue
		}

		// Creating hash
		hash := utils.Hash(fmt.Sprintf("%s:%s:%s", file.Name(), strconv.FormatInt(file.Size(), 10), file.ModTime()))

		if storedFiles[relativePath] == hash {
			delete(storedFiles, relativePath)
			continue
		}

		delete(storedFiles, relativePath)

		// If hashes are not equal - add file to archive
		env.Log.Debug("Packing file: ", currentFilePath)

		err = env.Storage.Write(env.Log, relativePath, hash)
		if err != nil {
			return storedFiles, err
		}

		fr, err := os.Open(currentFilePath)
		if err != nil {
			env.Log.Error(err)
			return storedFiles, err
		}

		h := &tar.Header{
			Name:    relativePath,
			Size:    file.Size(),
			Mode:    int64(file.Mode()),
			ModTime: file.ModTime(),
		}

		err = tw.WriteHeader(h)
		if err != nil {
			env.Log.Error(err)
			return storedFiles, err
		}

		_, err = io.Copy(tw, fr)
		if err != nil {
			env.Log.Error(err)
			return storedFiles, err
		}

		fr.Close()

	}

	dir.Close()

	return storedFiles, err

}
<|endoftext|>


<commit_before>package handlers

import (
	"os"
	"strconv"

	"gopkg.in/alexcesaro/statsd.v2"
	"gopkg.in/gin-contrib/cors.v1"
	"gopkg.in/gin-gonic/gin.v1"

	"github.com/dgrijalva/jwt-go"

	"github.com/ghmeier/bloodlines/gateways"
	coi "github.com/ghmeier/coinage/gateways"
	t "github.com/jakelong95/TownCenter/gateways"
	w "github.com/lcollin/warehouse/gateways"
	cov "github.com/yuderekyu/covenant/gateways"
)

/*BaseHandler contains wrapper methods that all handlers need and should use
  for consistency across services*/
type BaseHandler struct {
	Stats *statsd.Client
}

type ExpressoClaims struct {
	UserID string `json:"userId"`
	jwt.StandardClaims
}

/*GatewayContext contains references to each type of gateway used for simple
  use in handler construction*/
type GatewayContext struct {
	Sql        gateways.SQL
	Sendgrid   gateways.SendgridI
	TownCenter t.TownCenterI
	Covenant   cov.Covenant
	Warehouse  w.Warehouse
	Bloodlines gateways.Bloodlines
	Coinage    coi.Coinage
	Rabbit     gateways.RabbitI
	Stats      *statsd.Client
	Stripe     coi.Stripe
	S3         gateways.S3
}

/*NewBaseHandler returns a new BaseHandler instance from a given stats*/
func NewBaseHandler(stats *statsd.Client) *BaseHandler {
	return &BaseHandler{Stats: stats}
}

/*GetPaging returns the offset and limit parameters from a gin request context
defaults to offset=0 and limit=20*/
func (b *BaseHandler) GetPaging(ctx *gin.Context) (int, int) {
	offset, _ := strconv.Atoi(ctx.DefaultQuery("offset", "0"))
	limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "20"))
	return offset, limit
}

/*UserError sends a 400 response with the given message string and error object*/
func (b *BaseHandler) UserError(ctx *gin.Context, msg string, obj interface{}) {
	if b.Stats != nil {
		b.Stats.Increment("400")
	}
	b.send(ctx, 400, &gin.H{"success": false, "message": msg, "data": obj})
}

/*NotFoundError sends a 404 response and false success when a resource is not present*/
func (b *BaseHandler) NotFoundError(ctx *gin.Context, msg string) {
	if b.Stats != nil {
		b.Stats.Increment("404")
	}
	b.send(ctx, 404, &gin.H{"success": false, "message": msg})
}

/*Unauthorized sends a 401 response along with a message*/
func (b *BaseHandler) Unauthorized(ctx *gin.Context, msg string) {
	if b.Stats != nil {
		b.Stats.Increment("401")
	}
	b.send(ctx, 401, &gin.H{"success": false, "message": msg})
}

/*ServerError sends a 500 response with the given error and object*/
func (b *BaseHandler) ServerError(ctx *gin.Context, err error, obj interface{}) {
	if b.Stats != nil {
		b.Stats.Increment("500")
	}
	b.send(ctx, 500, &gin.H{"success": false, "message": err.Error(), "data": obj})
}

/*Success sends a 200 response with the given object*/
func (b *BaseHandler) Success(ctx *gin.Context, obj interface{}) {
	if b.Stats != nil {
		b.Stats.Increment("200")
	}
	b.send(ctx, 200, &gin.H{"success": true, "data": obj})
}

func (b *BaseHandler) send(ctx *gin.Context, status int, json *gin.H) {
	ctx.JSON(status, json)
}

/*Time sets up gin middleware for sending timing stats*/
func (b *BaseHandler) Time() gin.HandlerFunc {
	return func(c *gin.Context) {
		if b.Stats != nil {
			defer b.Stats.NewTiming().Send(c.Request.Method)
		}
		c.Next()
	}
}

/*GetCors returns a gin handlerFunc for CORS reuquests in expresso services */
func GetCors() gin.HandlerFunc {
	config := cors.DefaultConfig()
	config.AddAllowMethods("DELETE")
	config.AddAllowHeaders("X-Auth")
	config.AddExposeHeaders("X-Auth")
	config.AddAllowHeaders("X-Token")
	config.AddExposeHeaders("X-Token")
	config.AllowAllOrigins = true
	return cors.New(config)
}

/*GetJWT returns a gin handlerfunc for authenticating JWTs in expresso services*/
func (b *BaseHandler) GetJWT() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if gin.Mode() == gin.TestMode || gin.Mode() == gin.DebugMode {
			ctx.Next()
			return
		}

		jwtToken := os.Getenv("JWT_TOKEN")
		tokenHeader := ctx.Request.Header.Get("X-Token")
		if tokenHeader != "" && tokenHeader == jwtToken {
			ctx.Next()
			return
		}

		authHeader := ctx.Request.Header.Get("X-Auth")
		token, err := jwt.ParseWithClaims(authHeader, &ExpressoClaims{}, func(token *jwt.Token) (interface{}, error) {
			return []byte(jwtToken), nil
		})

		if err != nil {
			b.Unauthorized(ctx, "Unable to parse token")
			ctx.Abort()
			return
		}

		claims, _ := token.Claims.(*ExpressoClaims)
		if err != nil || !token.Valid || claims.Valid() != nil {
			b.Unauthorized(ctx, "Invalid token")
			ctx.Abort()
			return
		}

		ctx.Request.Header.Add("X-UserId", claims.UserID)
		ctx.Next()
	}
}
<commit_msg>Add patch to allowed methods<commit_after>package handlers

import (
	"os"
	"strconv"

	"gopkg.in/alexcesaro/statsd.v2"
	"gopkg.in/gin-contrib/cors.v1"
	"gopkg.in/gin-gonic/gin.v1"

	"github.com/dgrijalva/jwt-go"

	"github.com/ghmeier/bloodlines/gateways"
	coi "github.com/ghmeier/coinage/gateways"
	t "github.com/jakelong95/TownCenter/gateways"
	w "github.com/lcollin/warehouse/gateways"
	cov "github.com/yuderekyu/covenant/gateways"
)

/*BaseHandler contains wrapper methods that all handlers need and should use
  for consistency across services*/
type BaseHandler struct {
	Stats *statsd.Client
}

type ExpressoClaims struct {
	UserID string `json:"userId"`
	jwt.StandardClaims
}

/*GatewayContext contains references to each type of gateway used for simple
  use in handler construction*/
type GatewayContext struct {
	Sql        gateways.SQL
	Sendgrid   gateways.SendgridI
	TownCenter t.TownCenterI
	Covenant   cov.Covenant
	Warehouse  w.Warehouse
	Bloodlines gateways.Bloodlines
	Coinage    coi.Coinage
	Rabbit     gateways.RabbitI
	Stats      *statsd.Client
	Stripe     coi.Stripe
	S3         gateways.S3
}

/*NewBaseHandler returns a new BaseHandler instance from a given stats*/
func NewBaseHandler(stats *statsd.Client) *BaseHandler {
	return &BaseHandler{Stats: stats}
}

/*GetPaging returns the offset and limit parameters from a gin request context
defaults to offset=0 and limit=20*/
func (b *BaseHandler) GetPaging(ctx *gin.Context) (int, int) {
	offset, _ := strconv.Atoi(ctx.DefaultQuery("offset", "0"))
	limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "20"))
	return offset, limit
}

/*UserError sends a 400 response with the given message string and error object*/
func (b *BaseHandler) UserError(ctx *gin.Context, msg string, obj interface{}) {
	if b.Stats != nil {
		b.Stats.Increment("400")
	}
	b.send(ctx, 400, &gin.H{"success": false, "message": msg, "data": obj})
}

/*NotFoundError sends a 404 response and false success when a resource is not present*/
func (b *BaseHandler) NotFoundError(ctx *gin.Context, msg string) {
	if b.Stats != nil {
		b.Stats.Increment("404")
	}
	b.send(ctx, 404, &gin.H{"success": false, "message": msg})
}

/*Unauthorized sends a 401 response along with a message*/
func (b *BaseHandler) Unauthorized(ctx *gin.Context, msg string) {
	if b.Stats != nil {
		b.Stats.Increment("401")
	}
	b.send(ctx, 401, &gin.H{"success": false, "message": msg})
}

/*ServerError sends a 500 response with the given error and object*/
func (b *BaseHandler) ServerError(ctx *gin.Context, err error, obj interface{}) {
	if b.Stats != nil {
		b.Stats.Increment("500")
	}
	b.send(ctx, 500, &gin.H{"success": false, "message": err.Error(), "data": obj})
}

/*Success sends a 200 response with the given object*/
func (b *BaseHandler) Success(ctx *gin.Context, obj interface{}) {
	if b.Stats != nil {
		b.Stats.Increment("200")
	}
	b.send(ctx, 200, &gin.H{"success": true, "data": obj})
}

func (b *BaseHandler) send(ctx *gin.Context, status int, json *gin.H) {
	ctx.JSON(status, json)
}

/*Time sets up gin middleware for sending timing stats*/
func (b *BaseHandler) Time() gin.HandlerFunc {
	return func(c *gin.Context) {
		if b.Stats != nil {
			defer b.Stats.NewTiming().Send(c.Request.Method)
		}
		c.Next()
	}
}

/*GetCors returns a gin handlerFunc for CORS reuquests in expresso services */
func GetCors() gin.HandlerFunc {
	config := cors.DefaultConfig()
	config.AddAllowMethods("DELETE")
	config.AddAllowMethods("PATCH")
	config.AddAllowHeaders("X-Auth")
	config.AddExposeHeaders("X-Auth")
	config.AddAllowHeaders("X-Token")
	config.AddExposeHeaders("X-Token")
	config.AllowAllOrigins = true
	return cors.New(config)
}

/*GetJWT returns a gin handlerfunc for authenticating JWTs in expresso services*/
func (b *BaseHandler) GetJWT() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if gin.Mode() == gin.TestMode || gin.Mode() == gin.DebugMode {
			ctx.Next()
			return
		}

		jwtToken := os.Getenv("JWT_TOKEN")
		tokenHeader := ctx.Request.Header.Get("X-Token")
		if tokenHeader != "" && tokenHeader == jwtToken {
			ctx.Next()
			return
		}

		authHeader := ctx.Request.Header.Get("X-Auth")
		token, err := jwt.ParseWithClaims(authHeader, &ExpressoClaims{}, func(token *jwt.Token) (interface{}, error) {
			return []byte(jwtToken), nil
		})

		if err != nil {
			b.Unauthorized(ctx, "Unable to parse token")
			ctx.Abort()
			return
		}

		claims, _ := token.Claims.(*ExpressoClaims)
		if err != nil || !token.Valid || claims.Valid() != nil {
			b.Unauthorized(ctx, "Invalid token")
			ctx.Abort()
			return
		}

		ctx.Request.Header.Add("X-UserId", claims.UserID)
		ctx.Next()
	}
}
<|endoftext|>


<commit_before>package handlers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/byuoitav/event-router-microservice/eventinfrastructure"
	"github.com/byuoitav/event-router-microservice/subscription"
	"github.com/byuoitav/touchpanel-ui-microservice/events"
	"github.com/byuoitav/touchpanel-ui-microservice/helpers"
	"github.com/labstack/echo"
)

func OpenWebSocket(context echo.Context) error {
	events.StartWebClient(context.Response(), context.Request())
	return nil
}

func Subscribe(context echo.Context) error {
	var sr subscription.SubscribeRequest
	err := context.Bind(&sr)
	if err != nil {
		log.Printf("[error] %s", err.Error())
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("[handler] Subscribing to %s", sr.Address)
	err = events.Sub.Subscribe(sr.Address, []string{eventinfrastructure.UI})
	if err != nil {
		log.Printf("[error] %s", err.Error())
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	return context.JSON(http.StatusOK, context)
}

func GetHostname(context echo.Context) error {
	hostname := os.Getenv("PI_HOSTNAME")
	return context.JSON(http.StatusOK, hostname)
}

func PublishEvent(context echo.Context) error {
	var event eventinfrastructure.EventInfo
	err := context.Bind(&event)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	err = events.Publish(event)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.JSON(http.StatusOK, event)
}

func GetDeviceInfo(context echo.Context) error {
	di, err := helpers.GetDeviceInfo()
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.JSON(http.StatusOK, di)
}

func Refresh(context echo.Context) error {
	log.Printf("[management] Refreshing webpage")
	events.Refresh()

	return nil
}

func Reboot(context echo.Context) error {
	log.Printf("[management] Rebooting pi")
	http.Get("http://localhost:7010/reboot")
	return nil
}

func GetDockerStatus(context echo.Context) error {
	log.Printf("[management] Getting docker status")
	resp, err := http.Get("http://localhost:7010/dockerStatus")
	log.Printf("docker status response: %s", resp)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.String(http.StatusOK, string(body))
}

func Help(context echo.Context) error {
	var sh helpers.SlackHelp
	err := context.Bind(&sh)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("Requesting help in building %s, room %s", sh.Building, sh.Room)
	url := os.Getenv("HELP_SLACKBOT_WEBHOOK")
	if len(url) == 0 {
		panic(fmt.Sprintf("HELP_SLACKBOT_WEBHOOK is not set."))
	}

	// build json payload
	// attachment
	var attachment helpers.Attachment
	attachment.Title = "help request"
	// fields
	var fieldOne helpers.Field
	var fieldTwo helpers.Field
	fieldOne.Title = "building"
	fieldOne.Value = sh.Building
	fieldOne.Short = true
	fieldTwo.Title = "room"
	fieldTwo.Value = sh.Room
	fieldTwo.Short = true
	// actions
	var actionOne helpers.Action
	actionOne.Name = "accepthelp"
	actionOne.Text = "help"
	actionOne.Type = "button"
	actionOne.Value = "true"
	// put into sh
	attachment.Fields = append(attachment.Fields, fieldOne)
	attachment.Fields = append(attachment.Fields, fieldTwo)
	attachment.Actions = append(attachment.Actions, actionOne)
	sh.Attachments = append(sh.Attachments, attachment)

	json, err := json.Marshal(sh)
	if err != nil {
		log.Printf("failed to marshal sh: %s", sh)
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return context.JSON(http.StatusOK, string(body))
}

func ConfirmHelp(context echo.Context) error {
	var sh helpers.SlackHelp
	err := context.Bind(&sh)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("Confirming help in building %s, room %s", sh.Building, sh.Room)
	url := os.Getenv("HELP_SLACKBOT_WEBHOOK")
	if len(url) == 0 {
		panic(fmt.Sprintf("HELP_SLACKBOT_WEBHOOK is not set."))
	}

	var shm helpers.SlackMessage

	shm.Text = fmt.Sprintf("Confirmation of request for help in building %s and room %s", sh.Building, sh.Room)
	json, err := json.Marshal(shm)
	if err != nil {
		log.Printf("failed to marshal shm: %s", shm)
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return context.JSON(http.StatusOK, string(body))
}

func CancelHelp(context echo.Context) error {
	var sh helpers.SlackHelp
	err := context.Bind(&sh)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("Canceling request for help %s, room %s", sh.Building, sh.Room)
	url := os.Getenv("HELP_SLACKBOT_WEBHOOK")
	if len(url) == 0 {
		panic(fmt.Sprintf("HELP_SLACKBOT_WEBHOOK is not set."))
	}

	var shm helpers.SlackMessage

	shm.Text = fmt.Sprintf("Cancellation of request for help in building %s and room %s", sh.Building, sh.Room)
	json, err := json.Marshal(shm)
	if err != nil {
		log.Printf("failed to marshal shm: %s", shm)
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return context.JSON(http.StatusOK, string(body))

}

var configcache map[string]interface{}

func GetJSON(context echo.Context) error {
	address := os.Getenv("UI_CONFIGURATION_ADDRESS")
	hn := os.Getenv("PI_HOSTNAME")

	if len(hn) == 0 {
		return context.JSON(http.StatusInternalServerError, "PI_HOSTNAME is not set.")
	}

	if len(address) == 0 {
		if configcache != nil {
			console.log("[error] UI_CONFIGURATION_ADDRESS is not set. Returning cached configuration...")
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusInternalServerError, "UI_CONFIGURATION_ADDRESS is not set.")
	}

	log.Printf("getting json object from %s", address)
	resp, err := http.Get(address)
	if err != nil {
		if configcache != nil {
			console.log("[error] %s. Returning cached configuration...", err.Error())
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusGatewayTimeout, err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		if configcache != nil {
			console.log("[error] %s. Returning cached configuration...", err.Error())
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	var data map[string]interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		if configcache != nil {
			console.log("[error] %s. Returning cached configuration...", err.Error())
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusInternalServerError, err.Error())
	} else {
		configcache = data
	}

	return context.JSON(http.StatusOK, data[hn])
}
<commit_msg>whoops lol<commit_after>package handlers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/byuoitav/event-router-microservice/eventinfrastructure"
	"github.com/byuoitav/event-router-microservice/subscription"
	"github.com/byuoitav/touchpanel-ui-microservice/events"
	"github.com/byuoitav/touchpanel-ui-microservice/helpers"
	"github.com/labstack/echo"
)

func OpenWebSocket(context echo.Context) error {
	events.StartWebClient(context.Response(), context.Request())
	return nil
}

func Subscribe(context echo.Context) error {
	var sr subscription.SubscribeRequest
	err := context.Bind(&sr)
	if err != nil {
		log.Printf("[error] %s", err.Error())
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("[handler] Subscribing to %s", sr.Address)
	err = events.Sub.Subscribe(sr.Address, []string{eventinfrastructure.UI})
	if err != nil {
		log.Printf("[error] %s", err.Error())
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	return context.JSON(http.StatusOK, context)
}

func GetHostname(context echo.Context) error {
	hostname := os.Getenv("PI_HOSTNAME")
	return context.JSON(http.StatusOK, hostname)
}

func PublishEvent(context echo.Context) error {
	var event eventinfrastructure.EventInfo
	err := context.Bind(&event)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	err = events.Publish(event)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.JSON(http.StatusOK, event)
}

func GetDeviceInfo(context echo.Context) error {
	di, err := helpers.GetDeviceInfo()
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.JSON(http.StatusOK, di)
}

func Refresh(context echo.Context) error {
	log.Printf("[management] Refreshing webpage")
	events.Refresh()

	return nil
}

func Reboot(context echo.Context) error {
	log.Printf("[management] Rebooting pi")
	http.Get("http://localhost:7010/reboot")
	return nil
}

func GetDockerStatus(context echo.Context) error {
	log.Printf("[management] Getting docker status")
	resp, err := http.Get("http://localhost:7010/dockerStatus")
	log.Printf("docker status response: %s", resp)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.String(http.StatusOK, string(body))
}

func Help(context echo.Context) error {
	var sh helpers.SlackHelp
	err := context.Bind(&sh)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("Requesting help in building %s, room %s", sh.Building, sh.Room)
	url := os.Getenv("HELP_SLACKBOT_WEBHOOK")
	if len(url) == 0 {
		panic(fmt.Sprintf("HELP_SLACKBOT_WEBHOOK is not set."))
	}

	// build json payload
	// attachment
	var attachment helpers.Attachment
	attachment.Title = "help request"
	// fields
	var fieldOne helpers.Field
	var fieldTwo helpers.Field
	fieldOne.Title = "building"
	fieldOne.Value = sh.Building
	fieldOne.Short = true
	fieldTwo.Title = "room"
	fieldTwo.Value = sh.Room
	fieldTwo.Short = true
	// actions
	var actionOne helpers.Action
	actionOne.Name = "accepthelp"
	actionOne.Text = "help"
	actionOne.Type = "button"
	actionOne.Value = "true"
	// put into sh
	attachment.Fields = append(attachment.Fields, fieldOne)
	attachment.Fields = append(attachment.Fields, fieldTwo)
	attachment.Actions = append(attachment.Actions, actionOne)
	sh.Attachments = append(sh.Attachments, attachment)

	json, err := json.Marshal(sh)
	if err != nil {
		log.Printf("failed to marshal sh: %s", sh)
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return context.JSON(http.StatusOK, string(body))
}

func ConfirmHelp(context echo.Context) error {
	var sh helpers.SlackHelp
	err := context.Bind(&sh)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("Confirming help in building %s, room %s", sh.Building, sh.Room)
	url := os.Getenv("HELP_SLACKBOT_WEBHOOK")
	if len(url) == 0 {
		panic(fmt.Sprintf("HELP_SLACKBOT_WEBHOOK is not set."))
	}

	var shm helpers.SlackMessage

	shm.Text = fmt.Sprintf("Confirmation of request for help in building %s and room %s", sh.Building, sh.Room)
	json, err := json.Marshal(shm)
	if err != nil {
		log.Printf("failed to marshal shm: %s", shm)
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return context.JSON(http.StatusOK, string(body))
}

func CancelHelp(context echo.Context) error {
	var sh helpers.SlackHelp
	err := context.Bind(&sh)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	log.Printf("Canceling request for help %s, room %s", sh.Building, sh.Room)
	url := os.Getenv("HELP_SLACKBOT_WEBHOOK")
	if len(url) == 0 {
		panic(fmt.Sprintf("HELP_SLACKBOT_WEBHOOK is not set."))
	}

	var shm helpers.SlackMessage

	shm.Text = fmt.Sprintf("Cancellation of request for help in building %s and room %s", sh.Building, sh.Room)
	json, err := json.Marshal(shm)
	if err != nil {
		log.Printf("failed to marshal shm: %s", shm)
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return context.JSON(http.StatusOK, string(body))

}

var configcache map[string]interface{}

func GetJSON(context echo.Context) error {
	address := os.Getenv("UI_CONFIGURATION_ADDRESS")
	hn := os.Getenv("PI_HOSTNAME")

	if len(hn) == 0 {
		return context.JSON(http.StatusInternalServerError, "PI_HOSTNAME is not set.")
	}

	if len(address) == 0 {
		if configcache != nil {
			log.Printf("[error] UI_CONFIGURATION_ADDRESS is not set. Returning cached configuration...")
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusInternalServerError, "UI_CONFIGURATION_ADDRESS is not set.")
	}

	log.Printf("getting json object from %s", address)
	resp, err := http.Get(address)
	if err != nil {
		if configcache != nil {
			log.Printf("[error] %s. Returning cached configuration...", err.Error())
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusGatewayTimeout, err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		if configcache != nil {
			log.Printf("[error] %s. Returning cached configuration...", err.Error())
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusInternalServerError, err.Error())
	}

	var data map[string]interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		if configcache != nil {
			log.Printf("[error] %s. Returning cached configuration...", err.Error())
			return context.JSON(http.StatusOK, configcache[hn])
		}
		return context.JSON(http.StatusInternalServerError, err.Error())
	} else {
		configcache = data
	}

	return context.JSON(http.StatusOK, data[hn])
}
<|endoftext|>


<commit_before>package switchboard

import (
	"github.com/pivotal-golang/lager"
	"sync"
)

type Backends interface {
	All() <-chan Backend
	Active() Backend
	SetHealthy(backend Backend)
	SetUnhealthy(backend Backend)
	Healthy() <-chan Backend
	ActivityChannels() (<-chan struct{}, <-chan struct{})
}

type backends struct {
	mutex        sync.Mutex
	all          []*statefulBackend
	active       Backend
	logger       lager.Logger
	activeChan   chan struct{}
	inactiveChan chan struct{}
}

type statefulBackend struct {
	backend Backend
	healthy bool
}

func NewBackends(backendIPs []string, backendPorts []uint, healthcheckPorts []uint, logger lager.Logger) Backends {
	b := &backends{
		logger:       logger,
		all:          make([]*statefulBackend, len(backendIPs)),
		activeChan:   make(chan struct{}),
		inactiveChan: make(chan struct{}, 1),
	}

	for i, ip := range backendIPs {
		backend := NewBackend(
			ip,
			backendPorts[i],
			healthcheckPorts[i],
			logger,
		)

		b.all[i] = &statefulBackend{
			backend: backend,
			healthy: true,
		}
	}

	if len(b.all) > 0 {
		b.active = b.all[0].backend
	} else {
		b.nonBlockingWrite(b.inactiveChan, struct{}{})
	}

	return b
}

func (b *backends) ActivityChannels() (<-chan struct{}, <-chan struct{}) {
	return b.activeChan, b.inactiveChan
}

func (b *backends) All() <-chan Backend {
	ch := make(chan Backend, len(b.all))

	go func() {
		b.mutex.Lock()
		defer b.mutex.Unlock()

		for _, sb := range b.all {
			ch <- sb.backend
		}
		close(ch)
	}()

	return ch
}

func (b *backends) Active() Backend {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	return b.active
}

func (b *backends) SetHealthy(backend Backend) {
	b.mutex.Lock()
	defer b.mutex.Unlock()
	knownBackend := b.setHealth(backend, true)
	b.logger.Info("Backend became healthy again.")
	if b.active == nil {
		b.active = knownBackend
		if b.active != nil {
			b.logger.Info("Recovering from down cluster, new active backend...")
			b.nonBlockingWrite(b.activeChan, struct{}{})
		}
	}
}

func (b *backends) SetUnhealthy(backend Backend) {
	b.mutex.Lock()
	defer b.mutex.Unlock()
	knownBackend := b.setHealth(backend, false)
	if b.active == knownBackend {
		b.active = b.nextHealthy()
		b.logger.Info("Active backend became unhealthy. Switching over to next available...")
		if b.active == nil {
			b.logger.Info("All backends unhealthy! No currently active backend.")
			b.nonBlockingWrite(b.inactiveChan, struct{}{})
		} else {
			b.logger.Info("Successfully failed over to next available backend!")
		}
	}
}

func (b *backends) nextHealthy() Backend {
	for _, sb := range b.all {
		if sb.healthy {
			return sb.backend
		}
	}
	return nil
}

func (b *backends) Healthy() <-chan Backend {
	c := make(chan Backend, len(b.all))

	go func() {
		b.mutex.Lock()
		defer b.mutex.Unlock()

		for _, sb := range b.all {
			if sb.healthy {
				c <- sb.backend
			}
		}

		close(c)
	}()

	return c
}

func (b *backends) setHealth(backend Backend, healthy bool) Backend {
	for _, sb := range b.all {
		if sb.backend == backend {
			sb.healthy = healthy
			return sb.backend
		}
	}
	return nil
}

func (b *backends) nonBlockingWrite(channel chan struct{}, msg struct{}) {
	select {
	case channel <- msg:
	default:
	}
}
<commit_msg>Prefix non-thread-safe method names with 'unsafe'<commit_after>package switchboard

import (
	"github.com/pivotal-golang/lager"
	"sync"
)

type Backends interface {
	All() <-chan Backend
	Active() Backend
	SetHealthy(backend Backend)
	SetUnhealthy(backend Backend)
	Healthy() <-chan Backend
	ActivityChannels() (<-chan struct{}, <-chan struct{})
}

type backends struct {
	mutex        sync.Mutex
	all          []*statefulBackend
	active       Backend
	logger       lager.Logger
	activeChan   chan struct{}
	inactiveChan chan struct{}
}

type statefulBackend struct {
	backend Backend
	healthy bool
}

func NewBackends(backendIPs []string, backendPorts []uint, healthcheckPorts []uint, logger lager.Logger) Backends {
	b := &backends{
		logger:       logger,
		all:          make([]*statefulBackend, len(backendIPs)),
		activeChan:   make(chan struct{}),
		inactiveChan: make(chan struct{}, 1),
	}

	for i, ip := range backendIPs {
		backend := NewBackend(
			ip,
			backendPorts[i],
			healthcheckPorts[i],
			logger,
		)

		b.all[i] = &statefulBackend{
			backend: backend,
			healthy: true,
		}
	}

	if len(b.all) > 0 {
		b.active = b.all[0].backend
	} else {
		b.nonBlockingWrite(b.inactiveChan, struct{}{})
	}

	return b
}

func (b *backends) ActivityChannels() (<-chan struct{}, <-chan struct{}) {
	return b.activeChan, b.inactiveChan
}

func (b *backends) All() <-chan Backend {
	ch := make(chan Backend, len(b.all))

	go func() {
		b.mutex.Lock()
		defer b.mutex.Unlock()

		for _, sb := range b.all {
			ch <- sb.backend
		}
		close(ch)
	}()

	return ch
}

func (b *backends) Active() Backend {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	return b.active
}

func (b *backends) SetHealthy(backend Backend) {
	b.mutex.Lock()
	defer b.mutex.Unlock()
	knownBackend := b.unsafeSetHealth(backend, true)
	b.logger.Info("Backend became healthy again.")
	if b.active == nil {
		b.active = knownBackend
		if b.active != nil {
			b.logger.Info("Recovering from down cluster, new active backend...")
			b.nonBlockingWrite(b.activeChan, struct{}{})
		}
	}
}

func (b *backends) SetUnhealthy(backend Backend) {
	b.mutex.Lock()
	defer b.mutex.Unlock()
	knownBackend := b.unsafeSetHealth(backend, false)
	if b.active == knownBackend {
		b.active = b.unsafeNextHealthy()
		b.logger.Info("Active backend became unhealthy. Switching over to next available...")
		if b.active == nil {
			b.logger.Info("All backends unhealthy! No currently active backend.")
			b.nonBlockingWrite(b.inactiveChan, struct{}{})
		} else {
			b.logger.Info("Successfully failed over to next available backend!")
		}
	}
}

func (b *backends) unsafeNextHealthy() Backend {
	for _, sb := range b.all {
		if sb.healthy {
			return sb.backend
		}
	}
	return nil
}

func (b *backends) Healthy() <-chan Backend {
	c := make(chan Backend, len(b.all))

	go func() {
		b.mutex.Lock()
		defer b.mutex.Unlock()

		for _, sb := range b.all {
			if sb.healthy {
				c <- sb.backend
			}
		}

		close(c)
	}()

	return c
}

func (b *backends) unsafeSetHealth(backend Backend, healthy bool) Backend {
	for _, sb := range b.all {
		if sb.backend == backend {
			sb.healthy = healthy
			return sb.backend
		}
	}
	return nil
}

func (b *backends) nonBlockingWrite(channel chan struct{}, msg struct{}) {
	select {
	case channel <- msg:
	default:
	}
}
<|endoftext|>