<commit_before>package hpcloud

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

/* Server flavours Smallest to Largest */
type Flavor int

const (
	XSmall = Flavor(100) + iota
	Small
	Medium
	Large
	XLarge
	DblXLarge
)

/* Available images */
type ServerImage int

const (
	UbuntuLucid10_04Kernel    = ServerImage(1235)
	UbuntuLucid10_04          = 1236
	UbuntuMaverick10_10Kernel = 1237
	UbuntuMaverick10_10       = 1238
	UbuntuNatty11_04Kernel    = 1239
	UbuntuNatty11_04          = 1240
	UbuntuOneiric11_10        = 5579
	UbuntuPrecise12_04        = 8419
	CentOS5_8Server64         = 54021
	CentOS6_2Server64Kernel   = 1356
	CentOS6_2Server64Ramdisk  = 1357
	CentOS6_2Server64         = 1358
	DebianSqueeze6_0_3Kernel  = 1359
	DebianSqueeze6_0_3Ramdisk = 1360
	DebianSqueeze6_0_3Server  = 1361
	Fedora16Server64          = 16291
	BitNamiDrupal7_14_0       = 22729
	BitNamiWebPack1_2_0       = 22731
	BitNamiDevPack1_0_0       = 4654
	ActiveStateStackatov1_2_6 = 14345
	ActiveStateStackatov2_2_2 = 59297
	ActiveStateStackatov2_2_3 = 60815
	EnterpriseDBPPAS9_1_2     = 9953
	EnterpriseDBPSQL9_1_3     = 9995
)

type Link struct {
	HREF string `json:"href"`
	Rel  string `json:"rel"`
}

/*
  Several embedded types are simply an ID string with a slice of Link
*/
type IDLink struct {
	Name  string `json:"name"`
	ID    string `json:"id"`
	Links []Link `json:"links"`
}

type Flavor_ struct {
	Name  string `json:"name"`
	ID    int64  `json:"id"`
	Links []Link `json:"links"`
}

type Flavors struct {
	F []Flavor_ `json:"flavors"`
}

type Image struct {
	I struct {
		Name     string            `json:"name"`
		ID       string            `json:"id"`
		Links    []Link            `json:"links"`
		Progress int               `json:"progress"`
		Metadata map[string]string `json:"metadata"`
		Status   string            `json:"status"`
		Updated  string            `json:"updated"`
	} `json:"image"`
}

type Images struct {
	I []IDLink `json:"images"`
}

/*
  This type describes the JSON data which should be sent to the create
  server resource.
*/
type Server struct {
	ConfigDrive    bool              `json:"config_drive"`
	FlavorRef      Flavor            `json:"flavorRef"`
	ImageRef       ServerImage       `json:"imageRef"`
	MaxCount       int               `json:"max_count"`
	MinCount       int               `json:"min_count"`
	Name           string            `json:"name"`
	Key            string            `json:"key_name"`
	Personality    string            `json:"personality"`
	UserData       string            `json:"user_data"`
	SecurityGroups []IDLink          `json:"security_groups"`
	Metadata       map[string]string `json:"metadata"`
}

/*
  This type describes the JSON response from a successful CreateServer
  call.
*/
type ServerResponse struct {
	S struct {
		Status         string            `json:"status"`
		Updated        string            `json:"update"`
		HostID         string            `json:"hostId"`
		UserID         string            `json:"user_id"`
		Name           string            `json:"name"`
		Links          []Link            `json:"links"`
		Addresses      interface{}       `json:"addresses"`
		TenantID       string            `json:"tenant_id"`
		Image          IDLink            `json:"image"`
		Created        string            `json:"created"`
		UUID           string            `json:"uuid"`
		AccessIPv4     string            `json:"accessIPv4"`
		AccessIPv6     string            `json:"accessIPv6"`
		KeyName        string            `json:"key_name"`
		AdminPass      string            `json:"adminPass"`
		Flavor         IDLink            `json:"flavor"`
		ConfigDrive    string            `json:"config_drive"`
		ID             int64             `json:"id"`
		SecurityGroups []IDLink          `json:"security_groups"`
		Metadata       map[string]string `json:"metadata"`
	} `json:"server"`
}

/*
  CreateServer creates a new server in the HPCloud using the
  settings found in the Server instance passed to this function.

  This function implements the interface as described in:-
  * https://docs.hpcloud.com/api/compute/
  * section 4.4.5.2 Create Server
*/
func (a Access) CreateServer(s Server) (*ServerResponse, error) {
	b, err := s.MarshalJSON()
	if err != nil {
		return nil, err
	}

	body, err := a.baseComputeRequest("servers", "POST",
		strings.NewReader(string(b)),
	)
	if err != nil {
		return nil, err
	}
	sr := &ServerResponse{}
	err = json.Unmarshal(body, sr)
	if err != nil {
		return nil, err
	}
	return sr, nil
}

/*
  DeleteServer deletes the server with the `server_id`.

  This function implements the interface described in:-
  * https://docs.hpcloud.com/api/compute/
  * Section 4.4.6.3 Delete Server
*/
func (a Access) DeleteServer(server_id string) error {
	_, err := a.baseComputeRequest(
		fmt.Sprintf("servers/%s", server_id),
		"DELETE", nil,
	)
	if err != nil {
		return err
	}
	return nil
}

/*
  RebootServer will reboot the server with the `server_id`.

  This function implements the interface described in:-
  * https://docs.hpcloud.com/api/compute/
  * Section 4.4.7.1 Reboot Server
*/
func (a Access) RebootServer(server_id string) error {
	/*
		 The docs mention that a hard reboot will be used
	     no matter what, so there's no point making a type
	     or make the type of reboot an option
	*/
	s := `{"reboot":{"type":"HARD"}}`
	_, err := a.baseComputeRequest(
		fmt.Sprintf("servers/%s/action", server_id),
		"POST", strings.NewReader(s),
	)
	if err != nil {
		return err
	}
	return nil

}

/*
  ListFlavors will list all the available flavours
  on the HPCloud compute API.
*/
func (a Access) ListFlavors() (*Flavors, error) {
	body, err := a.baseComputeRequest("flavors", "GET", nil)
	if err != nil {
		return nil, err
	}

	fl := &Flavors{}
	err = json.Unmarshal(body, fl)
	if err != nil {
		return nil, err
	}
	return fl, nil
}

func (a Access) ListImages() (*Images, error) {
	body, err := a.baseComputeRequest("images", "GET", nil)
	if err != nil {
		return nil, err
	}
	im := &Images{}
	err = json.Unmarshal(body, im)
	if err != nil {
		return nil, err
	}
	return im, nil
}

func (a Access) DeleteImage(image_id string) error {
	_, err := a.baseComputeRequest(
		fmt.Sprintf("images/%s", image_id), "DELETE", nil,
	)
	if err != nil {
		return err
	}
	return nil
}

func (a Access) ListImage(image_id string) (*Image, error) {
	body, err := a.baseComputeRequest(
		fmt.Sprintf("images/%s", image_id), "GET", nil,
	)
	if err != nil {
		return nil, err
	}
	i := &Image{}
	err = json.Unmarshal(body, i)
	if err != nil {
		return nil, err
	}
	return i, nil
}

/*
  baseComputeRequest encapsulates the main basic request
  which is done for each endpoint in the Compute API.

  In the ComputeAPI all endpoints generally succeed on
  a 200/202 return code and fail on the usual fail codes.

  We simply check for the known good return codes and return
  the body in those cases or we fail with the appropriate
  response.
*/
func (a Access) baseComputeRequest(url, method string, b io.Reader) ([]byte, error) {
	path := fmt.Sprintf("%s%s/%s", COMPUTE_URL, a.TenantID, url)
	client := &http.Client{}
	req, err := http.NewRequest(method, path, b)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", a.A.Token.ID)
	req.Header.Add("Content-type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	switch resp.StatusCode {
	case http.StatusAccepted:
	case http.StatusNonAuthoritativeInfo:
	case http.StatusOK:
		return body, nil
	case http.StatusNotFound:
		nf := &NotFound{}
		err = json.Unmarshal(body, nf)
		if err != nil {
			return nil, err
		}
		return nil, errors.New(nf.NF.Message)
	default:
		br := &BadRequest{}
		err = json.Unmarshal(body, br)
		if err != nil {
			return nil, err
		}
		return nil, errors.New(br.B.Message)
	}
	panic("Unreachable")
}

/*
  MarshalJSON implements the Marshaler interface for the
  Server type.

  We implement this interface because when creating a server
  we have optional values and since Go has zero-values and
  does *not* have configurable zero values we need to make
  sure that zero-values are converted to known good values.

  As such:
    * FlavorRef is checked if it's a valid reference.
    * Ditto for ImageRef.
    * Name cannot be blank.
    * If the key is missing, it'll not put anything in.
    * The config_drive defaults to false anyway, no need
      to send a false value.
    * Min/MaxCount are ignored if they are zero.
    * UserData is ignored if it's a blank string.
    * Personality is ignored if it's a blank string.
    * Metadata/SecurityGroups are ignored if they have len(0)
*/
func (s Server) MarshalJSON() ([]byte, error) {
	b := bytes.NewBufferString("")
	b.WriteString(`{"server":{`)
	/* The available images are 100-105, x-small to x-large. */
	if s.FlavorRef < 100 || s.FlavorRef > 105 {
		return []byte{},
			errors.New("Flavor Reference refers to a non-existant flavour.")
	} else {
		b.WriteString(fmt.Sprintf(`"flavorRef":%d`, s.FlavorRef))
	}
	if s.ImageRef == 0 {
		return []byte{},
			errors.New("An image name is required.")
	} else {
		b.WriteString(fmt.Sprintf(`,"imageRef":%d`, s.ImageRef))
	}
	if s.Name == "" {
		return []byte{},
			errors.New("A name is required")
	} else {
		b.WriteString(fmt.Sprintf(`,"name":"%s"`, s.Name))
	}

	/* Optional items */
	if s.Key != "" {
		b.WriteString(fmt.Sprintf(`,"key_name":"%s"`, s.Key))
	}
	if s.ConfigDrive {
		b.WriteString(`,"config_drive": true`)
	}
	if s.MinCount > 0 {
		b.WriteString(fmt.Sprintf(`,"min_count":%d`, s.MinCount))
	}
	if s.MaxCount > 0 {
		b.WriteString(fmt.Sprintf(`,"max_count":%d`, s.MaxCount))
	}
	if s.UserData != "" {
		/* user_data needs to be base64'd */
		newb := make([]byte, 0, len(s.UserData))
		base64.StdEncoding.Encode([]byte(s.UserData), newb)
		b.WriteString(fmt.Sprintf(`,"user_data": "%s",`, string(newb)))
	}
	/* The max size of a personality string is 255 bytes. */
	if len(s.Personality) > 255 {
		return []byte{},
			errors.New("Server's personality cannot have >255 bytes.")
	} else if s.Personality != "" {
		b.WriteString(fmt.Sprintf(`,"personality":"%s",`, s.Personality))
	}
	/* Ignore the metadata if there isn't any, it's optional. */
	if len(s.Metadata) > 0 {
		fmt.Println(len(s.Metadata))
		b.WriteString(`,"metadata":{`)
		cnt := 0
		for key, value := range s.Metadata {
			b.WriteString(fmt.Sprintf(`"%s": "%s"`, key, value))
			if cnt+1 != len(s.Metadata) {
				b.WriteString(",")
				cnt++
			} else {
				b.WriteString("}")
			}
		}
	}
	/* Ignore the Security Groups if there isn't any, it's optional. */
	if len(s.SecurityGroups) > 0 {
		b.WriteString(`,"security_groups":[`)
		cnt := 0
		for _, sg := range s.SecurityGroups {
			b.WriteString(fmt.Sprintf(`{"name": "%s"}`, sg.Name))
			if cnt+1 != len(s.SecurityGroups) {
				b.WriteString(",")
				cnt++
			} else {
				b.WriteString("]")
			}
		}
	}
	b.WriteString("}}")
	return b.Bytes(), nil
}
<commit_msg>Moved the personality check before the rest of the code. The idea is that we should fail fast rather than actually try to make a request we later can't fulfill.<commit_after>package hpcloud

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

/* Server flavours Smallest to Largest */
type Flavor int

const (
	XSmall = Flavor(100) + iota
	Small
	Medium
	Large
	XLarge
	DblXLarge
)

/* Available images */
type ServerImage int

const (
	UbuntuLucid10_04Kernel    = ServerImage(1235)
	UbuntuLucid10_04          = 1236
	UbuntuMaverick10_10Kernel = 1237
	UbuntuMaverick10_10       = 1238
	UbuntuNatty11_04Kernel    = 1239
	UbuntuNatty11_04          = 1240
	UbuntuOneiric11_10        = 5579
	UbuntuPrecise12_04        = 8419
	CentOS5_8Server64         = 54021
	CentOS6_2Server64Kernel   = 1356
	CentOS6_2Server64Ramdisk  = 1357
	CentOS6_2Server64         = 1358
	DebianSqueeze6_0_3Kernel  = 1359
	DebianSqueeze6_0_3Ramdisk = 1360
	DebianSqueeze6_0_3Server  = 1361
	Fedora16Server64          = 16291
	BitNamiDrupal7_14_0       = 22729
	BitNamiWebPack1_2_0       = 22731
	BitNamiDevPack1_0_0       = 4654
	ActiveStateStackatov1_2_6 = 14345
	ActiveStateStackatov2_2_2 = 59297
	ActiveStateStackatov2_2_3 = 60815
	EnterpriseDBPPAS9_1_2     = 9953
	EnterpriseDBPSQL9_1_3     = 9995
)

type Link struct {
	HREF string `json:"href"`
	Rel  string `json:"rel"`
}

/*
  Several embedded types are simply an ID string with a slice of Link
*/
type IDLink struct {
	Name  string `json:"name"`
	ID    string `json:"id"`
	Links []Link `json:"links"`
}

type Flavor_ struct {
	Name  string `json:"name"`
	ID    int64  `json:"id"`
	Links []Link `json:"links"`
}

type Flavors struct {
	F []Flavor_ `json:"flavors"`
}

type Image struct {
	I struct {
		Name     string            `json:"name"`
		ID       string            `json:"id"`
		Links    []Link            `json:"links"`
		Progress int               `json:"progress"`
		Metadata map[string]string `json:"metadata"`
		Status   string            `json:"status"`
		Updated  string            `json:"updated"`
	} `json:"image"`
}

type Images struct {
	I []IDLink `json:"images"`
}

/*
  This type describes the JSON data which should be sent to the create
  server resource.
*/
type Server struct {
	ConfigDrive    bool              `json:"config_drive"`
	FlavorRef      Flavor            `json:"flavorRef"`
	ImageRef       ServerImage       `json:"imageRef"`
	MaxCount       int               `json:"max_count"`
	MinCount       int               `json:"min_count"`
	Name           string            `json:"name"`
	Key            string            `json:"key_name"`
	Personality    string            `json:"personality"`
	UserData       string            `json:"user_data"`
	SecurityGroups []IDLink          `json:"security_groups"`
	Metadata       map[string]string `json:"metadata"`
}

/*
  This type describes the JSON response from a successful CreateServer
  call.
*/
type ServerResponse struct {
	S struct {
		Status         string            `json:"status"`
		Updated        string            `json:"update"`
		HostID         string            `json:"hostId"`
		UserID         string            `json:"user_id"`
		Name           string            `json:"name"`
		Links          []Link            `json:"links"`
		Addresses      interface{}       `json:"addresses"`
		TenantID       string            `json:"tenant_id"`
		Image          IDLink            `json:"image"`
		Created        string            `json:"created"`
		UUID           string            `json:"uuid"`
		AccessIPv4     string            `json:"accessIPv4"`
		AccessIPv6     string            `json:"accessIPv6"`
		KeyName        string            `json:"key_name"`
		AdminPass      string            `json:"adminPass"`
		Flavor         IDLink            `json:"flavor"`
		ConfigDrive    string            `json:"config_drive"`
		ID             int64             `json:"id"`
		SecurityGroups []IDLink          `json:"security_groups"`
		Metadata       map[string]string `json:"metadata"`
	} `json:"server"`
}

/*
  CreateServer creates a new server in the HPCloud using the
  settings found in the Server instance passed to this function.

  This function implements the interface as described in:-
  * https://docs.hpcloud.com/api/compute/
  * section 4.4.5.2 Create Server
*/
func (a Access) CreateServer(s Server) (*ServerResponse, error) {
	b, err := s.MarshalJSON()
	if err != nil {
		return nil, err
	}

	body, err := a.baseComputeRequest("servers", "POST",
		strings.NewReader(string(b)),
	)
	if err != nil {
		return nil, err
	}
	sr := &ServerResponse{}
	err = json.Unmarshal(body, sr)
	if err != nil {
		return nil, err
	}
	return sr, nil
}

/*
  DeleteServer deletes the server with the `server_id`.

  This function implements the interface described in:-
  * https://docs.hpcloud.com/api/compute/
  * Section 4.4.6.3 Delete Server
*/
func (a Access) DeleteServer(server_id string) error {
	_, err := a.baseComputeRequest(
		fmt.Sprintf("servers/%s", server_id),
		"DELETE", nil,
	)
	if err != nil {
		return err
	}
	return nil
}

/*
  RebootServer will reboot the server with the `server_id`.

  This function implements the interface described in:-
  * https://docs.hpcloud.com/api/compute/
  * Section 4.4.7.1 Reboot Server
*/
func (a Access) RebootServer(server_id string) error {
	/*
		 The docs mention that a hard reboot will be used
	     no matter what, so there's no point making a type
	     or make the type of reboot an option
	*/
	s := `{"reboot":{"type":"HARD"}}`
	_, err := a.baseComputeRequest(
		fmt.Sprintf("servers/%s/action", server_id),
		"POST", strings.NewReader(s),
	)
	if err != nil {
		return err
	}
	return nil

}

/*
  ListFlavors will list all the available flavours
  on the HPCloud compute API.
*/
func (a Access) ListFlavors() (*Flavors, error) {
	body, err := a.baseComputeRequest("flavors", "GET", nil)
	if err != nil {
		return nil, err
	}

	fl := &Flavors{}
	err = json.Unmarshal(body, fl)
	if err != nil {
		return nil, err
	}
	return fl, nil
}

func (a Access) ListImages() (*Images, error) {
	body, err := a.baseComputeRequest("images", "GET", nil)
	if err != nil {
		return nil, err
	}
	im := &Images{}
	err = json.Unmarshal(body, im)
	if err != nil {
		return nil, err
	}
	return im, nil
}

func (a Access) DeleteImage(image_id string) error {
	_, err := a.baseComputeRequest(
		fmt.Sprintf("images/%s", image_id), "DELETE", nil,
	)
	if err != nil {
		return err
	}
	return nil
}

func (a Access) ListImage(image_id string) (*Image, error) {
	body, err := a.baseComputeRequest(
		fmt.Sprintf("images/%s", image_id), "GET", nil,
	)
	if err != nil {
		return nil, err
	}
	i := &Image{}
	err = json.Unmarshal(body, i)
	if err != nil {
		return nil, err
	}
	return i, nil
}

/*
  baseComputeRequest encapsulates the main basic request
  which is done for each endpoint in the Compute API.

  In the ComputeAPI all endpoints generally succeed on
  a 200/202 return code and fail on the usual fail codes.

  We simply check for the known good return codes and return
  the body in those cases or we fail with the appropriate
  response.
*/
func (a Access) baseComputeRequest(url, method string, b io.Reader) ([]byte, error) {
	path := fmt.Sprintf("%s%s/%s", COMPUTE_URL, a.TenantID, url)
	client := &http.Client{}
	req, err := http.NewRequest(method, path, b)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", a.A.Token.ID)
	req.Header.Add("Content-type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	switch resp.StatusCode {
	case http.StatusAccepted:
	case http.StatusNonAuthoritativeInfo:
	case http.StatusOK:
		return body, nil
	case http.StatusNotFound:
		nf := &NotFound{}
		err = json.Unmarshal(body, nf)
		if err != nil {
			return nil, err
		}
		return nil, errors.New(nf.NF.Message)
	default:
		br := &BadRequest{}
		err = json.Unmarshal(body, br)
		if err != nil {
			return nil, err
		}
		return nil, errors.New(br.B.Message)
	}
	panic("Unreachable")
}

/*
  MarshalJSON implements the Marshaler interface for the
  Server type.

  We implement this interface because when creating a server
  we have optional values and since Go has zero-values and
  does *not* have configurable zero values we need to make
  sure that zero-values are converted to known good values.

  As such:
    * FlavorRef is checked if it's a valid reference.
    * Ditto for ImageRef.
    * Name cannot be blank.
    * If the key is missing, it'll not put anything in.
    * The config_drive defaults to false anyway, no need
      to send a false value.
    * Min/MaxCount are ignored if they are zero.
    * UserData is ignored if it's a blank string.
    * Personality is ignored if it's a blank string.
    * Metadata/SecurityGroups are ignored if they have len(0)
*/
func (s Server) MarshalJSON() ([]byte, error) {
	b := bytes.NewBufferString("")
	b.WriteString(`{"server":{`)
	/* The available images are 100-105, x-small to x-large. */
	if s.FlavorRef < 100 || s.FlavorRef > 105 {
		return []byte{},
			errors.New("Flavor Reference refers to a non-existant flavour.")
	} else {
		b.WriteString(fmt.Sprintf(`"flavorRef":%d`, s.FlavorRef))
	}
	if s.ImageRef == 0 {
		return []byte{},
			errors.New("An image name is required.")
	} else {
		b.WriteString(fmt.Sprintf(`,"imageRef":%d`, s.ImageRef))
	}
	if s.Name == "" {
		return []byte{},
			errors.New("A name is required")
	} else {
		b.WriteString(fmt.Sprintf(`,"name":"%s"`, s.Name))
	}

	/* Optional items */
	/* The max size of a personality string is 255 bytes. */
	if len(s.Personality) > 255 {
		return []byte{},
			errors.New("Server's personality cannot have >255 bytes.")
	} else if s.Personality != "" {
		b.WriteString(fmt.Sprintf(`,"personality":"%s",`, s.Personality))
	}
	if s.Key != "" {
		b.WriteString(fmt.Sprintf(`,"key_name":"%s"`, s.Key))
	}
	if s.ConfigDrive {
		b.WriteString(`,"config_drive": true`)
	}
	if s.MinCount > 0 {
		b.WriteString(fmt.Sprintf(`,"min_count":%d`, s.MinCount))
	}
	if s.MaxCount > 0 {
		b.WriteString(fmt.Sprintf(`,"max_count":%d`, s.MaxCount))
	}
	if s.UserData != "" {
		/* user_data needs to be base64'd */
		newb := make([]byte, 0, len(s.UserData))
		base64.StdEncoding.Encode([]byte(s.UserData), newb)
		b.WriteString(fmt.Sprintf(`,"user_data": "%s",`, string(newb)))
	}

	/* Ignore the metadata if there isn't any, it's optional. */
	if len(s.Metadata) > 0 {
		fmt.Println(len(s.Metadata))
		b.WriteString(`,"metadata":{`)
		cnt := 0
		for key, value := range s.Metadata {
			b.WriteString(fmt.Sprintf(`"%s": "%s"`, key, value))
			if cnt+1 != len(s.Metadata) {
				b.WriteString(",")
				cnt++
			} else {
				b.WriteString("}")
			}
		}
	}
	/* Ignore the Security Groups if there isn't any, it's optional. */
	if len(s.SecurityGroups) > 0 {
		b.WriteString(`,"security_groups":[`)
		cnt := 0
		for _, sg := range s.SecurityGroups {
			b.WriteString(fmt.Sprintf(`{"name": "%s"}`, sg.Name))
			if cnt+1 != len(s.SecurityGroups) {
				b.WriteString(",")
				cnt++
			} else {
				b.WriteString("]")
			}
		}
	}
	b.WriteString("}}")
	return b.Bytes(), nil
}
<|endoftext|>


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

package main

import (
	"code.google.com/p/bencode-go"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
)

// Peers dictionary model
type Peers struct {
	PeerId string "peer id"
	Ip     string
	Port   int
}

// Tracker Response
type TrackerResponse struct {
	FailureReason  string "failure reason"
	WarningMessage string "warning message"
	Interval       int
	MinInterval    int    "min interval"
	TrackerId      string "tracker id"
	Complete       int
	Incomplete     int
	Peers          string "peers"
	//TODO: Figure out how to handle dict of peers
//	Peers          []Peers "peers"
}

type Tracker struct {
	url string
	trackerResponse TrackerResponse
}

func (tr *Tracker) Announce(t *Torrent) {
	var announceUrl *url.URL

	// Select the tracker to connect to, if it's a list, select the first
	// one in the list. TODO: If no response from first tracker in list,
	// then try the next one, and so on.
	if len(t.metaInfo.AnnounceList) > 0 {
		announceUrl, _ = url.Parse(t.metaInfo.AnnounceList[0][0])
	} else {
		announceUrl, _ = url.Parse(t.metaInfo.Announce)
	}
	// TODO: Implement UDP mode
	if announceUrl.Scheme != "http" {
		log.Fatalf("URL Scheme: %s not supported\n", announceUrl.Scheme)
	}

	// FIXME: Set our real listening port
	port := 6881

	// Build and encode the Tracker Request
	trackerRequest := url.Values{}
	trackerRequest.Set("info_hash", string(t.infoHash))
	trackerRequest.Add("peer_id", string(PeerId[:]))
	trackerRequest.Add("port", string(port))
	trackerRequest.Add("uploaded", string(t.uploaded))
	trackerRequest.Add("downloaded", string(t.downloaded))
//	trackerRequest.Add("left", string(metaInfo.Info.Length))
	trackerRequest.Add("compact", "1")
	announceUrl.RawQuery = trackerRequest.Encode()

	// Make a request to the tracker
	log.Printf("Announce %s\n", announceUrl.String())
	resp, err := http.Get(announceUrl.String())
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	var trackerResponse TrackerResponse

	bencode.Unmarshal(resp.Body, &trackerResponse)
	fmt.Printf("%x\n", trackerResponse)

	// Peers in binary mode. Parse the response and decode peer IP + port
	fmt.Printf("Peers: ")
	for i := 0; i < len(trackerResponse.Peers); i += 6 {
		ip := net.IPv4(trackerResponse.Peers[i], trackerResponse.Peers[i+1], trackerResponse.Peers[i+2], trackerResponse.Peers[i+3])
		pport := uint32(trackerResponse.Peers[i+4]) << 32
		pport = pport | uint32(trackerResponse.Peers[i+5])
		// TODO: Assign result to a TCPAddr type here
		fmt.Printf("%s:%d", ip, port)
	}
	fmt.Println()
}

func (tr *Tracker) Run(t *Torrent, trackerMonitor chan bool) {
	tr.Announce(t)
	// do something, loop forever and update very interval
	trackerMonitor <- true
}
<commit_msg>Fix decoding of peer ports and pretty print tracker response<commit_after>// Copyright 2013 Jari Takkala. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"code.google.com/p/bencode-go"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
)

// Peers dictionary model
type Peers struct {
	PeerId string "peer id"
	Ip     string
	Port   int
}

// Tracker Response
type TrackerResponse struct {
	FailureReason  string "failure reason"
	WarningMessage string "warning message"
	Interval       int
	MinInterval    int    "min interval"
	TrackerId      string "tracker id"
	Complete       int
	Incomplete     int
	Peers          string "peers"
	//TODO: Figure out how to handle dict of peers
//	Peers          []Peers "peers"
}

type Tracker struct {
	url string
	trackerResponse TrackerResponse
}

func (tr *Tracker) Announce(t *Torrent) {
	var announceUrl *url.URL

	// Select the tracker to connect to, if it's a list, select the first
	// one in the list. TODO: If no response from first tracker in list,
	// then try the next one, and so on.
	if len(t.metaInfo.AnnounceList) > 0 {
		announceUrl, _ = url.Parse(t.metaInfo.AnnounceList[0][0])
	} else {
		announceUrl, _ = url.Parse(t.metaInfo.Announce)
	}
	// TODO: Implement UDP mode
	if announceUrl.Scheme != "http" {
		log.Fatalf("URL Scheme: %s not supported\n", announceUrl.Scheme)
	}

	// FIXME: Set our real listening port
	port := 6881

	// Build and encode the Tracker Request
	trackerRequest := url.Values{}
	trackerRequest.Set("info_hash", string(t.infoHash))
	trackerRequest.Add("peer_id", string(PeerId[:]))
	trackerRequest.Add("port", string(port))
	trackerRequest.Add("uploaded", string(t.uploaded))
	trackerRequest.Add("downloaded", string(t.downloaded))
//	trackerRequest.Add("left", string(metaInfo.Info.Length))
	trackerRequest.Add("compact", "1")
	announceUrl.RawQuery = trackerRequest.Encode()

	// Make a request to the tracker
	log.Printf("Announce %s\n", announceUrl.String())
	resp, err := http.Get(announceUrl.String())
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	var trackerResponse TrackerResponse

	bencode.Unmarshal(resp.Body, &trackerResponse)
	fmt.Printf("%#v\n", trackerResponse)

	// Peers in binary mode. Parse the response and decode peer IP + port
	fmt.Printf("Peers: ")
	for i := 0; i < len(trackerResponse.Peers); i += 6 {
		ip := net.IPv4(trackerResponse.Peers[i], trackerResponse.Peers[i+1], trackerResponse.Peers[i+2], trackerResponse.Peers[i+3])
		pport := uint16(trackerResponse.Peers[i+4]) << 8
		pport = pport | uint16(trackerResponse.Peers[i+5])
		// TODO: Assign result to a TCPAddr type here
		fmt.Printf("%s:%d ", ip, pport)
	}
	fmt.Println()
}

func (tr *Tracker) Run(t *Torrent, trackerMonitor chan bool) {
	tr.Announce(t)
	// do something, loop forever and update very interval
	trackerMonitor <- true
}
<|endoftext|>


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

// +build !darwin

package main

import (
	"github.com/fsnotify/fsnotify"
	"os"
	"path"
)

func trackModifications() {

	fse, err := fsnotify.NewWatcher()
	if err != nil {
		trace("error starting modifications tracker: %s", err)
		os.Exit(1)
	}
	defer fse.Close()

	go func() {
		for ev := range fse.Events {
			debug("event: %q", ev.Name)
			pendingMx.Lock()
			matchCommands(pendingCommands, path.Join(root, event.Path))
			pendingMx.Unlock()
		}
	}()

	err = fse.Add(root)
	if err != nil {
		trace("error registering the watcher path: %s", err)
	}
}
<commit_msg>fix bug in tracker<commit_after>// Copyright 2016 José Santos <henrique_1609@me.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build !darwin

package main

import (
	"github.com/fsnotify/fsnotify"
	"os"
	"path"
)

func trackModifications() {

	fse, err := fsnotify.NewWatcher()
	if err != nil {
		trace("error starting modifications tracker: %s", err)
		os.Exit(1)
	}
	defer fse.Close()

	go func() {
		for ev := range fse.Events {
			debug("event: %q", ev.Name)
			pendingMx.Lock()
			matchCommands(pendingCommands, path.Join(root, ev.Name))
			pendingMx.Unlock()
		}
	}()

	err = fse.Add(root)
	if err != nil {
		trace("error registering the watcher path: %s", err)
	}
}
<|endoftext|>


<commit_before>// Package mgostorage provides an implementation of the
// bakery Storage interface that uses MongoDB to store
// items.
package mgostorage

import (
	"github.com/juju/errgo"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"gopkg.in/macaroon-bakery.v0/bakery"
)

// New returns an implementation of Storage
// that stores all items in MongoDB.
func New(c *mgo.Collection) (*mgoStorage, error) {
	m := mgoStorage{
		col: c,
	}
	err := m.setUpCollection()
	if err != nil {
		return nil, errgo.Mask(err)
	}
	return &m, nil
}

type mgoStorage struct {
	col *mgo.Collection
}

type storageDoc struct {
	Location string `bson:"loc"`
	Item     string `bson:"item"`
}

func (s *mgoStorage) setUpCollection() error {
	collection := s.collection()
	defer collection.Close()
	err := collection.EnsureIndex(mgo.Index{Key: []string{"loc"}, Unique: true})
	if err != nil {
		return errgo.Notef(err, "failed to ensure an index on loc exists")
	}
	return nil
}

// collection returns the collection with a copied mgo session.
// It must be closed when done with.
func (m *mgoStorage) collection() collectionCloser {
	c := m.col.Database.Session.Copy().DB(m.col.Database.Name).C(m.col.Name)
	return collectionCloser{c}
}

type collectionCloser struct {
	*mgo.Collection
}

func (c collectionCloser) Close() {
	c.Collection.Database.Session.Close()
}

// Put implements bakery.Storage.Put.
func (s mgoStorage) Put(location, item string) error {
	i := storageDoc{Location: location, Item: item}

	collection := s.collection()
	defer collection.Close()

	_, err := collection.Upsert(bson.M{"loc": location}, i)
	if err != nil {
		return errgo.Notef(err, "cannot store item for location %q", location)
	}
	return nil
}

// Get implements bakery.Storage.Get.
func (s mgoStorage) Get(location string) (string, error) {
	collection := s.collection()
	defer collection.Close()

	var i storageDoc
	err := collection.Find(bson.M{"loc": location}).One(&i)
	if err != nil {
		if err == mgo.ErrNotFound {
			return "", bakery.ErrNotFound
		}
		return "", errgo.Notef(err, "cannot get %q", location)
	}

	return i.Item, nil
}

// Del implements bakery.Storage.Del.
func (s mgoStorage) Del(location string) error {
	collection := s.collection()
	defer collection.Close()

	err := collection.Remove(bson.M{"loc": location})
	if err != nil {
		return errgo.Notef(err, "cannot remove %q", location)
	}
	return nil
}
<commit_msg>bakery/mgostorage: do not return private type<commit_after>// Package mgostorage provides an implementation of the
// bakery Storage interface that uses MongoDB to store
// items.
package mgostorage

import (
	"github.com/juju/errgo"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"gopkg.in/macaroon-bakery.v0/bakery"
)

// New returns an implementation of Storage
// that stores all items in MongoDB.
func New(c *mgo.Collection) (bakery.Storage, error) {
	m := mgoStorage{
		col: c,
	}
	err := m.setUpCollection()
	if err != nil {
		return nil, errgo.Mask(err)
	}
	return &m, nil
}

type mgoStorage struct {
	col *mgo.Collection
}

type storageDoc struct {
	Location string `bson:"loc"`
	Item     string `bson:"item"`
}

func (s *mgoStorage) setUpCollection() error {
	collection := s.collection()
	defer collection.Close()
	err := collection.EnsureIndex(mgo.Index{Key: []string{"loc"}, Unique: true})
	if err != nil {
		return errgo.Notef(err, "failed to ensure an index on loc exists")
	}
	return nil
}

// collection returns the collection with a copied mgo session.
// It must be closed when done with.
func (m *mgoStorage) collection() collectionCloser {
	c := m.col.Database.Session.Copy().DB(m.col.Database.Name).C(m.col.Name)
	return collectionCloser{c}
}

type collectionCloser struct {
	*mgo.Collection
}

func (c collectionCloser) Close() {
	c.Collection.Database.Session.Close()
}

// Put implements bakery.Storage.Put.
func (s mgoStorage) Put(location, item string) error {
	i := storageDoc{Location: location, Item: item}

	collection := s.collection()
	defer collection.Close()

	_, err := collection.Upsert(bson.M{"loc": location}, i)
	if err != nil {
		return errgo.Notef(err, "cannot store item for location %q", location)
	}
	return nil
}

// Get implements bakery.Storage.Get.
func (s mgoStorage) Get(location string) (string, error) {
	collection := s.collection()
	defer collection.Close()

	var i storageDoc
	err := collection.Find(bson.M{"loc": location}).One(&i)
	if err != nil {
		if err == mgo.ErrNotFound {
			return "", bakery.ErrNotFound
		}
		return "", errgo.Notef(err, "cannot get %q", location)
	}

	return i.Item, nil
}

// Del implements bakery.Storage.Del.
func (s mgoStorage) Del(location string) error {
	collection := s.collection()
	defer collection.Close()

	err := collection.Remove(bson.M{"loc": location})
	if err != nil {
		return errgo.Notef(err, "cannot remove %q", location)
	}
	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"log"
	"net/http"

	"github.com/ponzu-cms/ponzu/system/item"

	"github.com/tidwall/gjson"
	"golang.org/x/net/http2"
)

func push(res http.ResponseWriter, req *http.Request, pt func() interface{}, data []byte) {
	// Push(target string, opts *PushOptions) error
	if pusher, ok := res.(http.Pusher); ok {
		if p, ok := pt().(item.Pushable); ok {
			// get fields to pull values from data
			fields := p.Push()

			// parse values from data to push
			values := gjson.GetManyBytes(data, fields...)

			// push all values from Pushable items' fields
			for i := range values {
				val := values[i]
				val.ForEach(func(k, v gjson.Result) bool {
					if v.String() == "null" {
						return true
					}

					err := pusher.Push(v.String(), nil)
					if err != nil && err != http2.ErrRecursivePush {
						log.Println("Error during Push of value:", v.String())
					}

					return true
				})
			}
		}
	}

}
<commit_msg>checking Error() equality on recursive push err<commit_after>package api

import (
	"log"
	"net/http"

	"github.com/ponzu-cms/ponzu/system/item"

	"github.com/tidwall/gjson"
	"golang.org/x/net/http2"
)

func push(res http.ResponseWriter, req *http.Request, pt func() interface{}, data []byte) {
	// Push(target string, opts *PushOptions) error
	if pusher, ok := res.(http.Pusher); ok {
		if p, ok := pt().(item.Pushable); ok {
			// get fields to pull values from data
			fields := p.Push()

			// parse values from data to push
			values := gjson.GetManyBytes(data, fields...)

			// push all values from Pushable items' fields
			for i := range values {
				val := values[i]
				val.ForEach(func(k, v gjson.Result) bool {
					if v.String() == "null" {
						return true
					}

					// check that the push is not to its parent URL
					if v.String() == (req.URL.Path + "?" + req.URL.RawQuery) {
						return true
					}

					err := pusher.Push(v.String(), nil)
					// check for error, "http2: recursive push not allowed"
					// and return, supressing a log message
					if err != nil && err.Error() == http2.ErrRecursivePush.Error() {
						return true
					}
					if err != nil {
						log.Println("Error during Push of value:", v.String(), err)
					}

					return true
				})
			}
		}
	}

}
<|endoftext|>


<commit_before>package backend

import (
	"errors"

	"context"

	"github.com/joiggama/money"
)

var (
	// ErrNotAnItem is what happens if you try to vend an item what not be there
	ErrNotAnItem = errors.New("Item does not exist")
	// ErrItemEmpty is if you vend something that's out
	ErrItemEmpty = errors.New("Item has no Quantity")
)

// StockItem represents an item in the vending machine
type StockItem struct {
	ID       string
	Name     string
	Quantity uint8
	Reserved uint8
	Image    string
	Price    int64
	Location uint8
}

// CanVend checks stock availability
func (item *StockItem) CanVend() bool {
	return item.Quantity != 0 && item.Quantity >= item.Reserved
}

var mFormatOptions = money.Options{"currency": "GBP"}

// FormattedPrice returns the price as a currency string
func (item *StockItem) FormattedPrice() string {
	return money.Format(float64(item.Price)/100, mFormatOptions)

}

// Stock is an interface to the vending machine (possibly could be named better)
type Stock struct {
	items map[string]*StockItem
}

func newStock() *Stock {
	return &Stock{
		items: make(map[string]*StockItem, 14),
	}
}

// GetFakeStock returns a Stock with a bunch of fake items in it
func GetFakeStock() *Stock {
	stock := newStock()

	stock.items["c9b2d770-532f-41fc-acf7-e6e682bd82d5"] = &StockItem{
		ID:       "c9b2d770-532f-41fc-acf7-e6e682bd82d5",
		Name:     "Example Item #1",
		Quantity: 5,
		Price:    1000,
		Location: 0,
		Image:    "lorem-pixel-01.jpg",
	}
	stock.items["67fd2453-a2a9-4260-949d-c0f573b4b3ab"] = &StockItem{
		ID:       "67fd2453-a2a9-4260-949d-c0f573b4b3ab",
		Name:     "Example Item #2",
		Quantity: 50,
		Price:    333,
		Location: 1,
		Image:    "lorem-pixel-02.jpg",
	}
	stock.items["47db0c90-e9ff-4b2f-a646-ef259887a89b"] = &StockItem{
		ID:       "47db0c90-e9ff-4b2f-a646-ef259887a89b",
		Name:     "Example Item #3",
		Quantity: 200,
		Price:    11133,
		Location: 2,
		Image:    "lorem-pixel-03.jpg",
	}
	stock.items["aaac9bd8-ac3f-4348-af1c-c211396e3ff9"] = &StockItem{
		ID:       "aaac9bd8-ac3f-4348-af1c-c211396e3ff9",
		Name:     "Example Item #4",
		Quantity: 5,
		Price:    50,
		Location: 3,
		Image:    "lorem-pixel-04.jpg",
	}
	stock.items["552be5d3-ed40-4a2e-bb88-f0319b5f4af1"] = &StockItem{
		ID:       "552be5d3-ed40-4a2e-bb88-f0319b5f4af1",
		Name:     "Example Item #5",
		Quantity: 5,
		Price:    100,
		Location: 4,
		Image:    "lorem-pixel-05.jpg",
	}
	stock.items["ef8e1fc5-8a5d-49fb-baba-37377beead4a"] = &StockItem{
		ID:       "ef8e1fc5-8a5d-49fb-baba-37377beead4a",
		Name:     "Example Item #6",
		Quantity: 5,
		Price:    1200,
		Location: 5,
		Image:    "lorem-pixel-06.jpg",
	}
	stock.items["0840a34d-e024-4e47-a8db-471ac97c6aae"] = &StockItem{
		ID:       "0840a34d-e024-4e47-a8db-471ac97c6aae",
		Name:     "Example Item #7",
		Quantity: 5,
		Price:    123456,
		Location: 6,
		Image:    "lorem-pixel-07.jpg",
	}
	stock.items["193599ed-d1eb-411c-9ecc-e2343256609b"] = &StockItem{
		ID:       "193599ed-d1eb-411c-9ecc-e2343256609b",
		Name:     "Example Item #8",
		Quantity: 5,
		Price:    0,
		Location: 7,
		Image:    "lorem-pixel-08.jpg",
	}
	stock.items["c0e26c01-8f37-4a23-a7e9-caf322036ba9"] = &StockItem{
		ID:       "c0e26c01-8f37-4a23-a7e9-caf322036ba9",
		Name:     "Example Item #9",
		Quantity: 5,
		Price:    -100,
		Location: 8,
		Image:    "lorem-pixel-09.jpg",
	}
	stock.items["b4d6ec03-268c-49d6-ac17-c107b3375014"] = &StockItem{
		ID:       "b4d6ec03-268c-49d6-ac17-c107b3375014",
		Name:     "Example Item #10",
		Quantity: 0,
		Price:    1000,
		Location: 9,
		Image:    "lorem-pixel-10.jpg",
	}
	stock.items["31d1fb41-6af1-4fff-ad2e-1138632a6dab"] = &StockItem{
		ID:       "31d1fb41-6af1-4fff-ad2e-1138632a6dab",
		Name:     "Example Item #11",
		Quantity: 5,
		Price:    1,
		Location: 10,
		Image:    "lorem-pixel-11.jpg",
	}
	stock.items["1bb541b6-b465-445e-8bb3-58688bf40e13"] = &StockItem{
		ID:       "1bb541b6-b465-445e-8bb3-58688bf40e13",
		Name:     "Example Item #12",
		Quantity: 8,
		Price:    100,
		Location: 11,
		Image:    "lorem-pixel-12.jpg",
	}
	return stock
}

// GetAll returns all items currently in the vending machine (available or not)
func (stock *Stock) GetAll(ctx context.Context) (items []*StockItem, err error) {
	items = make([]*StockItem, len(stock.items))
	i := 0
	for _, item := range stock.items {
		items[i] = item
		i++
	}

	return
}

// GetItem returns information specific to a single item
func (stock *Stock) GetItem(ctx context.Context, ID string) (item *StockItem, err error) {
	return stock.items[ID], nil
}
<commit_msg>Prices are always positive<commit_after>package backend

import (
	"errors"

	"context"

	"github.com/joiggama/money"
)

var (
	// ErrNotAnItem is what happens if you try to vend an item what not be there
	ErrNotAnItem = errors.New("Item does not exist")
	// ErrItemEmpty is if you vend something that's out
	ErrItemEmpty = errors.New("Item has no Quantity")
)

// StockItem represents an item in the vending machine
type StockItem struct {
	ID       string
	Name     string
	Quantity uint8
	Reserved uint8
	Image    string
	Price    uint64
	Location uint8
}

// CanVend checks stock availability
func (item *StockItem) CanVend() bool {
	return item.Quantity != 0 && item.Quantity >= item.Reserved
}

var mFormatOptions = money.Options{"currency": "GBP"}

// FormattedPrice returns the price as a currency string
func (item *StockItem) FormattedPrice() string {
	return money.Format(float64(item.Price)/100, mFormatOptions)

}

// Stock is an interface to the vending machine (possibly could be named better)
type Stock struct {
	items map[string]*StockItem
}

func newStock() *Stock {
	return &Stock{
		items: make(map[string]*StockItem, 14),
	}
}

// GetFakeStock returns a Stock with a bunch of fake items in it
func GetFakeStock() *Stock {
	stock := newStock()

	stock.items["c9b2d770-532f-41fc-acf7-e6e682bd82d5"] = &StockItem{
		ID:       "c9b2d770-532f-41fc-acf7-e6e682bd82d5",
		Name:     "Example Item #1",
		Quantity: 5,
		Price:    1000,
		Location: 0,
		Image:    "lorem-pixel-01.jpg",
	}
	stock.items["67fd2453-a2a9-4260-949d-c0f573b4b3ab"] = &StockItem{
		ID:       "67fd2453-a2a9-4260-949d-c0f573b4b3ab",
		Name:     "Example Item #2",
		Quantity: 50,
		Price:    333,
		Location: 1,
		Image:    "lorem-pixel-02.jpg",
	}
	stock.items["47db0c90-e9ff-4b2f-a646-ef259887a89b"] = &StockItem{
		ID:       "47db0c90-e9ff-4b2f-a646-ef259887a89b",
		Name:     "Example Item #3",
		Quantity: 200,
		Price:    11133,
		Location: 2,
		Image:    "lorem-pixel-03.jpg",
	}
	stock.items["aaac9bd8-ac3f-4348-af1c-c211396e3ff9"] = &StockItem{
		ID:       "aaac9bd8-ac3f-4348-af1c-c211396e3ff9",
		Name:     "Example Item #4",
		Quantity: 5,
		Price:    50,
		Location: 3,
		Image:    "lorem-pixel-04.jpg",
	}
	stock.items["552be5d3-ed40-4a2e-bb88-f0319b5f4af1"] = &StockItem{
		ID:       "552be5d3-ed40-4a2e-bb88-f0319b5f4af1",
		Name:     "Example Item #5",
		Quantity: 5,
		Price:    100,
		Location: 4,
		Image:    "lorem-pixel-05.jpg",
	}
	stock.items["ef8e1fc5-8a5d-49fb-baba-37377beead4a"] = &StockItem{
		ID:       "ef8e1fc5-8a5d-49fb-baba-37377beead4a",
		Name:     "Example Item #6",
		Quantity: 5,
		Price:    1200,
		Location: 5,
		Image:    "lorem-pixel-06.jpg",
	}
	stock.items["0840a34d-e024-4e47-a8db-471ac97c6aae"] = &StockItem{
		ID:       "0840a34d-e024-4e47-a8db-471ac97c6aae",
		Name:     "Example Item #7",
		Quantity: 5,
		Price:    123456,
		Location: 6,
		Image:    "lorem-pixel-07.jpg",
	}
	stock.items["193599ed-d1eb-411c-9ecc-e2343256609b"] = &StockItem{
		ID:       "193599ed-d1eb-411c-9ecc-e2343256609b",
		Name:     "Example Item #8",
		Quantity: 5,
		Price:    0,
		Location: 7,
		Image:    "lorem-pixel-08.jpg",
	}
	stock.items["c0e26c01-8f37-4a23-a7e9-caf322036ba9"] = &StockItem{
		ID:       "c0e26c01-8f37-4a23-a7e9-caf322036ba9",
		Name:     "Example Item #9",
		Quantity: 5,
		Price:    100,
		Location: 8,
		Image:    "lorem-pixel-09.jpg",
	}
	stock.items["b4d6ec03-268c-49d6-ac17-c107b3375014"] = &StockItem{
		ID:       "b4d6ec03-268c-49d6-ac17-c107b3375014",
		Name:     "Example Item #10",
		Quantity: 0,
		Price:    1000,
		Location: 9,
		Image:    "lorem-pixel-10.jpg",
	}
	stock.items["31d1fb41-6af1-4fff-ad2e-1138632a6dab"] = &StockItem{
		ID:       "31d1fb41-6af1-4fff-ad2e-1138632a6dab",
		Name:     "Example Item #11",
		Quantity: 5,
		Price:    1,
		Location: 10,
		Image:    "lorem-pixel-11.jpg",
	}
	stock.items["1bb541b6-b465-445e-8bb3-58688bf40e13"] = &StockItem{
		ID:       "1bb541b6-b465-445e-8bb3-58688bf40e13",
		Name:     "Example Item #12",
		Quantity: 8,
		Price:    100,
		Location: 11,
		Image:    "lorem-pixel-12.jpg",
	}
	return stock
}

// GetAll returns all items currently in the vending machine (available or not)
func (stock *Stock) GetAll(ctx context.Context) (items []*StockItem, err error) {
	items = make([]*StockItem, len(stock.items))
	i := 0
	for _, item := range stock.items {
		items[i] = item
		i++
	}

	return
}

// GetItem returns information specific to a single item
func (stock *Stock) GetItem(ctx context.Context, ID string) (item *StockItem, err error) {
	return stock.items[ID], nil
}
<|endoftext|>


<commit_before>package air

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"errors"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"time"
)

// Context represents the context of the current HTTP request. It holds request and
// response objects, path, path parameters, data and registered handler.
type Context struct {
	goContext context.Context

	Request      *Request
	Response     *Response
	PristinePath string
	ParamNames   []string
	ParamValues  []string
	Params       map[string]string
	Handler      HandlerFunc
	StatusCode   int
	Data         JSONMap
	Air          *Air
}

// newContext returns a new instance of `Context`.
func newContext(a *Air) *Context {
	return &Context{
		goContext:  context.Background(),
		Request:    newRequest(a),
		Response:   newResponse(a),
		Params:     make(map[string]string),
		Handler:    NotFoundHandler,
		StatusCode: http.StatusOK,
		Data:       make(JSONMap),
		Air:        a,
	}
}

// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
func (c *Context) Deadline() (deadline time.Time, ok bool) {
	return c.goContext.Deadline()
}

// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
func (c *Context) Done() <-chan struct{} {
	return c.goContext.Done()
}

// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
func (c *Context) Err() error {
	return c.goContext.Err()
}

// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
func (c *Context) Value(key interface{}) interface{} {
	return c.goContext.Value(key)
}

// SetValue sets request-scoped value into the context.
func (c *Context) SetValue(key interface{}, val interface{}) {
	c.goContext = context.WithValue(c.goContext, key, val)
}

// QueryParam returns the query param for the provided name. It is an alias
// for `URI#QueryParam()`.
func (c *Context) QueryParam(name string) string {
	return c.Request.URI.QueryParam(name)
}

// QueryParams returns the query parameters as map. It is an alias for
// `URI#QueryParams()`.
func (c *Context) QueryParams() map[string][]string {
	return c.Request.URI.QueryParams()
}

// FormValue returns the form field value for the provided name. It is an
// alias for `Request#FormValue()`.
func (c *Context) FormValue(name string) string {
	return c.Request.FormValue(name)
}

// FormParams returns the form parameters as map.
// It is an alias for `Request#FormParams()`.
func (c *Context) FormParams() map[string][]string {
	return c.Request.FormParams()
}

// FormFile returns the multipart form file for the provided name. It is an
// alias for `Request#FormFile()`.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
	return c.Request.FormFile(name)
}

// MultipartForm returns the multipart form.
// It is an alias for `Request#MultipartForm()`.
func (c *Context) MultipartForm() (*multipart.Form, error) {
	return c.Request.MultipartForm()
}

// Cookie returns the named cookie provided in the request.
// It is an alias for `Request#Cookie()`.
func (c *Context) Cookie(name string) (*Cookie, error) {
	return c.Request.Cookie(name)
}

// SetCookie adds a "Set-Cookie" header in HTTP response.
// It is an alias for `Response#SetCookie()`.
func (c *Context) SetCookie(cookie Cookie) {
	c.Response.SetCookie(cookie)
}

// Cookies returns the HTTP cookies sent with the request. It is an alias
// for `Request#Cookies()`.
func (c *Context) Cookies() []Cookie {
	return c.Request.Cookies()
}

// Bind binds the request body into provided type i. The default binder doe
// it based on "Content-Type" header.
func (c *Context) Bind(i interface{}) error {
	return c.Air.binder.bind(i, c)
}

// Render renders a template with `Context#Data` and `Context#Data["template"]`
// and sends a "text/html" response with `Context#StatusCode`.
func (c *Context) Render() error {
	t, ok := c.Data["template"]
	if !ok || reflect.ValueOf(t).Kind() != reflect.String {
		return errors.New("c.Data[\"template\"] not setted")
	}
	buf := &bytes.Buffer{}
	if err := c.Air.renderer.render(buf, t.(string), c); err != nil {
		return err
	}
	c.Response.Header.Set(HeaderContentType, MIMETextHTML)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write(buf.Bytes())
	return err
}

// HTML sends an HTTP response with `Context#StatusCode` and `Context#Data["html"]`.
func (c *Context) HTML() error {
	h, ok := c.Data["html"]
	if !ok || reflect.ValueOf(h).Kind() != reflect.String {
		return errors.New("c.Data[\"html\"] not setted")
	}
	c.Response.Header.Set(HeaderContentType, MIMETextHTML)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write([]byte(h.(string)))
	return err
}

// String sends a string response with `Context#StatusCode` and `Context#Data["string"]`.
func (c *Context) String() error {
	s, ok := c.Data["string"]
	if !ok || reflect.ValueOf(s).Kind() != reflect.String {
		return errors.New("c.Data[\"string\"] not setted")
	}
	c.Response.Header.Set(HeaderContentType, MIMETextPlain)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write([]byte(s.(string)))
	return err
}

// JSON sends a JSON response with `Context#StatusCode` and `Context#Data["json"]`.
func (c *Context) JSON() error {
	j, ok := c.Data["json"]
	if !ok {
		return errors.New("c.Data[\"json\"] not setted")
	}
	b, err := json.Marshal(j)
	if c.Air.Config.DebugMode {
		b, err = json.MarshalIndent(j, "", "\t")
	}
	if err != nil {
		return err
	}
	return c.JSONBlob(b)
}

// JSONBlob sends a JSON blob response with `Context#StatusCode`.
func (c *Context) JSONBlob(b []byte) error {
	return c.Blob(MIMEApplicationJSON, b)
}

// JSONP sends a JSONP response with `Context#StatusCode` and `Context#Data["jsonp"]`.
// It uses `Context#Data["callback"]` to construct the JSONP payload.
func (c *Context) JSONP() error {
	j, jok := c.Data["jsonp"]
	if !jok {
		return errors.New("c.Data[\"jsonp\"] not setted")
	}
	b, err := json.Marshal(j)
	if err != nil {
		return err
	}
	return c.JSONPBlob(b)
}

// JSONPBlob sends a JSONP blob response with `Context#StatusCode`. It uses
// `Context#Data["callback"]` to construct the JSONP payload.
func (c *Context) JSONPBlob(b []byte) error {
	cb, cbok := c.Data["callback"]
	if !cbok || reflect.ValueOf(cb).Kind() != reflect.String {
		return errors.New("c.Data[\"callback\"] not setted")
	}
	c.Response.Header.Set(HeaderContentType, MIMEApplicationJavaScript)
	c.Response.WriteHeader(c.StatusCode)
	if _, err := c.Response.Write([]byte(cb.(string) + "(")); err != nil {
		return err
	}
	if _, err := c.Response.Write(b); err != nil {
		return err
	}
	_, err := c.Response.Write([]byte(");"))
	return err
}

// XML sends an XML response with `Context#StatusCode` and `Context#Data["xml"]`.
func (c *Context) XML() error {
	x, ok := c.Data["xml"]
	if !ok {
		return errors.New("c.Data[\"xml\"] not setted")
	}
	b, err := xml.Marshal(x)
	if c.Air.Config.DebugMode {
		b, err = xml.MarshalIndent(x, "", "\t")
	}
	if err != nil {
		return err
	}
	return c.XMLBlob(b)
}

// XMLBlob sends a XML blob response with `Context#StatusCode`.
func (c *Context) XMLBlob(b []byte) error {
	if _, err := c.Response.Write([]byte(xml.Header)); err != nil {
		return err
	}
	return c.Blob(MIMEApplicationXML, b)
}

// Blob sends a blob response with `Context#StatusCode` and contentType.
func (c *Context) Blob(contentType string, b []byte) error {
	c.Response.Header.Set(HeaderContentType, contentType)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write(b)
	return err
}

// Stream sends a streaming response with `Context#StatusCode` and contentType.
func (c *Context) Stream(contentType string, r io.Reader) error {
	c.Response.Header.Set(HeaderContentType, contentType)
	c.Response.WriteHeader(c.StatusCode)
	_, err := io.Copy(c.Response, r)
	return err
}

// File sends a response with the content of the file.
func (c *Context) File(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return ErrNotFound
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, "index.html")
		f, err = os.Open(file)
		if err != nil {
			return ErrNotFound
		}
		if fi, err = f.Stat(); err != nil {
			return err
		}
	}
	return c.ServeContent(f, fi.Name(), fi.ModTime())
}

// Attachment sends a response from `io.ReaderSeeker` as attachment, prompting
// client to save the file.
func (c *Context) Attachment(r io.ReadSeeker, name string) error {
	return c.contentDisposition(r, name, "attachment")
}

// Inline sends a response from `io.ReaderSeeker` as inline, opening the
// file in the browser.
func (c *Context) Inline(r io.ReadSeeker, name string) error {
	return c.contentDisposition(r, name, "inline")
}

// contentDisposition sends a response from `io.ReaderSeeker` as dispositionType.
func (c *Context) contentDisposition(r io.ReadSeeker, name, dispositionType string) error {
	c.Response.Header.Set(HeaderContentType, contentTypeByExtension(name))
	c.Response.Header.Set(HeaderContentDisposition, dispositionType+"; filename="+name)
	c.Response.WriteHeader(http.StatusOK)
	_, err := io.Copy(c.Response, r)
	return err
}

// NoContent sends a response with no body and a `Context#StatusCode`.
func (c *Context) NoContent() error {
	c.Response.WriteHeader(c.StatusCode)
	return nil
}

// Redirect redirects the request with `Context#StatusCode`.
func (c *Context) Redirect(uri string) error {
	if c.StatusCode < http.StatusMultipleChoices || c.StatusCode > http.StatusTemporaryRedirect {
		return ErrInvalidRedirectCode
	}
	c.Response.Header.Set(HeaderLocation, uri)
	c.Response.WriteHeader(c.StatusCode)
	return nil
}

// ServeContent sends static content from `io.Reader` and handles caching
// via "If-Modified-Since" request header. It automatically sets "Content-Type"
// and "Last-Modified" response headers.
func (c *Context) ServeContent(content io.ReadSeeker, name string, modtime time.Time) error {
	req := c.Request
	res := c.Response

	if t, err := time.Parse(http.TimeFormat, req.Header.Get(HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
		res.Header.Del(HeaderContentType)
		res.Header.Del(HeaderContentLength)
		c.StatusCode = http.StatusNotModified
		return c.NoContent()
	}

	res.Header.Set(HeaderContentType, contentTypeByExtension(name))
	res.Header.Set(HeaderLastModified, modtime.UTC().Format(http.TimeFormat))
	res.WriteHeader(http.StatusOK)
	_, err := io.Copy(res, content)
	return err
}

// reset resets the instance of `Context`.
func (c *Context) reset() {
	c.goContext = context.Background()
	c.Request.reset()
	c.Response.reset()
	c.PristinePath = ""
	c.ParamNames = c.ParamNames[:0]
	c.ParamValues = c.ParamValues[:0]
	for k := range c.Params {
		delete(c.Params, k)
	}
	c.Handler = NotFoundHandler
	c.StatusCode = http.StatusOK
	for k := range c.Data {
		delete(c.Data, k)
	}
}

// contentTypeByExtension returns the MIME type associated with the file based
// on its extension. It returns "application/octet-stream" incase MIME type is
// not found.
func contentTypeByExtension(name string) string {
	t := mime.TypeByExtension(filepath.Ext(name))
	if t == "" {
		t = MIMEOctetStream
	}
	return t
}
<commit_msg>fix: not closed file stream in `Context#File()`<commit_after>package air

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"errors"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"time"
)

// Context represents the context of the current HTTP request. It holds request and
// response objects, path, path parameters, data and registered handler.
type Context struct {
	goContext context.Context

	Request      *Request
	Response     *Response
	PristinePath string
	ParamNames   []string
	ParamValues  []string
	Params       map[string]string
	Handler      HandlerFunc
	StatusCode   int
	Data         JSONMap
	Air          *Air
}

// newContext returns a new instance of `Context`.
func newContext(a *Air) *Context {
	return &Context{
		goContext:  context.Background(),
		Request:    newRequest(a),
		Response:   newResponse(a),
		Params:     make(map[string]string),
		Handler:    NotFoundHandler,
		StatusCode: http.StatusOK,
		Data:       make(JSONMap),
		Air:        a,
	}
}

// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
func (c *Context) Deadline() (deadline time.Time, ok bool) {
	return c.goContext.Deadline()
}

// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
func (c *Context) Done() <-chan struct{} {
	return c.goContext.Done()
}

// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
func (c *Context) Err() error {
	return c.goContext.Err()
}

// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
func (c *Context) Value(key interface{}) interface{} {
	return c.goContext.Value(key)
}

// SetValue sets request-scoped value into the context.
func (c *Context) SetValue(key interface{}, val interface{}) {
	c.goContext = context.WithValue(c.goContext, key, val)
}

// QueryParam returns the query param for the provided name. It is an alias
// for `URI#QueryParam()`.
func (c *Context) QueryParam(name string) string {
	return c.Request.URI.QueryParam(name)
}

// QueryParams returns the query parameters as map. It is an alias for
// `URI#QueryParams()`.
func (c *Context) QueryParams() map[string][]string {
	return c.Request.URI.QueryParams()
}

// FormValue returns the form field value for the provided name. It is an
// alias for `Request#FormValue()`.
func (c *Context) FormValue(name string) string {
	return c.Request.FormValue(name)
}

// FormParams returns the form parameters as map.
// It is an alias for `Request#FormParams()`.
func (c *Context) FormParams() map[string][]string {
	return c.Request.FormParams()
}

// FormFile returns the multipart form file for the provided name. It is an
// alias for `Request#FormFile()`.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
	return c.Request.FormFile(name)
}

// MultipartForm returns the multipart form.
// It is an alias for `Request#MultipartForm()`.
func (c *Context) MultipartForm() (*multipart.Form, error) {
	return c.Request.MultipartForm()
}

// Cookie returns the named cookie provided in the request.
// It is an alias for `Request#Cookie()`.
func (c *Context) Cookie(name string) (*Cookie, error) {
	return c.Request.Cookie(name)
}

// SetCookie adds a "Set-Cookie" header in HTTP response.
// It is an alias for `Response#SetCookie()`.
func (c *Context) SetCookie(cookie Cookie) {
	c.Response.SetCookie(cookie)
}

// Cookies returns the HTTP cookies sent with the request. It is an alias
// for `Request#Cookies()`.
func (c *Context) Cookies() []Cookie {
	return c.Request.Cookies()
}

// Bind binds the request body into provided type i. The default binder doe
// it based on "Content-Type" header.
func (c *Context) Bind(i interface{}) error {
	return c.Air.binder.bind(i, c)
}

// Render renders a template with `Context#Data` and `Context#Data["template"]`
// and sends a "text/html" response with `Context#StatusCode`.
func (c *Context) Render() error {
	t, ok := c.Data["template"]
	if !ok || reflect.ValueOf(t).Kind() != reflect.String {
		return errors.New("c.Data[\"template\"] not setted")
	}
	buf := &bytes.Buffer{}
	if err := c.Air.renderer.render(buf, t.(string), c); err != nil {
		return err
	}
	c.Response.Header.Set(HeaderContentType, MIMETextHTML)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write(buf.Bytes())
	return err
}

// HTML sends an HTTP response with `Context#StatusCode` and `Context#Data["html"]`.
func (c *Context) HTML() error {
	h, ok := c.Data["html"]
	if !ok || reflect.ValueOf(h).Kind() != reflect.String {
		return errors.New("c.Data[\"html\"] not setted")
	}
	c.Response.Header.Set(HeaderContentType, MIMETextHTML)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write([]byte(h.(string)))
	return err
}

// String sends a string response with `Context#StatusCode` and `Context#Data["string"]`.
func (c *Context) String() error {
	s, ok := c.Data["string"]
	if !ok || reflect.ValueOf(s).Kind() != reflect.String {
		return errors.New("c.Data[\"string\"] not setted")
	}
	c.Response.Header.Set(HeaderContentType, MIMETextPlain)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write([]byte(s.(string)))
	return err
}

// JSON sends a JSON response with `Context#StatusCode` and `Context#Data["json"]`.
func (c *Context) JSON() error {
	j, ok := c.Data["json"]
	if !ok {
		return errors.New("c.Data[\"json\"] not setted")
	}
	b, err := json.Marshal(j)
	if c.Air.Config.DebugMode {
		b, err = json.MarshalIndent(j, "", "\t")
	}
	if err != nil {
		return err
	}
	return c.JSONBlob(b)
}

// JSONBlob sends a JSON blob response with `Context#StatusCode`.
func (c *Context) JSONBlob(b []byte) error {
	return c.Blob(MIMEApplicationJSON, b)
}

// JSONP sends a JSONP response with `Context#StatusCode` and `Context#Data["jsonp"]`.
// It uses `Context#Data["callback"]` to construct the JSONP payload.
func (c *Context) JSONP() error {
	j, jok := c.Data["jsonp"]
	if !jok {
		return errors.New("c.Data[\"jsonp\"] not setted")
	}
	b, err := json.Marshal(j)
	if err != nil {
		return err
	}
	return c.JSONPBlob(b)
}

// JSONPBlob sends a JSONP blob response with `Context#StatusCode`. It uses
// `Context#Data["callback"]` to construct the JSONP payload.
func (c *Context) JSONPBlob(b []byte) error {
	cb, cbok := c.Data["callback"]
	if !cbok || reflect.ValueOf(cb).Kind() != reflect.String {
		return errors.New("c.Data[\"callback\"] not setted")
	}
	c.Response.Header.Set(HeaderContentType, MIMEApplicationJavaScript)
	c.Response.WriteHeader(c.StatusCode)
	if _, err := c.Response.Write([]byte(cb.(string) + "(")); err != nil {
		return err
	}
	if _, err := c.Response.Write(b); err != nil {
		return err
	}
	_, err := c.Response.Write([]byte(");"))
	return err
}

// XML sends an XML response with `Context#StatusCode` and `Context#Data["xml"]`.
func (c *Context) XML() error {
	x, ok := c.Data["xml"]
	if !ok {
		return errors.New("c.Data[\"xml\"] not setted")
	}
	b, err := xml.Marshal(x)
	if c.Air.Config.DebugMode {
		b, err = xml.MarshalIndent(x, "", "\t")
	}
	if err != nil {
		return err
	}
	return c.XMLBlob(b)
}

// XMLBlob sends a XML blob response with `Context#StatusCode`.
func (c *Context) XMLBlob(b []byte) error {
	if _, err := c.Response.Write([]byte(xml.Header)); err != nil {
		return err
	}
	return c.Blob(MIMEApplicationXML, b)
}

// Blob sends a blob response with `Context#StatusCode` and contentType.
func (c *Context) Blob(contentType string, b []byte) error {
	c.Response.Header.Set(HeaderContentType, contentType)
	c.Response.WriteHeader(c.StatusCode)
	_, err := c.Response.Write(b)
	return err
}

// Stream sends a streaming response with `Context#StatusCode` and contentType.
func (c *Context) Stream(contentType string, r io.Reader) error {
	c.Response.Header.Set(HeaderContentType, contentType)
	c.Response.WriteHeader(c.StatusCode)
	_, err := io.Copy(c.Response, r)
	return err
}

// File sends a response with the content of the file.
func (c *Context) File(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return ErrNotFound
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, "index.html")
		f, err = os.Open(file)
		if err != nil {
			return ErrNotFound
		}
		defer f.Close()
		if fi, err = f.Stat(); err != nil {
			return err
		}
	}
	return c.ServeContent(f, fi.Name(), fi.ModTime())
}

// Attachment sends a response from `io.ReaderSeeker` as attachment, prompting
// client to save the file.
func (c *Context) Attachment(r io.ReadSeeker, name string) error {
	return c.contentDisposition(r, name, "attachment")
}

// Inline sends a response from `io.ReaderSeeker` as inline, opening the
// file in the browser.
func (c *Context) Inline(r io.ReadSeeker, name string) error {
	return c.contentDisposition(r, name, "inline")
}

// contentDisposition sends a response from `io.ReaderSeeker` as dispositionType.
func (c *Context) contentDisposition(r io.ReadSeeker, name, dispositionType string) error {
	c.Response.Header.Set(HeaderContentType, contentTypeByExtension(name))
	c.Response.Header.Set(HeaderContentDisposition, dispositionType+"; filename="+name)
	c.Response.WriteHeader(http.StatusOK)
	_, err := io.Copy(c.Response, r)
	return err
}

// NoContent sends a response with no body and a `Context#StatusCode`.
func (c *Context) NoContent() error {
	c.Response.WriteHeader(c.StatusCode)
	return nil
}

// Redirect redirects the request with `Context#StatusCode`.
func (c *Context) Redirect(uri string) error {
	if c.StatusCode < http.StatusMultipleChoices || c.StatusCode > http.StatusTemporaryRedirect {
		return ErrInvalidRedirectCode
	}
	c.Response.Header.Set(HeaderLocation, uri)
	c.Response.WriteHeader(c.StatusCode)
	return nil
}

// ServeContent sends static content from `io.Reader` and handles caching
// via "If-Modified-Since" request header. It automatically sets "Content-Type"
// and "Last-Modified" response headers.
func (c *Context) ServeContent(content io.ReadSeeker, name string, modtime time.Time) error {
	req := c.Request
	res := c.Response

	if t, err := time.Parse(http.TimeFormat, req.Header.Get(HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
		res.Header.Del(HeaderContentType)
		res.Header.Del(HeaderContentLength)
		c.StatusCode = http.StatusNotModified
		return c.NoContent()
	}

	res.Header.Set(HeaderContentType, contentTypeByExtension(name))
	res.Header.Set(HeaderLastModified, modtime.UTC().Format(http.TimeFormat))
	res.WriteHeader(http.StatusOK)
	_, err := io.Copy(res, content)
	return err
}

// reset resets the instance of `Context`.
func (c *Context) reset() {
	c.goContext = context.Background()
	c.Request.reset()
	c.Response.reset()
	c.PristinePath = ""
	c.ParamNames = c.ParamNames[:0]
	c.ParamValues = c.ParamValues[:0]
	for k := range c.Params {
		delete(c.Params, k)
	}
	c.Handler = NotFoundHandler
	c.StatusCode = http.StatusOK
	for k := range c.Data {
		delete(c.Data, k)
	}
}

// contentTypeByExtension returns the MIME type associated with the file based
// on its extension. It returns "application/octet-stream" incase MIME type is
// not found.
func contentTypeByExtension(name string) string {
	t := mime.TypeByExtension(filepath.Ext(name))
	if t == "" {
		t = MIMEOctetStream
	}
	return t
}
<|endoftext|>


<commit_before>package ak

import (
	"encoding/json"
	"log"
	"net/http"
	"path"
	"reflect"
	"strconv"
)

//定义方法类型
type actionFunc func(*Context)

//请求上下文
type Context struct {
	Request        *http.Request
	ResponseWriter http.ResponseWriter
	Params         map[string]string
	Data           map[string]interface{} //返回参数定义
	server         *Server
	Session        *Session
	closed         bool
}

//返回json
func (ctx *Context) WriteJson(content interface{}) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8")
	cv := reflect.ValueOf(content)
	log.Println("cv = ", cv.Type().Kind())
	if cv.Type().Kind() == reflect.String {
		ctx.ResponseWriter.Write([]byte(cv.String()))
	} else if cv.Type().Kind() == reflect.Struct || cv.Type().Kind() == reflect.Slice || cv.Type().Kind() == reflect.Ptr {
		jsonData, err := json.Marshal(content)
		if err != nil {
			panic(err)
		}
		ctx.ResponseWriter.Write(jsonData)
	}
	ctx.closed = true
}

//字符流
func (ctx *Context) WriteStream(filename string, contentType string, data []byte) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Header().Add("Content-Disposition", "attachment;filename="+filename)
	ctx.ResponseWriter.Header().Set("Content-Type", contentType)
	ctx.ResponseWriter.Header().Set("Content-Length", strconv.Itoa(len(data)))
	ctx.ResponseWriter.Write(data)
	ctx.closed = true
}

//返回html
func (ctx *Context) WriteHtml(html string) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Write([]byte(html))
	ctx.closed = true
}

//返回模板
func (ctx *Context) WriteTpl(tplName string) {
	if ctx.closed {
		return
	}
	tplPath := path.Join(ctx.server.config.tplPath, tplName)
	if !fileExists(tplPath) {
		ctx.Abort(404, tplName+" not fond")
	}
	parseTpl(ctx.ResponseWriter, tplPath, ctx.Data, ctx.server.config.leftDelim, ctx.server.config.rightDelim)
	//	s := parseTmplateToStr(tplPath)
	//	t, err := template.New("index").Funcs(template.FuncMap{"include": includeTmplate}).Parse(s)
	//	tpl, err := template.ParseFiles(tplPath)
	//	if err != nil {
	//		panic(err.Error())
	//	}
	//	t.Execute(ctx.ResponseWriter, ctx.Data)
	ctx.closed = true
}

//重定向 3xx
func (ctx *Context) Redirect(url_ string) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Header().Set("Location", url_)
	ctx.ResponseWriter.WriteHeader(301)
	ctx.ResponseWriter.Write([]byte("Redirecting to: " + url_))
	ctx.closed = true
}

//错误信息
func (ctx *Context) Abort(status int, body string) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.WriteHeader(status)
	ctx.ResponseWriter.Write([]byte(body))
	ctx.closed = true
}

//将请求数据的json格式信息转换成结构
func (ctx *Context) ParseReqBodyJson(obj interface{}) error {
	decoder := json.NewDecoder(ctx.Request.Body)
	k := reflect.ValueOf(obj).Type().Kind()
	var err error
	switch k {
	case reflect.Struct:
		err = decoder.Decode(&obj)
	case reflect.Ptr:
		err = decoder.Decode(obj)
	case reflect.Array:
		err = decoder.Decode(obj)
	case reflect.String:
		err = decoder.Decode(&obj)
	}
	return err
}
<commit_msg>fix map<commit_after>package ak

import (
	"encoding/json"
	"log"
	"net/http"
	"path"
	"reflect"
	"strconv"
)

//定义方法类型
type actionFunc func(*Context)

//请求上下文
type Context struct {
	Request        *http.Request
	ResponseWriter http.ResponseWriter
	Params         map[string]string
	Data           map[string]interface{} //返回参数定义
	server         *Server
	Session        *Session
	closed         bool
}

//返回json
func (ctx *Context) WriteJson(content interface{}) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8")
	cv := reflect.ValueOf(content)
	log.Println("cv = ", cv.Type().Kind())
	if cv.Type().Kind() == reflect.String {
		ctx.ResponseWriter.Write([]byte(cv.String()))
	} else if cv.Type().Kind() == reflect.Struct || cv.Type().Kind() == reflect.Slice || cv.Type().Kind() == reflect.Ptr || cv.Type().Kind() == reflect.Map {
		jsonData, err := json.Marshal(content)
		if err != nil {
			panic(err)
		}
		ctx.ResponseWriter.Write(jsonData)
	}
	ctx.closed = true
}

//字符流
func (ctx *Context) WriteStream(filename string, contentType string, data []byte) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Header().Add("Content-Disposition", "attachment;filename="+filename)
	ctx.ResponseWriter.Header().Set("Content-Type", contentType)
	ctx.ResponseWriter.Header().Set("Content-Length", strconv.Itoa(len(data)))
	ctx.ResponseWriter.Write(data)
	ctx.closed = true
}

//返回html
func (ctx *Context) WriteHtml(html string) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Write([]byte(html))
	ctx.closed = true
}

//返回模板
func (ctx *Context) WriteTpl(tplName string) {
	if ctx.closed {
		return
	}
	tplPath := path.Join(ctx.server.config.tplPath, tplName)
	if !fileExists(tplPath) {
		ctx.Abort(404, tplName+" not fond")
	}
	parseTpl(ctx.ResponseWriter, tplPath, ctx.Data, ctx.server.config.leftDelim, ctx.server.config.rightDelim)
	//	s := parseTmplateToStr(tplPath)
	//	t, err := template.New("index").Funcs(template.FuncMap{"include": includeTmplate}).Parse(s)
	//	tpl, err := template.ParseFiles(tplPath)
	//	if err != nil {
	//		panic(err.Error())
	//	}
	//	t.Execute(ctx.ResponseWriter, ctx.Data)
	ctx.closed = true
}

//重定向 3xx
func (ctx *Context) Redirect(url_ string) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.Header().Set("Location", url_)
	ctx.ResponseWriter.WriteHeader(301)
	ctx.ResponseWriter.Write([]byte("Redirecting to: " + url_))
	ctx.closed = true
}

//错误信息
func (ctx *Context) Abort(status int, body string) {
	if ctx.closed {
		return
	}
	ctx.ResponseWriter.WriteHeader(status)
	ctx.ResponseWriter.Write([]byte(body))
	ctx.closed = true
}

//将请求数据的json格式信息转换成结构
func (ctx *Context) ParseReqBodyJson(obj interface{}) error {
	decoder := json.NewDecoder(ctx.Request.Body)
	k := reflect.ValueOf(obj).Type().Kind()
	var err error
	switch k {
	case reflect.Struct:
		err = decoder.Decode(&obj)
	case reflect.Ptr:
		err = decoder.Decode(obj)
	case reflect.Array:
		err = decoder.Decode(obj)
	case reflect.String:
		err = decoder.Decode(&obj)
	}
	return err
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	_ "cloud.google.com/go/storage"
	"contrib.go.opencensus.io/exporter/stackdriver"
	"github.com/pkg/errors"
	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
	"go.opencensus.io/trace"
	pkgtrace "k8s.io/minikube/pkg/trace"
)

const (
	customMetricName = "custom.googleapis.com/minikube/start_time"
	profile          = "cloud-monitoring"
)

var (
	// The task latency in seconds
	latencyS = stats.Float64("repl/start_time", "start time in seconds", "s")
	labels   string
	tmpFile  string
)

func main() {
	if err := execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {
	flag.StringVar(&labels, "label", "", "Comma separated list of labels to add to metrics in key:value format")
	flag.StringVar(&tmpFile, "file", "/tmp/minikube", "Download minikube to this file for testing")
	flag.Parse()
}

func execute() error {
	projectID := os.Getenv(pkgtrace.ProjectEnvVar)
	if projectID == "" {
		return fmt.Errorf("metrics collector requires a valid GCP project id set via the %s env variable", pkgtrace.ProjectEnvVar)
	}
	ctx := context.Background()
	if err := downloadMinikube(ctx, tmpFile); err != nil {
		return errors.Wrap(err, "downloading minikube")
	}

	// Register the view. It is imperative that this step exists,
	// otherwise recorded metrics will be dropped and never exported.
	v := &view.View{
		Name:        customMetricName,
		Measure:     latencyS,
		Description: "minikube start time",
		Aggregation: view.LastValue(),
	}
	if err := view.Register(v); err != nil {
		return errors.Wrap(err, "registering view")
	}
	for _, cr := range []string{"docker", "containerd", "crio"} {
		if err := exportMinikubeStart(ctx, projectID, cr); err != nil {
			log.Printf("error exporting minikube start data for runtime %v: %v", cr, err)
		}
	}
	return nil
}

func exportMinikubeStart(ctx context.Context, projectID, containerRuntime string) error {
	sd, err := getExporter(projectID, containerRuntime)
	if err != nil {
		return errors.Wrap(err, "getting stackdriver exporter")
	}
	// Register it as a trace exporter
	trace.RegisterExporter(sd)

	if err := sd.StartMetricsExporter(); err != nil {
		return errors.Wrap(err, "starting metric exporter")
	}
	// track minikube start time and record it to metrics collector
	st, err := minikubeStartTime(ctx, projectID, tmpFile, containerRuntime)
	if err != nil {
		return errors.Wrap(err, "collecting start time")
	}
	fmt.Printf("Latency: %f\n", st)
	stats.Record(ctx, latencyS.M(st))
	time.Sleep(30 * time.Second)
	sd.Flush()
	sd.StopMetricsExporter()
	trace.UnregisterExporter(sd)
	return nil
}

func getExporter(projectID, containerRuntime string) (*stackdriver.Exporter, error) {
	return stackdriver.NewExporter(stackdriver.Options{
		ProjectID: projectID,
		// MetricPrefix helps uniquely identify your metrics.
		MetricPrefix: "minikube_start",
		// ReportingInterval sets the frequency of reporting metrics
		// to stackdriver backend.
		ReportingInterval:       1 * time.Second,
		DefaultMonitoringLabels: getLabels(containerRuntime),
	})
}

func getLabels(containerRuntime string) *stackdriver.Labels {
	l := &stackdriver.Labels{}
	l.Set("container-runtime", containerRuntime, "container-runtime")
	if labels == "" {
		return l
	}
	separated := strings.Split(labels, ",")
	for _, s := range separated {
		sep := strings.Split(s, ":")
		if len(sep) != 2 {
			continue
		}
		log.Printf("Adding label %s=%s to metrics...", sep[0], sep[1])
		l.Set(sep[0], sep[1], "")
	}
	return l
}

func minikubeStartTime(ctx context.Context, projectID, minikubePath, containerRuntime string) (float64, error) {
	defer deleteMinikube(ctx, minikubePath)

	cmd := exec.CommandContext(ctx, minikubePath, "start", "--driver=docker", "-p", profile, "--memory=2048", "--trace=gcp", fmt.Sprintf("--container-runtime=%s", containerRuntime))
	cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", pkgtrace.ProjectEnvVar, projectID))
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr

	t := time.Now()
	log.Printf("Running [%v]....", cmd.Args)
	if err := cmd.Run(); err != nil {
		return 0, errors.Wrapf(err, "running %v", cmd.Args)
	}
	totalTime := time.Since(t).Seconds()
	return totalTime, nil
}

func deleteMinikube(ctx context.Context, minikubePath string) {
	cmd := exec.CommandContext(ctx, minikubePath, "delete", "-p", profile)
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Printf("error deleting: %v", err)
	}
}
<commit_msg>fix metrics reporting<commit_after>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	_ "cloud.google.com/go/storage"
	"contrib.go.opencensus.io/exporter/stackdriver"
	"github.com/pkg/errors"
	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
	"go.opencensus.io/trace"
	pkgtrace "k8s.io/minikube/pkg/trace"
)

const (
	customMetricName = "custom.googleapis.com/minikube/start_time"
	profile          = "cloud-monitoring"
)

var (
	// The task latency in seconds
	latencyS = stats.Float64("repl/start_time", "start time in seconds", "s")
	labels   string
	tmpFile  string
)

func main() {
	if err := execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {
	flag.StringVar(&labels, "label", "", "Comma separated list of labels to add to metrics in key:value format")
	flag.StringVar(&tmpFile, "file", "/tmp/minikube", "Download minikube to this file for testing")
	flag.Parse()
}

func execute() error {
	projectID := os.Getenv(pkgtrace.ProjectEnvVar)
	if projectID == "" {
		return fmt.Errorf("metrics collector requires a valid GCP project id set via the %s env variable", pkgtrace.ProjectEnvVar)
	}
	ctx := context.Background()
	if err := downloadMinikube(ctx, tmpFile); err != nil {
		return errors.Wrap(err, "downloading minikube")
	}

	// Register the view. It is imperative that this step exists,
	// otherwise recorded metrics will be dropped and never exported.
	v := &view.View{
		Name:        customMetricName,
		Measure:     latencyS,
		Description: "minikube start time",
		Aggregation: view.LastValue(),
	}
	if err := view.Register(v); err != nil {
		return errors.Wrap(err, "registering view")
	}
	for _, cr := range []string{"docker", "containerd", "crio"} {
		if err := exportMinikubeStart(ctx, projectID, cr); err != nil {
			log.Printf("error exporting minikube start data for runtime %v: %v", cr, err)
		}
	}
	return nil
}

func exportMinikubeStart(ctx context.Context, projectID, containerRuntime string) error {
	sd, err := getExporter(projectID, containerRuntime)
	if err != nil {
		return errors.Wrap(err, "getting stackdriver exporter")
	}
	// Register it as a trace exporter
	trace.RegisterExporter(sd)

	if err := sd.StartMetricsExporter(); err != nil {
		return errors.Wrap(err, "starting metric exporter")
	}
	// track minikube start time and record it to metrics collector
	st, err := minikubeStartTime(ctx, projectID, tmpFile, containerRuntime)
	if err != nil {
		return errors.Wrap(err, "collecting start time")
	}
	fmt.Printf("Latency: %f\n", st)
	stats.Record(ctx, latencyS.M(st))
	time.Sleep(30 * time.Second)
	sd.Flush()
	sd.StopMetricsExporter()
	trace.UnregisterExporter(sd)
	return nil
}

func getExporter(projectID, containerRuntime string) (*stackdriver.Exporter, error) {
	return stackdriver.NewExporter(stackdriver.Options{
		ProjectID: projectID,
		// ReportingInterval sets the frequency of reporting metrics
		// to stackdriver backend.
		ReportingInterval:       11 * time.Second,
		DefaultMonitoringLabels: getLabels(containerRuntime),
	})
}

func getLabels(containerRuntime string) *stackdriver.Labels {
	l := &stackdriver.Labels{}
	l.Set("container-runtime", containerRuntime, "container-runtime")
	if labels == "" {
		return l
	}
	separated := strings.Split(labels, ",")
	for _, s := range separated {
		sep := strings.Split(s, ":")
		if len(sep) != 2 {
			continue
		}
		log.Printf("Adding label %s=%s to metrics...", sep[0], sep[1])
		l.Set(sep[0], sep[1], "")
	}
	return l
}

func minikubeStartTime(ctx context.Context, projectID, minikubePath, containerRuntime string) (float64, error) {
	defer deleteMinikube(ctx, minikubePath)

	cmd := exec.CommandContext(ctx, minikubePath, "start", "--driver=docker", "-p", profile, "--memory=2048", "--trace=gcp", fmt.Sprintf("--container-runtime=%s", containerRuntime))
	cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", pkgtrace.ProjectEnvVar, projectID))
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr

	t := time.Now()
	log.Printf("Running [%v]....", cmd.Args)
	if err := cmd.Run(); err != nil {
		return 0, errors.Wrapf(err, "running %v", cmd.Args)
	}
	totalTime := time.Since(t).Seconds()
	return totalTime, nil
}

func deleteMinikube(ctx context.Context, minikubePath string) {
	cmd := exec.CommandContext(ctx, minikubePath, "delete", "-p", profile)
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Printf("error deleting: %v", err)
	}
}
<|endoftext|>


<commit_before>package rest

import (
	"encoding/json"
	"net/http"
	"net/url"
	"strconv"
)

func NewContext() *Context {
	c := new(Context)
	c.params = &urlValues{&url.Values{}}

	// Default "Decode" method
	c.Decode = func(out interface{}) error {
		defer c.req.Body.Close()
		return json.NewDecoder(c.req.Body).Decode(out)
	}

	return c
}

type urlValues struct {
	*url.Values
}

func (self *urlValues) Int(key string) int {
	i, _ := strconv.Atoi(self.Get(key))
	return i
}

type Context struct {
	req                   *http.Request
	params                *urlValues
	user                  User
	runParseMultipartForm bool
	store                 *store
	handler               *Handler
	AutoSetUser           func()
	Decode                func(out interface{}) error
}

func (self *Context) User() User {
	if self.user.Id == "" {
		self.AutoSetUser()
	}

	return self.user
}

func (self *Context) Handler() *Handler {
	return self.handler
}

func (self *Context) SetHandler(handler *Handler) {
	self.handler = handler
}

func (self *Context) SetUser(user User) {
	self.user = user
}

func (self *Context) Store() *store {
	if self.store == nil {
		self.store = newStore()
	}

	return self.store
}

func (self *Context) SetReq(req *http.Request) {
	self.req = req
}

func (self *Context) Req() *http.Request {
	return self.req
}

func (self *Context) initParams() {
	if self.runParseMultipartForm {
		return
	}

	// 32m
	self.req.ParseMultipartForm(32 << 20)
	self.params = &urlValues{&self.req.Form}
	self.runParseMultipartForm = true
}

func (self *Context) Params() *urlValues {
	self.initParams()
	return self.params
}
<commit_msg>use Request to wrap the http.Request<commit_after>package rest

import (
	"encoding/json"
	"net"
	"net/http"
	"net/url"
	"strconv"
)

func NewContext() *Context {
	c := new(Context)
	c.params = &urlValues{&url.Values{}}

	// Default "Decode" method
	c.Decode = func(out interface{}) error {
		defer c.req.Body.Close()
		return json.NewDecoder(c.req.Body).Decode(out)
	}

	return c
}

type urlValues struct {
	*url.Values
}

func (self *urlValues) Int(key string) int {
	i, _ := strconv.Atoi(self.Get(key))
	return i
}

type Request struct {
	*http.Request
}

func (self *Request) Ip() string {
	ip, _, _ := net.SplitHostPort(self.RemoteAddr)
	return ip
}

type Context struct {
	req                   *Request
	params                *urlValues
	user                  User
	runParseMultipartForm bool
	store                 *store
	handler               *Handler
	AutoSetUser           func()
	Decode                func(out interface{}) error
}

func (self *Context) User() User {
	if self.user.Id == "" {
		self.AutoSetUser()
	}

	return self.user
}

func (self *Context) Handler() *Handler {
	return self.handler
}

func (self *Context) SetHandler(handler *Handler) {
	self.handler = handler
}

func (self *Context) SetUser(user User) {
	self.user = user
}

func (self *Context) Store() *store {
	if self.store == nil {
		self.store = newStore()
	}

	return self.store
}

func (self *Context) SetReq(req *http.Request) {
	self.req = &Request{
		Request: req,
	}
}

func (self *Context) Req() *Request {
	return self.req
}

func (self *Context) initParams() {
	if self.runParseMultipartForm {
		return
	}

	// 32m
	self.req.ParseMultipartForm(32 << 20)
	self.params = &urlValues{&self.req.Form}
	self.runParseMultipartForm = true
}

func (self *Context) Params() *urlValues {
	self.initParams()
	return self.params
}
<|endoftext|>


<commit_before>//Package nzb provides a function for parsing NZB files.
package nzb

import (
	"bufio"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"unicode/utf8"
)

//Segment represents a single segment in a file.
type Segment struct {
	//The amount of bytes
	Bytes int `xml:"bytes,attr"`
	//The sequence number
	Number int `xml:"number,attr"`
	//The Message-ID
	MsgId string `xml:",chardata"`
}

//File represents a single file in the NZB file.
type File struct {
	//The person who posted this to usenet
	Poster string `xml:"poster,attr"`
	//The date of the post in unix time
	Date int `xml:"date,attr"`
	//A subject line. This normally contains the filename
	Subject string `xml:"subject,attr"`
	//Which groups it was posted on
	Groups []string `xml:"groups>group"`
	//The Segments comprising this file
	Segments []Segment `xml:"segments>segment"`
}

//Nzb represents the top level for a NZB file
//It's just a dumb struct to contain all the files.
type Nzb struct {
	//The files described in the NZB file
	File []File `xml:"file"`
}

//Parse parses an nzb document from the reader and returns
//a Nzb struct and an error if any.
func Parse(r io.Reader) (n *Nzb, err error) {
	parser := xml.NewDecoder(r)
	parser.CharsetReader = charsetter

	n = new(Nzb)
	err = parser.DecodeElement(n, nil)
	if err != nil {
		err = errors.New(fmt.Sprintf("Could not parse NZB XML: %s", err.Error()))
		return nil, err
	}
	err = validate(n)
	if err != nil {
		err = errors.New(fmt.Sprintf("Invalid NZB file: %s", err.Error()))
		return nil, err
	}
	return n, nil
}

type charsetReader struct {
	reader    *bufio.Reader
	remainder []byte
}

func newCharsetReader(input io.Reader) *charsetReader {
	r := &charsetReader{reader: bufio.NewReader(input)}
	return r

}

func (c *charsetReader) Read(b []byte) (n int, err error) {
	//TODO: This is broken for reads smaller than utf8.UTFMax
	if c.remainder != nil {
		n = copy(b, c.remainder)
		c.remainder = nil
	}
	bs := b[n:]
	bsLen := len(bs)
	var char byte
	runeBuf := make([]byte, utf8.UTFMax)
	for bi := 0; bi < bsLen; {
		char, err = c.reader.ReadByte()
		if err != nil {
			return n, err
		}
		rSize := utf8.EncodeRune(runeBuf, rune(char))
		if bi+rSize > bsLen {
			c.remainder = runeBuf[:rSize]
			return n, err
		}
		copy(bs[bi:], runeBuf[:rSize])
		n += rSize
		bi += rSize
	}
	return n, err
}

//nzb files are almost always in iso8859-1, so provide a converter so that
//go's utf-8 world can handle it
func charsetter(charset string, input io.Reader) (r io.Reader, err error) {
	if charset != "iso-8859-1" {
		err = errors.New("Cannot handle charset")
		return
	}
	r = newCharsetReader(input)
	return r, err
}

func validate(n *Nzb) error {
	if len(n.File) < 1 {
		return errors.New("No files contained")
	}
	for _, file := range n.File {
		if len(file.Segments) < 1 {
			return errors.New("Zero segment file")
		}
	}
	return nil
}
<commit_msg>Better handling of alternate charsets<commit_after>//Package nzb provides a function for parsing NZB files.
package nzb

import (
	"bufio"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"strings"
	"unicode/utf8"
)

//Segment represents a single segment in a file.
type Segment struct {
	//The amount of bytes
	Bytes int `xml:"bytes,attr"`
	//The sequence number
	Number int `xml:"number,attr"`
	//The Message-ID
	MsgId string `xml:",chardata"`
}

//File represents a single file in the NZB file.
type File struct {
	//The person who posted this to usenet
	Poster string `xml:"poster,attr"`
	//The date of the post in unix time
	Date int `xml:"date,attr"`
	//A subject line. This normally contains the filename
	Subject string `xml:"subject,attr"`
	//Which groups it was posted on
	Groups []string `xml:"groups>group"`
	//The Segments comprising this file
	Segments []Segment `xml:"segments>segment"`
}

//Nzb represents the top level for a NZB file
//It's just a dumb struct to contain all the files.
type Nzb struct {
	//The files described in the NZB file
	File []File `xml:"file"`
}

//Parse parses an nzb document from the reader and returns
//a Nzb struct and an error if any.
func Parse(r io.Reader) (n *Nzb, err error) {
	parser := xml.NewDecoder(r)
	parser.CharsetReader = charsetter

	n = new(Nzb)
	err = parser.DecodeElement(n, nil)
	if err != nil {
		err = errors.New(fmt.Sprintf("Could not parse NZB XML: %s", err.Error()))
		return nil, err
	}
	err = validate(n)
	if err != nil {
		err = errors.New(fmt.Sprintf("Invalid NZB file: %s", err.Error()))
		return nil, err
	}
	return n, nil
}

type charsetReader struct {
	reader    *bufio.Reader
	remainder []byte
}

func newCharsetReader(input io.Reader) *charsetReader {
	r := &charsetReader{reader: bufio.NewReader(input)}
	return r

}

func (c *charsetReader) Read(b []byte) (n int, err error) {
	//TODO: This is broken for reads smaller than utf8.UTFMax
	if c.remainder != nil {
		n = copy(b, c.remainder)
		c.remainder = nil
	}
	bs := b[n:]
	bsLen := len(bs)
	var char byte
	runeBuf := make([]byte, utf8.UTFMax)
	for bi := 0; bi < bsLen; {
		char, err = c.reader.ReadByte()
		if err != nil {
			return n, err
		}
		rSize := utf8.EncodeRune(runeBuf, rune(char))
		if bi+rSize > bsLen {
			c.remainder = runeBuf[:rSize]
			return n, err
		}
		copy(bs[bi:], runeBuf[:rSize])
		n += rSize
		bi += rSize
	}
	return n, err
}

//nzb files are almost always in iso8859-1, so provide a converter so that
//go's utf-8 world can handle it
func charsetter(charset string, input io.Reader) (r io.Reader, err error) {
	if strings.ToLower(charset) != "iso-8859-1" {
		err = errors.New("Cannot handle charset")
		return
	}
	r = newCharsetReader(input)
	return r, err
}

func validate(n *Nzb) error {
	if len(n.File) < 1 {
		return errors.New("No files contained")
	}
	for _, file := range n.File {
		if len(file.Segments) < 1 {
			return errors.New("Zero segment file")
		}
	}
	return nil
}
<|endoftext|>