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

package juju

import (
	"github.com/globocom/tsuru/heal"
	"github.com/globocom/tsuru/log"
	"os/exec"
	"strings"
)

func init() {
	heal.Register("bootstrap", &BootstrapMachineHealer{})
	heal.Register("bootstrap-provision", &BootstrapProvisionHealer{})
}

// BootstrapProvisionHealer is an import for the Healer interface. For more
// details on how a healer work, check the documentation of the heal package.
type BootstrapProvisionHealer struct{}

func (h *BootstrapProvisionHealer) NeedsHeal() bool {
	return false
}

func (h *BootstrapProvisionHealer) Heal() error {
	return nil
}

// BootstrapMachineHealer is an implementation for the Healer interface. For more
// details on how a healer work, check the documentation of the heal package.
type BootstrapMachineHealer struct{}

// getBootstrapMachine returns the bootstrap machine.
func getBootstrapMachine() machine {
	p := JujuProvisioner{}
	output, _ := p.getOutput()
	// for juju bootstrap machine always is the machine 0.
	return output.Machines[0]
}

// NeedsHeal returns true if the AgentState of bootstrap machine is "not-started".
func (h *BootstrapMachineHealer) NeedsHeal() bool {
	bootstrapMachine := getBootstrapMachine()
	return bootstrapMachine.AgentState == "not-started"
}

// Heal executes the action for heal the bootstrap machine agent.
func (h *BootstrapMachineHealer) Heal() error {
	if h.NeedsHeal() {
		bootstrapMachine := getBootstrapMachine()
		args := []string{
			"-o",
			"StrictHostKeyChecking no",
			"-q",
			"-l",
			"ubuntu",
			bootstrapMachine.IpAddress,
			"sudo",
			"stop",
			"juju-machine-agent",
		}
		cmd := exec.Command("ssh", args...)
		log.Printf("Healing bootstrap juju-machine-agent (stop)")
		log.Printf(strings.Join(args, " "))
		cmd.Run()
		args = []string{
			"-o",
			"StrictHostKeyChecking no",
			"-q",
			"-l",
			"ubuntu",
			bootstrapMachine.IpAddress,
			"sudo",
			"start",
			"juju-machine-agent",
		}
		cmd = exec.Command("ssh", args...)
		log.Printf("Healing bootstrap juju-machine-agent (start)")
		log.Printf(strings.Join(args, " "))
		return cmd.Run()
	}
	log.Printf("Bootstrap juju-machine-agent needs no cure, skipping...")
	return nil
}
<commit_msg>refactored bootstra machine heal.<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package juju

import (
	"github.com/globocom/tsuru/heal"
	"github.com/globocom/tsuru/log"
	"os/exec"
	"strings"
)

func init() {
	heal.Register("bootstrap", &BootstrapMachineHealer{})
	heal.Register("bootstrap-provision", &BootstrapProvisionHealer{})
}

// BootstrapProvisionHealer is an import for the Healer interface. For more
// details on how a healer work, check the documentation of the heal package.
type BootstrapProvisionHealer struct{}

func (h *BootstrapProvisionHealer) NeedsHeal() bool {
	return false
}

func (h *BootstrapProvisionHealer) Heal() error {
	return nil
}

// BootstrapMachineHealer is an implementation for the Healer interface. For more
// details on how a healer work, check the documentation of the heal package.
type BootstrapMachineHealer struct{}

// getBootstrapMachine returns the bootstrap machine.
func getBootstrapMachine() machine {
	p := JujuProvisioner{}
	output, _ := p.getOutput()
	// for juju bootstrap machine always is the machine 0.
	return output.Machines[0]
}

// NeedsHeal returns true if the AgentState of bootstrap machine is "not-started".
func (h *BootstrapMachineHealer) NeedsHeal() bool {
	bootstrapMachine := getBootstrapMachine()
	return bootstrapMachine.AgentState == "not-started"
}

func upStartCmd(cmd, daemon, machine string) error {
	args := []string{
		"-o",
		"StrictHostKeyChecking no",
		"-q",
		"-l",
		"ubuntu",
		machine,
		"sudo",
		cmd,
		daemon,
	}
	log.Printf(strings.Join(args, " "))
	c := exec.Command("ssh", args...)
	return c.Run()
}

// Heal executes the action for heal the bootstrap machine agent.
func (h *BootstrapMachineHealer) Heal() error {
	if h.NeedsHeal() {
		bootstrapMachine := getBootstrapMachine()
		log.Printf("Healing bootstrap juju-machine-agent")
		upStartCmd("stop", "juju-machine-agent", bootstrapMachine.IpAddress)
		return upStartCmd("start", "juju-machine-agent", bootstrapMachine.IpAddress)
	}
	log.Printf("Bootstrap juju-machine-agent needs no cure, skipping...")
	return nil
}
<|endoftext|>


<commit_before>package cloudformation

import (
	"encoding/json"
	"strconv"
)

// IntegerExpr is a integer expression. If the value is computed then
// Func will be non-nill. If it is a literal constant integer then
// the Literal gives the value. Typically instances of this function
// are created by Integer() Ex:
//
//   type LocalBalancer struct {
//     Timeout *IntegerExpr
//   }
//
//   lb := LocalBalancer{Timeout: Integer(300)}
//
type IntegerExpr struct {
	Func    IntegerFunc
	Literal int
}

// MarshalJSON returns a JSON representation of the object
func (x IntegerExpr) MarshalJSON() ([]byte, error) {
	if x.Func != nil {
		return json.Marshal(x.Func)
	}
	return json.Marshal(x.Literal)
}

// UnmarshalJSON sets the object from the provided JSON representation
func (x *IntegerExpr) UnmarshalJSON(data []byte) error {
	var v int
	err := json.Unmarshal(data, &v)
	if err == nil {
		x.Func = nil
		x.Literal = v
		return nil
	}

	// Cloudformation allows int values to be represented as strings
	var strValue string
	if err := json.Unmarshal(data, &strValue); err == nil {
		if v, err := strconv.ParseInt(strValue, 10, 64); err == nil {
			x.Func = nil
			x.Literal = int(v)
			return nil
		}
	}

	// Perhaps we have a serialized function call (like `{"Ref": "Foo"}`)
	// so we'll try to unmarshal it with UnmarshalFunc. Not all Funcs also
	// implement IntegerFunc, so we have to make sure that the referenced
	// function actually works in the intean context
	funcCall, err2 := unmarshalFunc(data)
	if err2 == nil {
		intFunc, ok := funcCall.(IntegerFunc)
		if ok {
			x.Func = intFunc
			return nil
		}
	} else if unknownFunctionErr, ok := err2.(UnknownFunctionError); ok {
		return unknownFunctionErr
	}

	// Return the original error trying to unmarshal the literal expression,
	// which will be the most expressive.
	return err
}

// Integer returns a new IntegerExpr representing the literal value v.
func Integer(v int) *IntegerExpr {
	return &IntegerExpr{Literal: v}
}
<commit_msg>Add Integer32, Integer64 IntegerExpr functions & use `int64` for Literal type.<commit_after>package cloudformation

import (
	"encoding/json"
	"strconv"
)

// IntegerExpr is a integer expression. If the value is computed then
// Func will be non-nill. If it is a literal constant integer then
// the Literal gives the value. Typically instances of this function
// are created by Integer() Ex:
//
//   type LocalBalancer struct {
//     Timeout *IntegerExpr
//   }
//
//   lb := LocalBalancer{Timeout: Integer(300)}
//
type IntegerExpr struct {
	Func    IntegerFunc
	Literal int64
}

// MarshalJSON returns a JSON representation of the object
func (x IntegerExpr) MarshalJSON() ([]byte, error) {
	if x.Func != nil {
		return json.Marshal(x.Func)
	}
	return json.Marshal(x.Literal)
}

// UnmarshalJSON sets the object from the provided JSON representation
func (x *IntegerExpr) UnmarshalJSON(data []byte) error {
	var v int64
	err := json.Unmarshal(data, &v)
	if err == nil {
		x.Func = nil
		x.Literal = v
		return nil
	}

	// Cloudformation allows int values to be represented as strings
	var strValue string
	if err := json.Unmarshal(data, &strValue); err == nil {
		if v, err := strconv.ParseInt(strValue, 10, 64); err == nil {
			x.Func = nil
			x.Literal = v
			return nil
		}
	}

	// Perhaps we have a serialized function call (like `{"Ref": "Foo"}`)
	// so we'll try to unmarshal it with UnmarshalFunc. Not all Funcs also
	// implement IntegerFunc, so we have to make sure that the referenced
	// function actually works in the intean context
	funcCall, err2 := unmarshalFunc(data)
	if err2 == nil {
		intFunc, ok := funcCall.(IntegerFunc)
		if ok {
			x.Func = intFunc
			return nil
		}
	} else if unknownFunctionErr, ok := err2.(UnknownFunctionError); ok {
		return unknownFunctionErr
	}

	// Return the original error trying to unmarshal the literal expression,
	// which will be the most expressive.
	return err
}

// Integer returns a new IntegerExpr representing the literal value v.
func Integer(v int) *IntegerExpr {
	return &IntegerExpr{Literal: int64(v)}
}

// Integer32 returns a new IntegerExpr representing the literal value v.
func Integer32(v int32) *IntegerExpr {
	return &IntegerExpr{Literal: int64(v)}
}

// Integer64 returns a new IntegerExpr representing the literal value v.
func Integer64(v int64) *IntegerExpr {
	return &IntegerExpr{Literal: v}
}
<|endoftext|>


<commit_before>package ip17mon

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io/ioutil"
	"net"
)

const Null = "N/A"

var (
	ErrInvalidIp = errors.New("invalid ip format")
	std          *Locator
)

// Init defaut locator with dataFile
func Init(dataFile string) (err error) {
	if std != nil {
		return
	}
	std, err = NewLocator(dataFile)
	return
}

// Init defaut locator with data
func InitWithData(data []byte) {
	if std != nil {
		return
	}
	std = NewLocatorWithData(data)
	return
}

// Find locationInfo by ip string
// It will return err when ipstr is not a valid format
func Find(ipstr string) (*LocationInfo, error) {
	return std.Find(ipstr)
}

// Find locationInfo by uint32
func FindByUint(ip uint32) *LocationInfo {
	return std.FindByUint(ip)
}

//-----------------------------------------------------------------------------

// New locator with dataFile
func NewLocator(dataFile string) (loc *Locator, err error) {
	data, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return
	}
	loc = NewLocatorWithData(data)
	return
}

// New locator with data
func NewLocatorWithData(data []byte) (loc *Locator) {
	loc = new(Locator)
	loc.init(data)
	return
}

type Locator struct {
	textData   []byte
	indexData1 []uint32
	indexData2 []int
	indexData3 []int
	index      []int
}

type LocationInfo struct {
	Country string
	Region  string
	City    string
	Isp     string
}

// Find locationInfo by ip string
// It will return err when ipstr is not a valid format
func (loc *Locator) Find(ipstr string) (info *LocationInfo, err error) {
	ip := net.ParseIP(ipstr)
	if ip == nil {
		err = ErrInvalidIp
		return
	}
	info = loc.FindByUint(binary.BigEndian.Uint32([]byte(ip.To4())))
	return
}

// Find locationInfo by uint32
func (loc *Locator) FindByUint(ip uint32) (info *LocationInfo) {
	end := len(loc.indexData1) - 1
	if ip>>24 != 0xff {
		end = loc.index[(ip>>24)+1]
	}
	idx := loc.findIndexOffset(ip, loc.index[ip>>24], end)
	off := loc.indexData2[idx]
	return newLocationInfo(loc.textData[off : off+loc.indexData3[idx]])
}

// binary search
func (loc *Locator) findIndexOffset(ip uint32, start, end int) int {
	for start < end {
		mid := (start + end) / 2
		if ip > loc.indexData1[mid] {
			start = mid + 1
		} else {
			end = mid
		}
	}

	if loc.indexData1[end] >= ip {
		return end
	}

	return start
}

func (loc *Locator) init(data []byte) {
	textoff := int(binary.BigEndian.Uint32(data[:4]))

	loc.textData = data[textoff-1024:]

	loc.index = make([]int, 256)
	for i := 0; i < 256; i++ {
		off := 4 + i*4
		loc.index[i] = int(binary.LittleEndian.Uint32(data[off : off+4]))
	}

	nidx := (textoff - 4 - 1024 - 1024) / 8

	loc.indexData1 = make([]uint32, nidx)
	loc.indexData2 = make([]int, nidx)
	loc.indexData3 = make([]int, nidx)

	for i := 0; i < nidx; i++ {
		off := 4 + 1024 + i*8
		loc.indexData1[i] = binary.BigEndian.Uint32(data[off : off+4])
		loc.indexData2[i] = int(uint32(data[off+4]) | uint32(data[off+5])<<8 | uint32(data[off+6])<<16)
		loc.indexData3[i] = int(data[off+7])
	}
	return
}

func newLocationInfo(str []byte) *LocationInfo {

	var info *LocationInfo

	fields := bytes.Split(str, []byte("\t"))
	switch len(fields) {
	case 4:
		// free version
		info = &LocationInfo{
			Country: string(fields[0]),
			Region:  string(fields[1]),
			City:    string(fields[2]),
		}
	case 5:
		// pay version
		info = &LocationInfo{
			Country: string(fields[0]),
			Region:  string(fields[1]),
			City:    string(fields[2]),
			Isp:     string(fields[4]),
		}
	default:
		panic("unexpected ip info:" + string(str))
	}

	if len(info.Country) == 0 {
		info.Country = Null
	}
	if len(info.Region) == 0 {
		info.Region = Null
	}
	if len(info.City) == 0 {
		info.City = Null
	}
	if len(info.Isp) == 0 {
		info.Isp = Null
	}
	return info
}
<commit_msg>Update ip17mon.go<commit_after>package ip17mon

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io/ioutil"
	"net"
)

const Null = "N/A"

var (
	ErrInvalidIp = errors.New("invalid ip format")
	std          *Locator
)

// Init defaut locator with dataFile
func Init(dataFile string) (err error) {
	if std != nil {
		return
	}
	std, err = NewLocator(dataFile)
	return
}

// Init defaut locator with data
func InitWithData(data []byte) {
	if std != nil {
		return
	}
	std = NewLocatorWithData(data)
	return
}

// Find locationInfo by ip string
// It will return err when ipstr is not a valid format
func Find(ipstr string) (*LocationInfo, error) {
	return std.Find(ipstr)
}

// Find locationInfo by uint32
func FindByUint(ip uint32) *LocationInfo {
	return std.FindByUint(ip)
}

//-----------------------------------------------------------------------------

// New locator with dataFile
func NewLocator(dataFile string) (loc *Locator, err error) {
	data, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return
	}
	loc = NewLocatorWithData(data)
	return
}

// New locator with data
func NewLocatorWithData(data []byte) (loc *Locator) {
	loc = new(Locator)
	loc.init(data)
	return
}

type Locator struct {
	textData   []byte
	indexData1 []uint32
	indexData2 []int
	indexData3 []int
	index      []int
}

type LocationInfo struct {
	Country string
	Region  string
	City    string
	Isp     string
}

// Find locationInfo by ip string
// It will return err when ipstr is not a valid format
func (loc *Locator) Find(ipstr string) (info *LocationInfo, err error) {
	ip := net.ParseIP(ipstr).To4()
	if ip == nil {
		err = ErrInvalidIp
		return
	}
	info = loc.FindByUint(binary.BigEndian.Uint32([]byte(ip))
	return
}

// Find locationInfo by uint32
func (loc *Locator) FindByUint(ip uint32) (info *LocationInfo) {
	end := len(loc.indexData1) - 1
	if ip>>24 != 0xff {
		end = loc.index[(ip>>24)+1]
	}
	idx := loc.findIndexOffset(ip, loc.index[ip>>24], end)
	off := loc.indexData2[idx]
	return newLocationInfo(loc.textData[off : off+loc.indexData3[idx]])
}

// binary search
func (loc *Locator) findIndexOffset(ip uint32, start, end int) int {
	for start < end {
		mid := (start + end) / 2
		if ip > loc.indexData1[mid] {
			start = mid + 1
		} else {
			end = mid
		}
	}

	if loc.indexData1[end] >= ip {
		return end
	}

	return start
}

func (loc *Locator) init(data []byte) {
	textoff := int(binary.BigEndian.Uint32(data[:4]))

	loc.textData = data[textoff-1024:]

	loc.index = make([]int, 256)
	for i := 0; i < 256; i++ {
		off := 4 + i*4
		loc.index[i] = int(binary.LittleEndian.Uint32(data[off : off+4]))
	}

	nidx := (textoff - 4 - 1024 - 1024) / 8

	loc.indexData1 = make([]uint32, nidx)
	loc.indexData2 = make([]int, nidx)
	loc.indexData3 = make([]int, nidx)

	for i := 0; i < nidx; i++ {
		off := 4 + 1024 + i*8
		loc.indexData1[i] = binary.BigEndian.Uint32(data[off : off+4])
		loc.indexData2[i] = int(uint32(data[off+4]) | uint32(data[off+5])<<8 | uint32(data[off+6])<<16)
		loc.indexData3[i] = int(data[off+7])
	}
	return
}

func newLocationInfo(str []byte) *LocationInfo {

	var info *LocationInfo

	fields := bytes.Split(str, []byte("\t"))
	switch len(fields) {
	case 4:
		// free version
		info = &LocationInfo{
			Country: string(fields[0]),
			Region:  string(fields[1]),
			City:    string(fields[2]),
		}
	case 5:
		// pay version
		info = &LocationInfo{
			Country: string(fields[0]),
			Region:  string(fields[1]),
			City:    string(fields[2]),
			Isp:     string(fields[4]),
		}
	default:
		panic("unexpected ip info:" + string(str))
	}

	if len(info.Country) == 0 {
		info.Country = Null
	}
	if len(info.Region) == 0 {
		info.Region = Null
	}
	if len(info.City) == 0 {
		info.City = Null
	}
	if len(info.Isp) == 0 {
		info.Isp = Null
	}
	return info
}
<|endoftext|>


<commit_before>package goutil

// Divisors returns an integer slice of the proper divisors of an integer
func Divisors(n int) (divisors []int) {
	divisors = append(divisors, 1)
	for i := 2; i < SqrtInt(n); i++ {
		if n%i == 0 {
			divisors = append(divisors, i)
			divisors = append(divisors, n/i)
		}
	}
	return
}

// Abundant checks if an integer is an abundant integer
// An integer is abundant if the sum of its proper divisors is more than the integer
func Abundant(n int) bool {
	return Sum(Divisors(n)) > n
}

// Perfect checks if an integer is a perfect integer
// An integer is perfect if the sum of its proper divisors is equal to the integer
func Perfect(n int) bool {
	return Sum(Divisors(n)) == n
}

// Deficient checks if an integer is a deficient integer
// An integer is deficient if the sum of its proper divisors is less than the integer
func Deficient(n int) bool {
	return Sum(Divisors(n)) < n
}
<commit_msg>Improve divisors to return sorted integer array<commit_after>package goutil

// Divisors returns an integer slice of the proper divisors of an integer
func Divisors(n int) (divisors []int) {
	count := 1
	divisors = append(divisors, 1)
	for i := 2; i <= SqrtInt(n); i++ {
		if n%i == 0 {
			// Rather than sorting at the end, we can take advantage of the
			// fact that i > previous i values and n/i < previous n/i values
			// to keep the divisors array sorted
			// So, we insert i and n/i in consecutive positions after the previous
			// i value's location

			// Free up space for two more divisors
			divisors = append(divisors, 0)
			divisors = append(divisors, 0)

			// Copy the values past count back two positions
			copy(divisors[count+2:], divisors[count:])

			divisors[count] = i
			divisors[count+1] = n / i
			count++
		}
	}
	return
}

// Abundant checks if an integer is an abundant integer
// An integer is abundant if the sum of its proper divisors is more than the integer
func Abundant(n int) bool {
	return Sum(Divisors(n)) > n
}

// Perfect checks if an integer is a perfect integer
// An integer is perfect if the sum of its proper divisors is equal to the integer
func Perfect(n int) bool {
	return Sum(Divisors(n)) == n
}

// Deficient checks if an integer is a deficient integer
// An integer is deficient if the sum of its proper divisors is less than the integer
func Deficient(n int) bool {
	return Sum(Divisors(n)) < n
}
<|endoftext|>


<commit_before>package dns

import (
	"errors"
	"net"
	"strconv"
)

const hexDigit = "0123456789abcdef"

// Everything is assumed in ClassINET.

// SetReply creates a reply message from a request message.
func (dns *Msg) SetReply(request *Msg) *Msg {
	dns.Id = request.Id
	dns.Response = true
	dns.Opcode = request.Opcode
	if dns.Opcode == OpcodeQuery {
		dns.RecursionDesired = request.RecursionDesired // Copy rd bit
		dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit
	}
	dns.Rcode = RcodeSuccess
	if len(request.Question) > 0 {
		dns.Question = make([]Question, 1)
		dns.Question[0] = request.Question[0]
	}
	return dns
}

// SetQuestion creates a question message, it sets the Question
// section, generates an Id and sets the RecursionDesired (RD)
// bit to true.
func (dns *Msg) SetQuestion(z string, t uint16) *Msg {
	dns.Id = Id()
	dns.RecursionDesired = true
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, t, ClassINET}
	return dns
}

// SetNotify creates a notify message, it sets the Question
// section, generates an Id and sets the Authoritative (AA)
// bit to true.
func (dns *Msg) SetNotify(z string) *Msg {
	dns.Opcode = OpcodeNotify
	dns.Authoritative = true
	dns.Id = Id()
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, TypeSOA, ClassINET}
	return dns
}

// SetRcode creates an error message suitable for the request.
func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg {
	dns.SetReply(request)
	dns.Rcode = rcode
	return dns
}

// SetRcodeFormatError creates a message with FormError set.
func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg {
	dns.Rcode = RcodeFormatError
	dns.Opcode = OpcodeQuery
	dns.Response = true
	dns.Authoritative = false
	dns.Id = request.Id
	return dns
}

// SetUpdate makes the message a dynamic update message. It
// sets the ZONE section to: z, TypeSOA, ClassINET.
func (dns *Msg) SetUpdate(z string) *Msg {
	dns.Id = Id()
	dns.Response = false
	dns.Opcode = OpcodeUpdate
	dns.Compress = false // BIND9 cannot handle compression
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, TypeSOA, ClassINET}
	return dns
}

// SetIxfr creates message for requesting an IXFR.
func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg {
	dns.Id = Id()
	dns.Question = make([]Question, 1)
	dns.Ns = make([]RR, 1)
	s := new(SOA)
	s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0}
	s.Serial = serial
	s.Ns = ns
	s.Mbox = mbox
	dns.Question[0] = Question{z, TypeIXFR, ClassINET}
	dns.Ns[0] = s
	return dns
}

// SetAxfr creates message for requesting an AXFR.
func (dns *Msg) SetAxfr(z string) *Msg {
	dns.Id = Id()
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, TypeAXFR, ClassINET}
	return dns
}

// SetTsig appends a TSIG RR to the message.
// This is only a skeleton TSIG RR that is added as the last RR in the
// additional section. The Tsig is calculated when the message is being send.
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
	t := new(TSIG)
	t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
	t.Algorithm = algo
	t.Fudge = fudge
	t.TimeSigned = uint64(timesigned)
	t.OrigId = dns.Id
	dns.Extra = append(dns.Extra, t)
	return dns
}

// SetEdns0 appends a EDNS0 OPT RR to the message.
// TSIG should always the last RR in a message.
func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg {
	e := new(OPT)
	e.Hdr.Name = "."
	e.Hdr.Rrtype = TypeOPT
	e.SetUDPSize(udpsize)
	if do {
		e.SetDo()
	}
	dns.Extra = append(dns.Extra, e)
	return dns
}

// IsTsig checks if the message has a TSIG record as the last record
// in the additional section. It returns the TSIG record found or nil.
func (dns *Msg) IsTsig() *TSIG {
	if len(dns.Extra) > 0 {
		if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG {
			return dns.Extra[len(dns.Extra)-1].(*TSIG)
		}
	}
	return nil
}

// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0
// record in the additional section will do. It returns the OPT record
// found or nil.
func (dns *Msg) IsEdns0() *OPT {
	// EDNS0 is at the end of the additional section, start there.
	// We might want to change this to *only* look at the last two
	// records. So we see TSIG and/or OPT - this a slightly bigger
	// change though.
	for i := len(dns.Extra) - 1; i >= 0; i-- {
		if dns.Extra[i].Header().Rrtype == TypeOPT {
			return dns.Extra[i].(*OPT)
		}
	}
	return nil
}

// IsDomainName checks if s is a valid domain name, it returns the number of
// labels and true, when a domain name is valid.  Note that non fully qualified
// domain name is considered valid, in this case the last label is counted in
// the number of labels.  When false is returned the number of labels is not
// defined.  Also note that this function is extremely liberal; almost any
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
// label fits in 63 characters, but there is no length check for the entire
// string s. I.e.  a domain name longer than 255 characters is considered valid.
func IsDomainName(s string) (labels int, ok bool) {
	_, labels, err := packDomainName(s, nil, 0, compressionMap{}, false)
	return labels, err == nil
}

// IsSubDomain checks if child is indeed a child of the parent. If child and parent
// are the same domain true is returned as well.
func IsSubDomain(parent, child string) bool {
	// Entire child is contained in parent
	return CompareDomainName(parent, child) == CountLabel(parent)
}

// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet.
// The checking is performed on the binary payload.
func IsMsg(buf []byte) error {
	// Header
	if len(buf) < 12 {
		return errors.New("dns: bad message header")
	}
	// Header: Opcode
	// TODO(miek): more checks here, e.g. check all header bits.
	return nil
}

// IsFqdn checks if a domain name is fully qualified.
func IsFqdn(s string) bool {
	l := len(s)
	if l == 0 {
		return false
	}
	return s[l-1] == '.'
}

// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
// This means the RRs need to have the same type, name, and class. Returns true
// if the RR set is valid, otherwise false.
func IsRRset(rrset []RR) bool {
	if len(rrset) == 0 {
		return false
	}
	if len(rrset) == 1 {
		return true
	}
	rrHeader := rrset[0].Header()
	rrType := rrHeader.Rrtype
	rrClass := rrHeader.Class
	rrName := rrHeader.Name

	for _, rr := range rrset[1:] {
		curRRHeader := rr.Header()
		if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
			// Mismatch between the records, so this is not a valid rrset for
			//signing/verifying
			return false
		}
	}

	return true
}

// Fqdn return the fully qualified domain name from s.
// If s is already fully qualified, it behaves as the identity function.
func Fqdn(s string) string {
	if IsFqdn(s) {
		return s
	}
	return s + "."
}

// Copied from the official Go code.

// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
// address suitable for reverse DNS (PTR) record lookups or an error if it fails
// to parse the IP address.
func ReverseAddr(addr string) (arpa string, err error) {
	ip := net.ParseIP(addr)
	if ip == nil {
		return "", &Error{err: "unrecognized address: " + addr}
	}
	if v4 := ip.To4(); v4 != nil {
		buf := make([]byte, 0, net.IPv4len*4+len("in-addr.arpa."))
		// Add it, in reverse, to the buffer
		for i := len(v4) - 1; i >= 0; i-- {
			buf = strconv.AppendInt(buf, int64(v4[i]), 10)
			buf = append(buf, '.')
		}
		// Append "in-addr.arpa." and return (buf already has the final .)
		buf = append(buf, "in-addr.arpa."...)
		return string(buf), nil
	}
	// Must be IPv6
	buf := make([]byte, 0, net.IPv6len*4+len("ip6.arpa."))
	// Add it, in reverse, to the buffer
	for i := len(ip) - 1; i >= 0; i-- {
		v := ip[i]
		buf = append(buf, hexDigit[v&0xF])
		buf = append(buf, '.')
		buf = append(buf, hexDigit[v>>4])
		buf = append(buf, '.')
	}
	// Append "ip6.arpa." and return (buf already has the final .)
	buf = append(buf, "ip6.arpa."...)
	return string(buf), nil
}

// String returns the string representation for the type t.
func (t Type) String() string {
	if t1, ok := TypeToString[uint16(t)]; ok {
		return t1
	}
	return "TYPE" + strconv.Itoa(int(t))
}

// String returns the string representation for the class c.
func (c Class) String() string {
	if s, ok := ClassToString[uint16(c)]; ok {
		// Only emit mnemonics when they are unambiguous, specically ANY is in both.
		if _, ok := StringToType[s]; !ok {
			return s
		}
	}
	return "CLASS" + strconv.Itoa(int(c))
}

// String returns the string representation for the name n.
func (n Name) String() string {
	return sprintName(string(n))
}
<commit_msg>Use strings.HasSuffix for IsFqdn (#874)<commit_after>package dns

import (
	"errors"
	"net"
	"strconv"
	"strings"
)

const hexDigit = "0123456789abcdef"

// Everything is assumed in ClassINET.

// SetReply creates a reply message from a request message.
func (dns *Msg) SetReply(request *Msg) *Msg {
	dns.Id = request.Id
	dns.Response = true
	dns.Opcode = request.Opcode
	if dns.Opcode == OpcodeQuery {
		dns.RecursionDesired = request.RecursionDesired // Copy rd bit
		dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit
	}
	dns.Rcode = RcodeSuccess
	if len(request.Question) > 0 {
		dns.Question = make([]Question, 1)
		dns.Question[0] = request.Question[0]
	}
	return dns
}

// SetQuestion creates a question message, it sets the Question
// section, generates an Id and sets the RecursionDesired (RD)
// bit to true.
func (dns *Msg) SetQuestion(z string, t uint16) *Msg {
	dns.Id = Id()
	dns.RecursionDesired = true
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, t, ClassINET}
	return dns
}

// SetNotify creates a notify message, it sets the Question
// section, generates an Id and sets the Authoritative (AA)
// bit to true.
func (dns *Msg) SetNotify(z string) *Msg {
	dns.Opcode = OpcodeNotify
	dns.Authoritative = true
	dns.Id = Id()
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, TypeSOA, ClassINET}
	return dns
}

// SetRcode creates an error message suitable for the request.
func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg {
	dns.SetReply(request)
	dns.Rcode = rcode
	return dns
}

// SetRcodeFormatError creates a message with FormError set.
func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg {
	dns.Rcode = RcodeFormatError
	dns.Opcode = OpcodeQuery
	dns.Response = true
	dns.Authoritative = false
	dns.Id = request.Id
	return dns
}

// SetUpdate makes the message a dynamic update message. It
// sets the ZONE section to: z, TypeSOA, ClassINET.
func (dns *Msg) SetUpdate(z string) *Msg {
	dns.Id = Id()
	dns.Response = false
	dns.Opcode = OpcodeUpdate
	dns.Compress = false // BIND9 cannot handle compression
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, TypeSOA, ClassINET}
	return dns
}

// SetIxfr creates message for requesting an IXFR.
func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg {
	dns.Id = Id()
	dns.Question = make([]Question, 1)
	dns.Ns = make([]RR, 1)
	s := new(SOA)
	s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0}
	s.Serial = serial
	s.Ns = ns
	s.Mbox = mbox
	dns.Question[0] = Question{z, TypeIXFR, ClassINET}
	dns.Ns[0] = s
	return dns
}

// SetAxfr creates message for requesting an AXFR.
func (dns *Msg) SetAxfr(z string) *Msg {
	dns.Id = Id()
	dns.Question = make([]Question, 1)
	dns.Question[0] = Question{z, TypeAXFR, ClassINET}
	return dns
}

// SetTsig appends a TSIG RR to the message.
// This is only a skeleton TSIG RR that is added as the last RR in the
// additional section. The Tsig is calculated when the message is being send.
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
	t := new(TSIG)
	t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
	t.Algorithm = algo
	t.Fudge = fudge
	t.TimeSigned = uint64(timesigned)
	t.OrigId = dns.Id
	dns.Extra = append(dns.Extra, t)
	return dns
}

// SetEdns0 appends a EDNS0 OPT RR to the message.
// TSIG should always the last RR in a message.
func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg {
	e := new(OPT)
	e.Hdr.Name = "."
	e.Hdr.Rrtype = TypeOPT
	e.SetUDPSize(udpsize)
	if do {
		e.SetDo()
	}
	dns.Extra = append(dns.Extra, e)
	return dns
}

// IsTsig checks if the message has a TSIG record as the last record
// in the additional section. It returns the TSIG record found or nil.
func (dns *Msg) IsTsig() *TSIG {
	if len(dns.Extra) > 0 {
		if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG {
			return dns.Extra[len(dns.Extra)-1].(*TSIG)
		}
	}
	return nil
}

// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0
// record in the additional section will do. It returns the OPT record
// found or nil.
func (dns *Msg) IsEdns0() *OPT {
	// EDNS0 is at the end of the additional section, start there.
	// We might want to change this to *only* look at the last two
	// records. So we see TSIG and/or OPT - this a slightly bigger
	// change though.
	for i := len(dns.Extra) - 1; i >= 0; i-- {
		if dns.Extra[i].Header().Rrtype == TypeOPT {
			return dns.Extra[i].(*OPT)
		}
	}
	return nil
}

// IsDomainName checks if s is a valid domain name, it returns the number of
// labels and true, when a domain name is valid.  Note that non fully qualified
// domain name is considered valid, in this case the last label is counted in
// the number of labels.  When false is returned the number of labels is not
// defined.  Also note that this function is extremely liberal; almost any
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
// label fits in 63 characters, but there is no length check for the entire
// string s. I.e.  a domain name longer than 255 characters is considered valid.
func IsDomainName(s string) (labels int, ok bool) {
	_, labels, err := packDomainName(s, nil, 0, compressionMap{}, false)
	return labels, err == nil
}

// IsSubDomain checks if child is indeed a child of the parent. If child and parent
// are the same domain true is returned as well.
func IsSubDomain(parent, child string) bool {
	// Entire child is contained in parent
	return CompareDomainName(parent, child) == CountLabel(parent)
}

// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet.
// The checking is performed on the binary payload.
func IsMsg(buf []byte) error {
	// Header
	if len(buf) < 12 {
		return errors.New("dns: bad message header")
	}
	// Header: Opcode
	// TODO(miek): more checks here, e.g. check all header bits.
	return nil
}

// IsFqdn checks if a domain name is fully qualified.
func IsFqdn(s string) bool {
	return strings.HasSuffix(s, ".")
}

// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
// This means the RRs need to have the same type, name, and class. Returns true
// if the RR set is valid, otherwise false.
func IsRRset(rrset []RR) bool {
	if len(rrset) == 0 {
		return false
	}
	if len(rrset) == 1 {
		return true
	}
	rrHeader := rrset[0].Header()
	rrType := rrHeader.Rrtype
	rrClass := rrHeader.Class
	rrName := rrHeader.Name

	for _, rr := range rrset[1:] {
		curRRHeader := rr.Header()
		if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
			// Mismatch between the records, so this is not a valid rrset for
			//signing/verifying
			return false
		}
	}

	return true
}

// Fqdn return the fully qualified domain name from s.
// If s is already fully qualified, it behaves as the identity function.
func Fqdn(s string) string {
	if IsFqdn(s) {
		return s
	}
	return s + "."
}

// Copied from the official Go code.

// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
// address suitable for reverse DNS (PTR) record lookups or an error if it fails
// to parse the IP address.
func ReverseAddr(addr string) (arpa string, err error) {
	ip := net.ParseIP(addr)
	if ip == nil {
		return "", &Error{err: "unrecognized address: " + addr}
	}
	if v4 := ip.To4(); v4 != nil {
		buf := make([]byte, 0, net.IPv4len*4+len("in-addr.arpa."))
		// Add it, in reverse, to the buffer
		for i := len(v4) - 1; i >= 0; i-- {
			buf = strconv.AppendInt(buf, int64(v4[i]), 10)
			buf = append(buf, '.')
		}
		// Append "in-addr.arpa." and return (buf already has the final .)
		buf = append(buf, "in-addr.arpa."...)
		return string(buf), nil
	}
	// Must be IPv6
	buf := make([]byte, 0, net.IPv6len*4+len("ip6.arpa."))
	// Add it, in reverse, to the buffer
	for i := len(ip) - 1; i >= 0; i-- {
		v := ip[i]
		buf = append(buf, hexDigit[v&0xF])
		buf = append(buf, '.')
		buf = append(buf, hexDigit[v>>4])
		buf = append(buf, '.')
	}
	// Append "ip6.arpa." and return (buf already has the final .)
	buf = append(buf, "ip6.arpa."...)
	return string(buf), nil
}

// String returns the string representation for the type t.
func (t Type) String() string {
	if t1, ok := TypeToString[uint16(t)]; ok {
		return t1
	}
	return "TYPE" + strconv.Itoa(int(t))
}

// String returns the string representation for the class c.
func (c Class) String() string {
	if s, ok := ClassToString[uint16(c)]; ok {
		// Only emit mnemonics when they are unambiguous, specically ANY is in both.
		if _, ok := StringToType[s]; !ok {
			return s
		}
	}
	return "CLASS" + strconv.Itoa(int(c))
}

// String returns the string representation for the name n.
func (n Name) String() string {
	return sprintName(string(n))
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"code.cloudfoundry.org/bytefmt"

	"golang.org/x/net/websocket"

	"net/url"

	sigar "github.com/cloudfoundry/gosigar"
	"github.com/hyperhq/runv/hypervisor/pod"
	"github.com/labstack/echo"
)

func (a *Api) CreateBox(c echo.Context) error {
	body, err := ioutil.ReadAll(c.Request().Body)
	if err != nil {
		return err
	}

	var req CreateBoxRequest
	if err = json.Unmarshal(body, &req); err != nil {
		return err
	}

	// validate captcha if secret is configured
	if a.Config.RCSecret != "" {
		data := url.Values{}
		data.Set("secret", a.Config.RCSecret)
		data.Set("response", req.Captcha)
		data.Set("remoteip", c.RealIP())

		res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", data)
		if err != nil {
			return err
		}

		defer res.Body.Close()

		var verify CaptchaVerifyResponse
		if err := json.NewDecoder(res.Body).Decode(&verify); err != nil {
			return err
		}
		if !verify.Success {
			return c.String(http.StatusBadRequest, "Captcha verification failed")
		}
	} else {
		a.Log.Warn("Creating box without captcha verfication")
	}

	// verify image is whitelisted
	var image Image
	for _, i := range *a.Images {
		if req.Image == i.Image {
			for _, version := range i.Versions {
				if req.Version == version {
					image = i
				}
			}
		}
	}
	if image.Image == "" {
		return c.String(http.StatusBadRequest, "Image not allowed")
	}

	// make sure we are not running out of memory
	mem := sigar.Mem{}
	mem.Get()
	if mem.ActualFree < bytefmt.GIGABYTE {
		return c.String(http.StatusTooManyRequests, "Resource limit reached, try again later")
	}

	container := pod.UserContainer{
		Image:   fmt.Sprintf("%s:%s", image.Image, req.Version),
		Command: []string{"sh"},
	}

	pod := pod.UserPod{
		Name:       "termbox",
		Hostname:   image.Name,
		Containers: []pod.UserContainer{container},
		Resource:   pod.UserResource{Vcpu: 1, Memory: 512},
	}

	podID, statusCode, err := a.Hyper.CreatePod(pod)
	if err != nil {
		if statusCode == http.StatusNotFound {
			if err := a.HyperClient.PullImages(&pod); err != nil {
				return err
			}
			podID, statusCode, err = a.Hyper.CreatePod(pod)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return c.JSON(http.StatusOK, CreateBoxResponse{PodID: podID})
}

type execMessage struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

func (a *Api) ExecBox(c echo.Context) error {

	websocket.Handler(func(ws *websocket.Conn) {
		defer ws.Close()

		podID := c.Param("id")
		podInfo, err := a.Hyper.GetPodInfo(podID)
		if err != nil {
			a.Log.Debug(err)
			websocket.Message.Send(ws, "box does not exist, closing connection")
			return
		}
		if podInfo.Status.Phase != "Running" {
			_, err := a.Hyper.StartPod(podID, "", false, false, nil, nil, nil)
			if err != nil {
				a.Log.Warn(err)
				return
			}
		}
		containerID, _ := a.Hyper.GetContainerByPod(podID)

		command, err := json.Marshal([]string{"sh", "-c", "tmux attach || tmux"})
		if err != nil {
			a.Log.Error(err)
			return
		}

		execID, err := a.Hyper.CreateExec(containerID, command, true)
		if err != nil {
			a.Log.Warn(err)
			return
		}

		dec := json.NewDecoder(ws)

		r, w := io.Pipe()
		defer r.Close()
		defer w.Close()

		go func() {
			if err := a.Hyper.StartExec(containerID, execID, true, r, ws, ws); err != nil {
				a.Log.Error(err)
			}
		}()

		for dec.More() {
			var message ExecBoxMessage
			if err := dec.Decode(&message); err != nil {
				a.Log.Error(err)
				break
			}

			if message.Width != 0 && message.Height != 0 {
				if err := a.Hyper.WinResize(containerID, execID, message.Height, message.Width); err != nil {
					a.Log.Warn(err)
				}
				continue
			}
			if message.Data != "" {
				_, err = io.WriteString(w, message.Data)
				if err != nil {
					a.Log.Error(err)
					break
				}
			}
		}

	}).ServeHTTP(c.Response(), c.Request())
	return nil

}

type ExecReader struct {
	data      []byte
	readIndex int64
}
<commit_msg>detach old tmux clients<commit_after>package api

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"code.cloudfoundry.org/bytefmt"

	"golang.org/x/net/websocket"

	"net/url"

	sigar "github.com/cloudfoundry/gosigar"
	"github.com/hyperhq/runv/hypervisor/pod"
	"github.com/labstack/echo"
)

func (a *Api) CreateBox(c echo.Context) error {
	body, err := ioutil.ReadAll(c.Request().Body)
	if err != nil {
		return err
	}

	var req CreateBoxRequest
	if err = json.Unmarshal(body, &req); err != nil {
		return err
	}

	// validate captcha if secret is configured
	if a.Config.RCSecret != "" {
		data := url.Values{}
		data.Set("secret", a.Config.RCSecret)
		data.Set("response", req.Captcha)
		data.Set("remoteip", c.RealIP())

		res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", data)
		if err != nil {
			return err
		}

		defer res.Body.Close()

		var verify CaptchaVerifyResponse
		if err := json.NewDecoder(res.Body).Decode(&verify); err != nil {
			return err
		}
		if !verify.Success {
			return c.String(http.StatusBadRequest, "Captcha verification failed")
		}
	} else {
		a.Log.Warn("Creating box without captcha verfication")
	}

	// verify image is whitelisted
	var image Image
	for _, i := range *a.Images {
		if req.Image == i.Image {
			for _, version := range i.Versions {
				if req.Version == version {
					image = i
				}
			}
		}
	}
	if image.Image == "" {
		return c.String(http.StatusBadRequest, "Image not allowed")
	}

	// make sure we are not running out of memory
	mem := sigar.Mem{}
	mem.Get()
	if mem.ActualFree < bytefmt.GIGABYTE {
		return c.String(http.StatusTooManyRequests, "Resource limit reached, try again later")
	}

	container := pod.UserContainer{
		Image:   fmt.Sprintf("%s:%s", image.Image, req.Version),
		Command: []string{"sh"},
	}

	pod := pod.UserPod{
		Name:       "termbox",
		Hostname:   image.Name,
		Containers: []pod.UserContainer{container},
		Resource:   pod.UserResource{Vcpu: 1, Memory: 512},
	}

	podID, statusCode, err := a.Hyper.CreatePod(pod)
	if err != nil {
		if statusCode == http.StatusNotFound {
			if err := a.HyperClient.PullImages(&pod); err != nil {
				return err
			}
			podID, statusCode, err = a.Hyper.CreatePod(pod)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return c.JSON(http.StatusOK, CreateBoxResponse{PodID: podID})
}

type execMessage struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

func (a *Api) ExecBox(c echo.Context) error {

	websocket.Handler(func(ws *websocket.Conn) {
		defer ws.Close()

		podID := c.Param("id")
		podInfo, err := a.Hyper.GetPodInfo(podID)
		if err != nil {
			a.Log.Debug(err)
			websocket.Message.Send(ws, "box does not exist, closing connection")
			return
		}
		if podInfo.Status.Phase != "Running" {
			_, err := a.Hyper.StartPod(podID, "", false, false, nil, nil, nil)
			if err != nil {
				a.Log.Warn(err)
				return
			}
		}
		containerID, _ := a.Hyper.GetContainerByPod(podID)

		command, err := json.Marshal([]string{"sh", "-c", "tmux attach -d || tmux"})
		if err != nil {
			a.Log.Error(err)
			return
		}

		execID, err := a.Hyper.CreateExec(containerID, command, true)
		if err != nil {
			a.Log.Warn(err)
			return
		}

		dec := json.NewDecoder(ws)

		r, w := io.Pipe()
		defer r.Close()
		defer w.Close()

		go func() {
			if err := a.Hyper.StartExec(containerID, execID, true, r, ws, ws); err != nil {
				a.Log.Error(err)
			}
		}()

		for dec.More() {
			var message ExecBoxMessage
			if err := dec.Decode(&message); err != nil {
				a.Log.Error(err)
				break
			}

			if message.Width != 0 && message.Height != 0 {
				if err := a.Hyper.WinResize(containerID, execID, message.Height, message.Width); err != nil {
					a.Log.Warn(err)
				}
				continue
			}
			if message.Data != "" {
				_, err = io.WriteString(w, message.Data)
				if err != nil {
					a.Log.Error(err)
					break
				}
			}
		}

	}).ServeHTTP(c.Response(), c.Request())
	return nil

}

type ExecReader struct {
	data      []byte
	readIndex int64
}
<|endoftext|>


<commit_before>package filestore

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/ipfs/go-ipfs/blocks"
	"github.com/ipfs/go-ipfs/blocks/blockstore"
	pb "github.com/ipfs/go-ipfs/filestore/pb"
	dshelp "github.com/ipfs/go-ipfs/thirdparty/ds-help"
	posinfo "github.com/ipfs/go-ipfs/thirdparty/posinfo"

	ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
	dsns "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/namespace"
	dsq "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/query"
	proto "gx/ipfs/QmT6n4mspWYEya864BhCUJEgyxiRfmiSY9ruQwTUNpRKaM/protobuf/proto"
	cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid"
)

var FilestorePrefix = ds.NewKey("filestore")

type FileManager struct {
	ds   ds.Batching
	root string
}

type CorruptReferenceError struct {
	Err error
}

func (c CorruptReferenceError) Error() string {
	return c.Err.Error()
}

func NewFileManager(ds ds.Batching, root string) *FileManager {
	return &FileManager{dsns.Wrap(ds, FilestorePrefix), root}
}

func (f *FileManager) AllKeysChan(ctx context.Context) (<-chan *cid.Cid, error) {
	q := dsq.Query{KeysOnly: true}
	q.Prefix = FilestorePrefix.String()

	res, err := f.ds.Query(q)
	if err != nil {
		return nil, err
	}

	out := make(chan *cid.Cid)
	go func() {
		defer close(out)
		for {
			v, ok := res.NextSync()
			if !ok {
				return
			}

			k := ds.RawKey(v.Key)
			c, err := dshelp.DsKeyToCid(k)
			if err != nil {
				log.Error("decoding cid from filestore: %s", err)
				continue
			}

			select {
			case out <- c:
			case <-ctx.Done():
				return
			}
		}
	}()

	return out, nil
}

func (f *FileManager) DeleteBlock(c *cid.Cid) error {
	err := f.ds.Delete(dshelp.CidToDsKey(c))
	if err == ds.ErrNotFound {
		return blockstore.ErrNotFound
	}
	return err
}

func (f *FileManager) Get(c *cid.Cid) (blocks.Block, error) {
	o, err := f.ds.Get(dshelp.CidToDsKey(c))
	switch err {
	case ds.ErrNotFound:
		return nil, blockstore.ErrNotFound
	default:
		return nil, err
	case nil:
		//
	}

	data, ok := o.([]byte)
	if !ok {
		return nil, fmt.Errorf("stored filestore dataobj was not a []byte")
	}

	var dobj pb.DataObj
	if err := proto.Unmarshal(data, &dobj); err != nil {
		return nil, err
	}

	out, err := f.readDataObj(c, &dobj)
	if err != nil {
		return nil, err
	}

	return blocks.NewBlockWithCid(out, c)
}

// reads and verifies the block
func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	p := filepath.FromSlash(d.GetFilePath())
	abspath := filepath.Join(f.root, p)

	fi, err := os.Open(abspath)
	if err != nil {
		return nil, &CorruptReferenceError{err}
	}
	defer fi.Close()

	_, err = fi.Seek(int64(d.GetOffset()), os.SEEK_SET)
	if err != nil {
		return nil, &CorruptReferenceError{err}
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(fi, outbuf)
	if err != nil {
		return nil, &CorruptReferenceError{err}
	}

	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
		return nil, &CorruptReferenceError{fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
	}

	return outbuf, nil
}

func (f *FileManager) Has(c *cid.Cid) (bool, error) {
	// NOTE: interesting thing to consider. Has doesnt validate the data.
	// So the data on disk could be invalid, and we could think we have it.
	dsk := dshelp.CidToDsKey(c)
	return f.ds.Has(dsk)
}

type putter interface {
	Put(ds.Key, interface{}) error
}

func (f *FileManager) Put(b *posinfo.FilestoreNode) error {
	return f.putTo(b, f.ds)
}

func (f *FileManager) putTo(b *posinfo.FilestoreNode, to putter) error {
	var dobj pb.DataObj

	if !filepath.HasPrefix(b.PosInfo.FullPath, f.root) {
		return fmt.Errorf("cannot add filestore references outside ipfs root")
	}

	p, err := filepath.Rel(f.root, b.PosInfo.FullPath)
	if err != nil {
		return err
	}

	dobj.FilePath = proto.String(filepath.ToSlash(p))
	dobj.Offset = proto.Uint64(b.PosInfo.Offset)
	dobj.Size_ = proto.Uint64(uint64(len(b.RawData())))

	data, err := proto.Marshal(&dobj)
	if err != nil {
		return err
	}

	return to.Put(dshelp.CidToDsKey(b.Cid()), data)
}

func (f *FileManager) PutMany(bs []*posinfo.FilestoreNode) error {
	batch, err := f.ds.Batch()
	if err != nil {
		return err
	}

	for _, b := range bs {
		if err := f.putTo(b, batch); err != nil {
			return err
		}
	}

	return batch.Commit()
}
<commit_msg>Refactor.<commit_after>package filestore

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/ipfs/go-ipfs/blocks"
	"github.com/ipfs/go-ipfs/blocks/blockstore"
	pb "github.com/ipfs/go-ipfs/filestore/pb"
	dshelp "github.com/ipfs/go-ipfs/thirdparty/ds-help"
	posinfo "github.com/ipfs/go-ipfs/thirdparty/posinfo"

	ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
	dsns "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/namespace"
	dsq "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/query"
	proto "gx/ipfs/QmT6n4mspWYEya864BhCUJEgyxiRfmiSY9ruQwTUNpRKaM/protobuf/proto"
	cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid"
)

var FilestorePrefix = ds.NewKey("filestore")

type FileManager struct {
	ds   ds.Batching
	root string
}

type CorruptReferenceError struct {
	Err error
}

func (c CorruptReferenceError) Error() string {
	return c.Err.Error()
}

func NewFileManager(ds ds.Batching, root string) *FileManager {
	return &FileManager{dsns.Wrap(ds, FilestorePrefix), root}
}

func (f *FileManager) AllKeysChan(ctx context.Context) (<-chan *cid.Cid, error) {
	q := dsq.Query{KeysOnly: true}
	q.Prefix = FilestorePrefix.String()

	res, err := f.ds.Query(q)
	if err != nil {
		return nil, err
	}

	out := make(chan *cid.Cid)
	go func() {
		defer close(out)
		for {
			v, ok := res.NextSync()
			if !ok {
				return
			}

			k := ds.RawKey(v.Key)
			c, err := dshelp.DsKeyToCid(k)
			if err != nil {
				log.Error("decoding cid from filestore: %s", err)
				continue
			}

			select {
			case out <- c:
			case <-ctx.Done():
				return
			}
		}
	}()

	return out, nil
}

func (f *FileManager) DeleteBlock(c *cid.Cid) error {
	err := f.ds.Delete(dshelp.CidToDsKey(c))
	if err == ds.ErrNotFound {
		return blockstore.ErrNotFound
	}
	return err
}

func (f *FileManager) Get(c *cid.Cid) (blocks.Block, error) {
	dobj, err := f.getDataObj(c)
	if err != nil {
		return nil, err
	}

	out, err := f.readDataObj(c, dobj)
	if err != nil {
		return nil, err
	}

	return blocks.NewBlockWithCid(out, c)
}

func (f *FileManager) getDataObj(c *cid.Cid) (*pb.DataObj, error) {
	o, err := f.ds.Get(dshelp.CidToDsKey(c))
	switch err {
	case ds.ErrNotFound:
		return nil, blockstore.ErrNotFound
	default:
		return nil, err
	case nil:
		//
	}

	data, ok := o.([]byte)
	if !ok {
		return nil, fmt.Errorf("stored filestore dataobj was not a []byte")
	}

	var dobj pb.DataObj
	if err := proto.Unmarshal(data, &dobj); err != nil {
		return nil, err
	}

	return &dobj, nil
}

// reads and verifies the block
func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	p := filepath.FromSlash(d.GetFilePath())
	abspath := filepath.Join(f.root, p)

	fi, err := os.Open(abspath)
	if err != nil {
		return nil, &CorruptReferenceError{err}
	}
	defer fi.Close()

	_, err = fi.Seek(int64(d.GetOffset()), os.SEEK_SET)
	if err != nil {
		return nil, &CorruptReferenceError{err}
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(fi, outbuf)
	if err != nil {
		return nil, &CorruptReferenceError{err}
	}

	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
		return nil, &CorruptReferenceError{fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
	}

	return outbuf, nil
}

func (f *FileManager) Has(c *cid.Cid) (bool, error) {
	// NOTE: interesting thing to consider. Has doesnt validate the data.
	// So the data on disk could be invalid, and we could think we have it.
	dsk := dshelp.CidToDsKey(c)
	return f.ds.Has(dsk)
}

type putter interface {
	Put(ds.Key, interface{}) error
}

func (f *FileManager) Put(b *posinfo.FilestoreNode) error {
	return f.putTo(b, f.ds)
}

func (f *FileManager) putTo(b *posinfo.FilestoreNode, to putter) error {
	var dobj pb.DataObj

	if !filepath.HasPrefix(b.PosInfo.FullPath, f.root) {
		return fmt.Errorf("cannot add filestore references outside ipfs root")
	}

	p, err := filepath.Rel(f.root, b.PosInfo.FullPath)
	if err != nil {
		return err
	}

	dobj.FilePath = proto.String(filepath.ToSlash(p))
	dobj.Offset = proto.Uint64(b.PosInfo.Offset)
	dobj.Size_ = proto.Uint64(uint64(len(b.RawData())))

	data, err := proto.Marshal(&dobj)
	if err != nil {
		return err
	}

	return to.Put(dshelp.CidToDsKey(b.Cid()), data)
}

func (f *FileManager) PutMany(bs []*posinfo.FilestoreNode) error {
	batch, err := f.ds.Batch()
	if err != nil {
		return err
	}

	for _, b := range bs {
		if err := f.putTo(b, batch); err != nil {
			return err
		}
	}

	return batch.Commit()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/zeebo/bencode"
)

var (
	errNewFile = errors.New("Got new file")
)

type Watcher struct {
	lastModTime time.Time
	workDir     string
	watchedDir  string
	lock        sync.Mutex

	PingNewTorrent chan string
}

func NewWatcher(workDir, watchedDir string, canWrite bool) (w *Watcher) {

	if _, err := os.Stat(workDir); err != nil {
		if os.IsNotExist(err) {
			os.MkdirAll(workDir, os.ModeDir|0755)
		}
	}

	// Lastmodtime
	// If we have a current torrent, it's its mod time
	// Otherwise it's time.Now()
	defaultNow := time.Now()
	lastModTime := defaultNow

	currentFile := filepath.Join(workDir, "current")
	st, err := os.Stat(currentFile)
	if err != nil && !os.IsNotExist(err) {
		log.Fatal("Couldn't stat current file: ", err)
	}
	if st != nil {
		lastModTime = st.ModTime()
	}

	err = clean(workDir)
	if err != nil {
		log.Fatal("Couldn't clean workDir dir:", err)
	}

	w = &Watcher{
		lastModTime:    lastModTime,
		workDir:        workDir,
		watchedDir:     watchedDir,
		PingNewTorrent: make(chan string),
	}

	if canWrite {
		go w.watch()
	}

	// Initialization, only if there is something in the dir
	if _, err := os.Stat(watchedDir); err != nil {
		return
	}

	dir, err := os.Open(watchedDir)
	if err != nil {
		return
	}

	names, err := dir.Readdirnames(1)
	if len(names) == 0 || err != nil && err != io.EOF {
		return
	}

	ih, err := w.currentTorrent()
	if err == nil {
		go func() {
			w.PingNewTorrent <- ih
		}()
	}

	return
}

func (w *Watcher) currentTorrent() (ih string, err error) {
	currentFile := filepath.Join(w.workDir, "current")
	current, err := os.Open(currentFile)
	if err != nil && !os.IsNotExist(err) {
		log.Fatal("Couldn't stat current file: ", err)
	} else if err == nil {
		var mess IHMessage
		err = bencode.NewDecoder(current).Decode(&mess)
		if err == nil {
			return mess.Info.InfoHash, nil
		} else if err != io.EOF {
			log.Printf("Error when decoding \"current\": %s\n", err)
			return
		}
	}

	// No torrent but there is content. Calculate manually.
	return w.torrentify()
}

func (w *Watcher) watch() {

	for _ = range time.Tick(10 * time.Second) {
		w.lock.Lock()

		err := torrentWalk(w.watchedDir, func(path string, info os.FileInfo, perr error) (err error) {
			if info.ModTime().After(w.lastModTime) {
				fmt.Printf("[newer] %s\n", path)
				return errNewFile
			}
			return
		})

		w.lock.Unlock()

		if err == nil {
			continue
		}

		if err == errNewFile {
			// New torrent: block until we completely manage it. We will take
			// care of other changes in the next run of the loop.
			ih, err := w.torrentify()
			if err != nil {
				log.Printf("Couldn't torrentify: ", err)
				continue
			}
			w.PingNewTorrent <- ih
		} else {
			log.Println("Error while walking dir:", err)
		}
	}
}

func (w *Watcher) torrentify() (ih string, err error) {
	w.lock.Lock()
	defer w.lock.Unlock()

	meta, err := createMeta(w.watchedDir)
	if err != nil {
		log.Println(err)
		return
	}

	err = w.saveMetainfo(meta)

	return meta.InfoHash, err
}

func (w *Watcher) saveMetainfo(meta *MetaInfo) error {

	tmpFile, err := ioutil.TempFile(w.workDir, "current.")
	if err != nil {
		return err
	}
	defer func() {
		err = os.Remove(tmpFile.Name())
		if err != nil {
			return
		}
	}()

	err = tmpFile.Close()
	if err != nil {
		return err
	}
	ihhex := fmt.Sprintf("%x", meta.InfoHash)

	f, err := os.Create(tmpFile.Name())
	if err != nil {
		return err
	}
	err = bencode.NewEncoder(f).Encode(meta)
	if err != nil {
		return err
	}
	f.Close()

	// Move tmp file to final file (with infohash as name)
	currentTorrent := filepath.Join(w.workDir, ihhex)
	if st, err := os.Stat(currentTorrent); st != nil {
		if err = os.Remove(currentTorrent); err != nil {
			return err
		}
	}
	err = os.Link(tmpFile.Name(), currentTorrent)
	if err != nil {
		return err
	}

	// Update last mod time
	st, err := os.Stat(currentTorrent)
	if err != nil {
		return err
	}
	w.lastModTime = st.ModTime()

	return nil
}

func createMeta(dir string) (meta *MetaInfo, err error) {
	blockSize := int64(1 << 20) // 1MiB

	fileDicts := make([]*FileDict, 0)

	hasher := NewBlockHasher(blockSize)
	err = torrentWalk(dir, func(path string, info os.FileInfo, perr error) (err error) {
		if perr != nil {
			return perr
		}

		f, err := os.Open(path)
		if err != nil {
			return errors.New(fmt.Sprintf("Couldn't open %s for hashing: %s\n", path, err))
		}
		defer f.Close()

		_, err = io.Copy(hasher, f)
		if err != nil {
			log.Printf("Couldn't hash %s: %s\n", path, err)
			return err
		}

		relPath, err := filepath.Rel(dir, path)
		if err != nil {
			return
		}

		fileDict := &FileDict{
			Length: info.Size(),
			Path:   strings.Split(relPath, string(os.PathSeparator)),
		}
		fileDicts = append(fileDicts, fileDict)

		return
	})
	if err != nil {
		return
	}

	end := hasher.Close()
	if end != nil {
		return
	}

	meta = &MetaInfo{
		Info: &InfoDict{
			Pieces:      string(hasher.Pieces),
			PieceLength: blockSize,
			Private:     0,
			Name:        "rakoshare",
			Files:       fileDicts,
		},
	}

	hash := sha1.New()
	err = bencode.NewEncoder(hash).Encode(meta.Info)
	if err != nil {
		return
	}
	meta.InfoHash = string(hash.Sum(nil))

	return
}

type BlockHasher struct {
	sha1er    hash.Hash
	left      int64
	blockSize int64
	Pieces    []byte
}

func NewBlockHasher(blockSize int64) (h *BlockHasher) {
	return &BlockHasher{
		blockSize: blockSize,
		sha1er:    sha1.New(),
		left:      blockSize,
	}
}

// You shouldn't use this one
func (h *BlockHasher) Write(p []byte) (n int, err error) {
	n2, err := h.ReadFrom(bytes.NewReader(p))
	return int(n2), err
}

func (h *BlockHasher) ReadFrom(rd io.Reader) (n int64, err error) {
	var stop bool

	for {
		if h.left > 0 {
			thisN, err := io.CopyN(h.sha1er, rd, h.left)
			if err != nil {
				if err == io.EOF {
					stop = true
				} else {
					return n, err
				}
			}
			h.left -= thisN
			n += thisN
		}
		if h.left == 0 {
			h.Pieces = h.sha1er.Sum(h.Pieces)
			h.sha1er = sha1.New()
			h.left = h.blockSize
		}

		if stop {
			break
		}
	}

	return
}

func (h *BlockHasher) Close() (err error) {
	if h.left == h.blockSize {
		// We're at the end of a blockSize, we don't have any buffered data
		return
	}
	h.Pieces = h.sha1er.Sum(h.Pieces)
	return
}

func clean(dirname string) (err error) {
	dir, err := os.Open(dirname)
	if err != nil {
		return
	}
	names, err := dir.Readdirnames(-1)
	if err != nil {
		return
	}
	for _, name := range names {
		if strings.HasPrefix(name, "current.") {
			err = os.Remove(filepath.Join(dirname, name))
			if err != nil {
				break
			}
		}
	}

	return
}

func torrentWalk(root string, fn filepath.WalkFunc) (err error) {
	return filepath.Walk(root, func(path string, info os.FileInfo, perr error) (err error) {
		if !info.Mode().IsRegular() {
			return
		}

		// Torrents can't have empty files
		if info.Size() == 0 {
			return
		}

		if strings.HasPrefix(filepath.Base(path), ".") {
			return
		}

		if filepath.Ext(path) == ".part" {
			return
		}

		return fn(path, info, perr)
	})
}
<commit_msg>Wait for a stop in changes to update current meta<commit_after>package main

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/zeebo/bencode"
)

var (
	errNewFile = errors.New("Got new file")
)

type state int

const (
	IDEM = iota
	CHANGED
)

type Watcher struct {
	lastModTime time.Time
	workDir     string
	watchedDir  string
	lock        sync.Mutex

	PingNewTorrent chan string
}

func NewWatcher(workDir, watchedDir string, canWrite bool) (w *Watcher) {

	if _, err := os.Stat(workDir); err != nil {
		if os.IsNotExist(err) {
			os.MkdirAll(workDir, os.ModeDir|0755)
		}
	}

	// Lastmodtime
	// If we have a current torrent, it's its mod time
	// Otherwise it's time.Now()
	defaultNow := time.Now()
	lastModTime := defaultNow

	currentFile := filepath.Join(workDir, "current")
	st, err := os.Stat(currentFile)
	if err != nil && !os.IsNotExist(err) {
		log.Fatal("Couldn't stat current file: ", err)
	}
	if st != nil {
		lastModTime = st.ModTime()
	}

	err = clean(workDir)
	if err != nil {
		log.Fatal("Couldn't clean workDir dir:", err)
	}

	w = &Watcher{
		lastModTime:    lastModTime,
		workDir:        workDir,
		watchedDir:     watchedDir,
		PingNewTorrent: make(chan string),
	}

	if canWrite {
		go w.watch()
	}

	// Initialization, only if there is something in the dir
	if _, err := os.Stat(watchedDir); err != nil {
		return
	}

	dir, err := os.Open(watchedDir)
	if err != nil {
		return
	}

	names, err := dir.Readdirnames(1)
	if len(names) == 0 || err != nil && err != io.EOF {
		return
	}

	ih, err := w.currentTorrent()
	if err == nil {
		go func() {
			w.PingNewTorrent <- ih
		}()
	}

	return
}

func (w *Watcher) currentTorrent() (ih string, err error) {
	currentFile := filepath.Join(w.workDir, "current")
	current, err := os.Open(currentFile)
	if err != nil && !os.IsNotExist(err) {
		log.Fatal("Couldn't stat current file: ", err)
	} else if err == nil {
		var mess IHMessage
		err = bencode.NewDecoder(current).Decode(&mess)
		if err == nil {
			return mess.Info.InfoHash, nil
		} else if err != io.EOF {
			log.Printf("Error when decoding \"current\": %s\n", err)
			return
		}
	}

	// No torrent but there is content. Calculate manually.
	return w.torrentify()
}

func (w *Watcher) watch() {
	var previousState, currentState state
	currentState = IDEM

	compareTime := w.lastModTime

	for _ = range time.Tick(10 * time.Second) {
		w.lock.Lock()

		err := torrentWalk(w.watchedDir, func(path string, info os.FileInfo, perr error) (err error) {
			if info.ModTime().After(compareTime) {
				fmt.Printf("[newer] %s\n", path)
				return errNewFile
			}
			return
		})

		w.lock.Unlock()

		previousState = currentState

		if err == errNewFile {
			currentState = CHANGED
		} else if err == nil {
			currentState = IDEM
		} else {
			log.Println("Error while walking dir:", err)
		}

		compareTime = time.Now()

		if currentState == IDEM && previousState == CHANGED {
			// Note that we may be in the CHANGED state for multiple
			// iterations, such as when changes take more than 10 seconds to
			// finish. When we go back to "idle" state, we kick in the
			// metadata creation.

			// Block until we completely manage it. We will take
			// care of other changes in the next run of the loop.
			ih, err := w.torrentify()
			if err != nil {
				log.Printf("Couldn't torrentify: ", err)
				continue
			}
			w.PingNewTorrent <- ih
		}
	}
}

func (w *Watcher) torrentify() (ih string, err error) {
	w.lock.Lock()
	defer w.lock.Unlock()

	meta, err := createMeta(w.watchedDir)
	if err != nil {
		log.Println(err)
		return
	}

	err = w.saveMetainfo(meta)

	return meta.InfoHash, err
}

func (w *Watcher) saveMetainfo(meta *MetaInfo) error {

	tmpFile, err := ioutil.TempFile(w.workDir, "current.")
	if err != nil {
		return err
	}
	defer func() {
		err = os.Remove(tmpFile.Name())
		if err != nil {
			return
		}
	}()

	err = tmpFile.Close()
	if err != nil {
		return err
	}
	ihhex := fmt.Sprintf("%x", meta.InfoHash)

	f, err := os.Create(tmpFile.Name())
	if err != nil {
		return err
	}
	err = bencode.NewEncoder(f).Encode(meta)
	if err != nil {
		return err
	}
	f.Close()

	// Move tmp file to final file (with infohash as name)
	currentTorrent := filepath.Join(w.workDir, ihhex)
	if st, err := os.Stat(currentTorrent); st != nil {
		if err = os.Remove(currentTorrent); err != nil {
			return err
		}
	}
	err = os.Link(tmpFile.Name(), currentTorrent)
	if err != nil {
		return err
	}

	// Update last mod time
	st, err := os.Stat(currentTorrent)
	if err != nil {
		return err
	}
	w.lastModTime = st.ModTime()

	return nil
}

func createMeta(dir string) (meta *MetaInfo, err error) {
	blockSize := int64(1 << 20) // 1MiB

	fileDicts := make([]*FileDict, 0)

	hasher := NewBlockHasher(blockSize)
	err = torrentWalk(dir, func(path string, info os.FileInfo, perr error) (err error) {
		if perr != nil {
			return perr
		}

		f, err := os.Open(path)
		if err != nil {
			return errors.New(fmt.Sprintf("Couldn't open %s for hashing: %s\n", path, err))
		}
		defer f.Close()

		_, err = io.Copy(hasher, f)
		if err != nil {
			log.Printf("Couldn't hash %s: %s\n", path, err)
			return err
		}

		relPath, err := filepath.Rel(dir, path)
		if err != nil {
			return
		}

		fileDict := &FileDict{
			Length: info.Size(),
			Path:   strings.Split(relPath, string(os.PathSeparator)),
		}
		fileDicts = append(fileDicts, fileDict)

		return
	})
	if err != nil {
		return
	}

	end := hasher.Close()
	if end != nil {
		return
	}

	meta = &MetaInfo{
		Info: &InfoDict{
			Pieces:      string(hasher.Pieces),
			PieceLength: blockSize,
			Private:     0,
			Name:        "rakoshare",
			Files:       fileDicts,
		},
	}

	hash := sha1.New()
	err = bencode.NewEncoder(hash).Encode(meta.Info)
	if err != nil {
		return
	}
	meta.InfoHash = string(hash.Sum(nil))

	return
}

type BlockHasher struct {
	sha1er    hash.Hash
	left      int64
	blockSize int64
	Pieces    []byte
}

func NewBlockHasher(blockSize int64) (h *BlockHasher) {
	return &BlockHasher{
		blockSize: blockSize,
		sha1er:    sha1.New(),
		left:      blockSize,
	}
}

// You shouldn't use this one
func (h *BlockHasher) Write(p []byte) (n int, err error) {
	n2, err := h.ReadFrom(bytes.NewReader(p))
	return int(n2), err
}

func (h *BlockHasher) ReadFrom(rd io.Reader) (n int64, err error) {
	var stop bool

	for {
		if h.left > 0 {
			thisN, err := io.CopyN(h.sha1er, rd, h.left)
			if err != nil {
				if err == io.EOF {
					stop = true
				} else {
					return n, err
				}
			}
			h.left -= thisN
			n += thisN
		}
		if h.left == 0 {
			h.Pieces = h.sha1er.Sum(h.Pieces)
			h.sha1er = sha1.New()
			h.left = h.blockSize
		}

		if stop {
			break
		}
	}

	return
}

func (h *BlockHasher) Close() (err error) {
	if h.left == h.blockSize {
		// We're at the end of a blockSize, we don't have any buffered data
		return
	}
	h.Pieces = h.sha1er.Sum(h.Pieces)
	return
}

func clean(dirname string) (err error) {
	dir, err := os.Open(dirname)
	if err != nil {
		return
	}
	names, err := dir.Readdirnames(-1)
	if err != nil {
		return
	}
	for _, name := range names {
		if strings.HasPrefix(name, "current.") {
			err = os.Remove(filepath.Join(dirname, name))
			if err != nil {
				break
			}
		}
	}

	return
}

func torrentWalk(root string, fn filepath.WalkFunc) (err error) {
	return filepath.Walk(root, func(path string, info os.FileInfo, perr error) (err error) {
		if !info.Mode().IsRegular() {
			return
		}

		// Torrents can't have empty files
		if info.Size() == 0 {
			return
		}

		if strings.HasPrefix(filepath.Base(path), ".") {
			return
		}

		if filepath.Ext(path) == ".part" {
			return
		}

		return fn(path, info, perr)
	})
}
<|endoftext|>


<commit_before>package models

import (
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

// Stop is a single transit stop for a particular route. If a
// stop serves more than one route, there are multiple distinct
// entries for that stop.
type Stop struct {
	ID       string `json:"stop_id" db:"stop_id" upsert:"key"`
	RouteID  string `json:"route_id" db:"route_id" upsert:"key"`
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	Name     string `json:"stop_name" db:"stop_name"`

	DirectionID int    `json:"direction_id" db:"direction_id"`
	Headsign    string `json:"headsign" db:"headsign"`

	Lat float64 `json:"lat" db:"lat" upsert:"omit"`
	Lon float64 `json:"lon" db:"lon" upsert:"omit"`

	// Location is an "earth" field value that combines lat and lon into
	// a single field.
	Location interface{} `json:"-" db:"location" upsert_value:"ll_to_earth(:lat, :lon)"`

	// StopSequence is the order in which this stop occurs in a typical
	// route trip, for comparisons with other stops matching the
	// same agency / route / stop / direction / headsign
	StopSequence int `json:"stop_sequence" db:"stop_sequence" upsert:"omit"`

	Dist      float64      `json:"dist" db:"-" upsert:"omit"`
	Scheduled []*Departure `json:"scheduled" db:"-" upsert:"omit"`
	Live      []*Departure `json:"live" db:"-" upsert:"omit"`

	Vehicles []Vehicle `json:"vehicles" db:"-" upsert:"omit"`
}

// Table implements the upsert.Upserter interface, returning the table
// where we save stops.
func (s *Stop) Table() string {
	return "stop"
}

// Save saves a stop to the database
func (s *Stop) Save() error {
	_, err := upsert.Upsert(etc.DBConn, s)
	return err
}

// String returns a descriptive string for this stop.
func (s Stop) String() string {
	return fmt.Sprintf("{%v %v %v %v %v @ (%v,%v)}",
		s.ID, s.Name, s.RouteID, s.Headsign, s.DirectionID, s.Lat, s.Lon,
	)
}

// Key() returns the unique string for this stop, so we can identify
// unique stops in the loader.
func (s Stop) Key() string {
	return fmt.Sprintf("%v%v", s.ID, s.RouteID)
}

// setDepartures checks the database and any relevant APIs to set the scheduled
// and live departures for this stop
func (s *Stop) setDepartures(now time.Time, db sqlx.Ext) (err error) {

	allDepartures := Departures{}

	yesterday := baseTime(now.Add(-time.Hour * 12))
	today := baseTime(now)

	yesterdayName := strings.ToLower(yesterday.Format("Monday"))
	todayName := strings.ToLower(now.Format("Monday"))

	func() {
		if yesterdayName != todayName {
			var yesterdayIDs []string
			// Looks for trips starting yesterday that arrive here
			// after midnight
			yesterdayIDs, err = getServiceIDsByDay(
				db, s.AgencyID, s.RouteID, yesterdayName, yesterday,
			)
			if err == sql.ErrNoRows {
				err = nil
				log.Println("no rows, ok, moving on")
				return
			}
			if err != nil {
				log.Println("can't get yesterday id", err)
				return
			}

			nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second() + midnightSecs

			for _, yesterdayID := range yesterdayIDs {
				departures, err := getDepartures(
					s.AgencyID, s.RouteID, s.ID, yesterdayID,
					nowSecs, yesterday)
				if err != nil {
					log.Println("can't get departures", err)
					return
				}

				allDepartures = append(allDepartures, departures...)
			}
		}
	}()

	func() {
		var todayIDs []string
		todayIDs, err = getServiceIDsByDay(db, s.AgencyID, s.RouteID, todayName, today)
		if err == sql.ErrNoRows {
			err = nil
			log.Println("no rows there", err)
			return
		}
		if err != nil {
			log.Println("can't get today id", err)
			return
		}

		nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second()

		for _, todayID := range todayIDs {
			departures, err := getDepartures(
				s.AgencyID, s.RouteID, s.ID, todayID,
				nowSecs, today)
			if err != nil {
				log.Println("can't get departures", err)
				return
			}

			allDepartures = append(allDepartures, departures...)
		}

	}()

	sort.Sort(allDepartures)

	for i, d := range allDepartures {
		if i > MaxDepartures {
			break
		}
		s.Scheduled = append(s.Scheduled, d)
	}

	return
}

// GetStop returns a single stop by its unique id
func GetStop(db sqlx.Ext, agencyID, routeID, stopID string, appendInfo bool) (*Stop, error) {
	var s Stop
	now := time.Now()

	err := sqlx.Get(db, &s, `
		 SELECT stop.*, COALESCE(sst.stop_sequence, 0) as stop_sequence,
				latitude(stop.location) AS lat,
				longitude(stop.location) AS lon

		 FROM stop
		 INNER JOIN route_trip ON route_trip.agency_id = stop.agency_id AND
		            			  route_trip.route_id  = stop.route_id
		 LEFT JOIN scheduled_stop_time sst ON
								sst.agency_id = stop.agency_id     AND
								sst.route_id  = stop.route_id      AND
		            			sst.trip_id   = route_trip.trip_id AND
								sst.stop_id   = stop.stop_id
		 WHERE stop.agency_id = $1 AND
			   stop.route_id  = $2 AND
			   stop.stop_id   = $3
		`, agencyID, routeID, stopID,
	)
	if err != nil {
		log.Println("can't get stop", err, agencyID, routeID, stopID)
		return nil, err
	}

	if appendInfo {
		err = s.setDepartures(now, db)
		if err != nil {
			log.Println("can't set departures", err)
			return nil, err
		}
	}

	return &s, nil
}

// GetStopsByQuery returns stops matching this StopQuery
func GetStopsByQuery(db sqlx.Ext, sq StopQuery) (stops []*Stop, err error) {
	// distinct maps agency_id|route_id|direction_id to bool to ensure
	// we don't load duplicate routes
	distinct := map[string]bool{}

	// Get rows matching the stop query
	rows, err := sqlx.NamedQuery(db, sq.Query(), sq)
	if err != nil {
		log.Println("can't get stops", sq.Query(), err)
		return
	}

	defer rows.Close()

	count := 0
	for rows.Next() {
		var sqr stopQueryRow
		var stop *Stop

		if count >= sq.MaxStops {
			break
		}

		err = rows.StructScan(&sqr)
		if err != nil {
			log.Println("can't scan stop row", err)
			return
		}

		// skip duplicate rows if requested
		if sq.Distinct && distinct[sqr.id()] {
			continue
		}
		distinct[sqr.id()] = true

		stop, err = GetStop(
			db, sqr.AgencyID, sqr.RouteID, sqr.StopID,
			sq.Departures,
		)
		if err != nil {
			log.Println("can't get stop", err)
			return
		}

		stop.Dist = sqr.Dist

		stops = append(stops, stop)
		count++
	}

	return
}

func getServiceIDsByDay(db sqlx.Ext, agencyID, routeID, day string, now time.Time) (serviceIDs []string, err error) {

	// Select the service_ids that:
	//   * matches our agencyID, routeID, day
	//   * has an end_date after now
	//   * has a start_date before now

	q := `
		SELECT service_id
		FROM   service_route_day
		WHERE  day = $1 AND
			   end_date >= $2 AND
			   start_date <= $3 AND
			   route_id = $4 AND
			   agency_id = $5
	`

	err = sqlx.Select(db, &serviceIDs, q, day, now, now, routeID, agencyID)
	if err != nil {
		log.Println("can't scan service ids", err, day, now, routeID, agencyID)
		return
	}

	return
}
<commit_msg>support service route exceptions<commit_after>package models

import (
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

// Stop is a single transit stop for a particular route. If a
// stop serves more than one route, there are multiple distinct
// entries for that stop.
type Stop struct {
	ID       string `json:"stop_id" db:"stop_id" upsert:"key"`
	RouteID  string `json:"route_id" db:"route_id" upsert:"key"`
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	Name     string `json:"stop_name" db:"stop_name"`

	DirectionID int    `json:"direction_id" db:"direction_id"`
	Headsign    string `json:"headsign" db:"headsign"`

	Lat float64 `json:"lat" db:"lat" upsert:"omit"`
	Lon float64 `json:"lon" db:"lon" upsert:"omit"`

	// Location is an "earth" field value that combines lat and lon into
	// a single field.
	Location interface{} `json:"-" db:"location" upsert_value:"ll_to_earth(:lat, :lon)"`

	// StopSequence is the order in which this stop occurs in a typical
	// route trip, for comparisons with other stops matching the
	// same agency / route / stop / direction / headsign
	StopSequence int `json:"stop_sequence" db:"stop_sequence" upsert:"omit"`

	Dist      float64      `json:"dist" db:"-" upsert:"omit"`
	Scheduled []*Departure `json:"scheduled" db:"-" upsert:"omit"`
	Live      []*Departure `json:"live" db:"-" upsert:"omit"`

	Vehicles []Vehicle `json:"vehicles" db:"-" upsert:"omit"`
}

// Table implements the upsert.Upserter interface, returning the table
// where we save stops.
func (s *Stop) Table() string {
	return "stop"
}

// Save saves a stop to the database
func (s *Stop) Save() error {
	_, err := upsert.Upsert(etc.DBConn, s)
	return err
}

// String returns a descriptive string for this stop.
func (s Stop) String() string {
	return fmt.Sprintf("{%v %v %v %v %v @ (%v,%v)}",
		s.ID, s.Name, s.RouteID, s.Headsign, s.DirectionID, s.Lat, s.Lon,
	)
}

// Key() returns the unique string for this stop, so we can identify
// unique stops in the loader.
func (s Stop) Key() string {
	return fmt.Sprintf("%v%v", s.ID, s.RouteID)
}

// setDepartures checks the database and any relevant APIs to set the scheduled
// and live departures for this stop
func (s *Stop) setDepartures(now time.Time, db sqlx.Ext) (err error) {

	allDepartures := Departures{}

	yesterday := baseTime(now.Add(-time.Hour * 12))
	today := baseTime(now)

	yesterdayName := strings.ToLower(yesterday.Format("Monday"))
	todayName := strings.ToLower(now.Format("Monday"))

	func() {
		if yesterdayName != todayName {
			var yesterdayIDs []string
			// Looks for trips starting yesterday that arrive here
			// after midnight
			yesterdayIDs, err = getServiceIDsByDay(
				db, s.AgencyID, s.RouteID, yesterdayName, yesterday,
			)
			if err == sql.ErrNoRows {
				err = nil
				log.Println("no rows, ok, moving on")
				return
			}
			if err != nil {
				log.Println("can't get yesterday id", err)
				return
			}

			nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second() + midnightSecs

			for _, yesterdayID := range yesterdayIDs {
				departures, err := getDepartures(
					s.AgencyID, s.RouteID, s.ID, yesterdayID,
					nowSecs, yesterday)
				if err != nil {
					log.Println("can't get departures", err)
					return
				}

				allDepartures = append(allDepartures, departures...)
			}
		}
	}()

	func() {
		var todayIDs []string
		todayIDs, err = getServiceIDsByDay(db, s.AgencyID, s.RouteID, todayName, today)
		if err == sql.ErrNoRows {
			err = nil
			log.Println("no rows there", err)
			return
		}
		if err != nil {
			log.Println("can't get today id", err)
			return
		}

		nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second()

		for _, todayID := range todayIDs {
			departures, err := getDepartures(
				s.AgencyID, s.RouteID, s.ID, todayID,
				nowSecs, today)
			if err != nil {
				log.Println("can't get departures", err)
				return
			}

			allDepartures = append(allDepartures, departures...)
		}

	}()

	sort.Sort(allDepartures)

	for i, d := range allDepartures {
		if i > MaxDepartures {
			break
		}
		s.Scheduled = append(s.Scheduled, d)
	}

	return
}

// GetStop returns a single stop by its unique id
func GetStop(db sqlx.Ext, agencyID, routeID, stopID string, appendInfo bool) (*Stop, error) {
	var s Stop
	now := time.Now()

	err := sqlx.Get(db, &s, `
		 SELECT stop.*, COALESCE(sst.stop_sequence, 0) AS stop_sequence,
				latitude(stop.location) AS lat,
				longitude(stop.location) AS lon

		 FROM stop
		 INNER JOIN route_trip ON route_trip.agency_id = stop.agency_id AND
		            			  route_trip.route_id  = stop.route_id
		 LEFT JOIN scheduled_stop_time sst ON
								sst.agency_id = stop.agency_id     AND
								sst.route_id  = stop.route_id      AND
		            			sst.trip_id   = route_trip.trip_id AND
								sst.stop_id   = stop.stop_id
		 WHERE stop.agency_id = $1 AND
			   stop.route_id  = $2 AND
			   stop.stop_id   = $3
		`, agencyID, routeID, stopID,
	)
	if err != nil {
		log.Println("can't get stop", err, agencyID, routeID, stopID)
		return nil, err
	}

	if appendInfo {
		err = s.setDepartures(now, db)
		if err != nil {
			log.Println("can't set departures", err)
			return nil, err
		}
	}

	return &s, nil
}

// GetStopsByQuery returns stops matching this StopQuery
func GetStopsByQuery(db sqlx.Ext, sq StopQuery) (stops []*Stop, err error) {
	// distinct maps agency_id|route_id|direction_id to bool to ensure
	// we don't load duplicate routes
	distinct := map[string]bool{}

	// Get rows matching the stop query
	rows, err := sqlx.NamedQuery(db, sq.Query(), sq)
	if err != nil {
		log.Println("can't get stops", sq.Query(), err)
		return
	}

	defer rows.Close()

	count := 0
	for rows.Next() {
		var sqr stopQueryRow
		var stop *Stop

		if count >= sq.MaxStops {
			break
		}

		err = rows.StructScan(&sqr)
		if err != nil {
			log.Println("can't scan stop row", err)
			return
		}

		// skip duplicate rows if requested
		if sq.Distinct && distinct[sqr.id()] {
			continue
		}
		distinct[sqr.id()] = true

		stop, err = GetStop(
			db, sqr.AgencyID, sqr.RouteID, sqr.StopID,
			sq.Departures,
		)
		if err != nil {
			log.Println("can't get stop", err)
			return
		}

		stop.Dist = sqr.Dist

		stops = append(stops, stop)
		count++
	}

	return
}

func getServiceIDsByDay(db sqlx.Ext, agencyID, routeID, day string, now time.Time) (serviceIDs []string, err error) {
	var normalIDs []string
	var addedIDs []string
	var removedIDs []string

	removed := map[string]bool{}

	// Select the service_ids that:
	//   * matches our agencyID, routeID, day
	//   * has an end_date after now
	//   * has a start_date before now

	q := `
		SELECT service_id
		FROM   service_route_day
		WHERE  day = $1 AND
			   end_date >= $2 AND
			   start_date <= $3 AND
			   route_id = $4 AND
			   agency_id = $5
	`

	err = sqlx.Select(db, &normalIDs, q, day, now, now, routeID, agencyID)
	if err != nil {
		log.Println("can't scan service ids", err, q, day, now, routeID, agencyID)
		return
	}

	// Get services added / removed
	q = `
		SELECT service_id
		FROM   service_route_exception
		WHERE  exception_date = $1 AND
			   route_id = $2 AND
			   agency_id = $3 AND
			   exception_type = $4
	`

	// Added
	err = sqlx.Select(db, &addedIDs, q, now, routeID, agencyID, ServiceAdded)
	if err != nil {
		log.Println("can't scan service ids", err, q, day, now, routeID, agencyID, ServiceAdded)
		return
	}

	// Removed
	err = sqlx.Select(db, &removedIDs, q, now, routeID, agencyID, ServiceRemoved)
	if err != nil {
		log.Println("can't scan service ids", err, q, day, now, routeID, agencyID, ServiceRemoved)
		return
	}

	for _, v := range removedIDs {
		removed[v] = true
	}

	for _, v := range normalIDs {
		if !removed[v] {
			serviceIDs = append(serviceIDs, v)
		}
	}

	for _, v := range addedIDs {
		if !removed[v] {
			serviceIDs = append(serviceIDs, v)
		}
	}

	return
}
<|endoftext|>


<commit_before>package shared

import (
	"strings"

	"github.com/pkg/errors"
)

// ArgsDelVersion describes arguments to delete version command
type ArgsDelVersion struct {
	Name    string `flag:"name,component name"`
	Version string `flag:"version,unique version id"`
}

// Validate checks arguments sanity
func (a *ArgsDelVersion) Validate() error {
	if a.Name == "" || a.Version == "" {
		return errors.New("both name and version should be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	return nil
}

// ArgsDelComponent describes arguments to delete component command
type ArgsDelComponent struct {
	Name string `flag:"name,component name"`
}

// Validate checks arguments sanity
func (a *ArgsDelComponent) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	return nil
}

// ArgsDelConfiguration describes arguments to delete configuration command
type ArgsDelConfiguration struct {
	Name  string `flag:"name,configuration name"`
	Force bool   `flag:"force,remove configuration for real"`
}

// Validate checks arguments sanity
func (a *ArgsDelConfiguration) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsAddConfiguration describes arguments to configuratin add command
type ArgsAddConfiguration struct {
	Name   string       `flag:"name,configuration name"`
	Layers compVerSlice `flag:"layer,layer in component:version format; can be set multiple times"`
}

// Validate checks arguments sanity
func (a *ArgsAddConfiguration) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if len(a.Layers) == 0 {
		return errors.New("configuration should have at least one layer")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsUpdateConfiguration describes update configuration command arguments
type ArgsUpdateConfiguration struct {
	Name string `flag:"name,configuration name"`
	Comp string `flag:"component,component name to update"`
	Ver  string `flag:"version,new version of selected component"`
}

// Validate checks arguments sanity
func (a *ArgsUpdateConfiguration) Validate() error {
	if a.Name == "" || a.Comp == "" || a.Ver == "" {
		return errors.New("name, component and version should all be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsBumpConfiguration describes arguments for command to update single
// configuration layer to its most recent version
type ArgsBumpConfiguration struct {
	Name string `flag:"name,configuration name"`
	Comp string `flag:"component,component name to update"`
}

// Validate checks arguments sanity
func (a *ArgsBumpConfiguration) Validate() error {
	if a.Name == "" || a.Comp == "" {
		return errors.New("both name and component should be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsShowConfiguration describes show configuration command arguments
type ArgsShowConfiguration struct {
	Name    string `flag:"name,configuration name"`
	Verbose bool   `flag:"v,show extra details"`
}

// Validate checks arguments sanity
func (a *ArgsShowConfiguration) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsShowComponent describes show component command arguments
type ArgsShowComponent struct {
	Name string `flag:"name,component name"`
}

// Validate checks arguments sanity
func (a *ArgsShowComponent) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	return nil
}

// ArgsAddVersionByHash describes arguments to add component version command
// when version is added by its hash from previously downloaded file
type ArgsAddVersionByHash struct {
	Name    string `flag:"name,component name"`
	Version string `flag:"version,unique version id"`
	Hash    string `flag:"hash,sha256 content hash in hex representation (64 chars)"`
}

// Validate checks arguments sanity
func (a *ArgsAddVersionByHash) Validate() error {
	if a.Name == "" || a.Version == "" || a.Hash == "" {
		return errors.New("name, version and hash should all be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	if len(a.Hash) != 64 {
		return errors.New("hash should be a hex representation of content sha256 sum, 64 chars long")
	}
	return nil
}

// ArgsAddVersionByFile describes arguments to add component version command
// when version is added by uploading file
type ArgsAddVersionByFile struct {
	Name    string `flag:"name,component name"`
	Version string `flag:"version,unique version id"`
	File    string `flag:"file,tar.gz file to upload"`
}

// Validate checks arguments sanity
func (a *ArgsAddVersionByFile) Validate() error {
	if a.Name == "" || a.Version == "" || a.File == "" {
		return errors.New("name, version and file should all be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	return nil
}

// compVer holds single layer specification as passed by operator
type compVer struct {
	Comp, Ver string
}

// compVerSlice implements flag.Value interface
type compVerSlice []compVer

func (c *compVerSlice) String() string { return "" }
func (c *compVerSlice) Set(value string) error {
	flds := strings.SplitN(value, ":", 2)
	if len(flds) != 2 {
		return errors.New("invalid value")
	}
	for _, v := range *c {
		// XXX: this may not the best way to check for dupes, but
		// normally number of layers is expected to be small, so leave
		// this as is for now
		if v.Comp == flds[0] {
			return errors.Errorf("duplicate component %q", flds[0])
		}
	}
	*c = append(*c, compVer{Comp: flds[0], Ver: flds[1]})
	return nil
}

// CommandsListing is used to print listing of all supported commands with their
// short description
const CommandsListing = `
addver          add new component version from previously uploaded file
addconf         add new configuration from existing component versions
bumpconf        update single layer of configuration to most recent uploaded version
changeconf      update single layer of configuration to specifig version
showconf        show configuration
showcomp        show component versions
components      list all known components
configurations  list all known configurations
delver          delete component version
delcomp         delete all component versions
delconf         delete configuration

use -h flag to get more help on a specific command
`
<commit_msg>Fix typo in help text<commit_after>package shared

import (
	"strings"

	"github.com/pkg/errors"
)

// ArgsDelVersion describes arguments to delete version command
type ArgsDelVersion struct {
	Name    string `flag:"name,component name"`
	Version string `flag:"version,unique version id"`
}

// Validate checks arguments sanity
func (a *ArgsDelVersion) Validate() error {
	if a.Name == "" || a.Version == "" {
		return errors.New("both name and version should be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	return nil
}

// ArgsDelComponent describes arguments to delete component command
type ArgsDelComponent struct {
	Name string `flag:"name,component name"`
}

// Validate checks arguments sanity
func (a *ArgsDelComponent) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	return nil
}

// ArgsDelConfiguration describes arguments to delete configuration command
type ArgsDelConfiguration struct {
	Name  string `flag:"name,configuration name"`
	Force bool   `flag:"force,remove configuration for real"`
}

// Validate checks arguments sanity
func (a *ArgsDelConfiguration) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsAddConfiguration describes arguments to configuratin add command
type ArgsAddConfiguration struct {
	Name   string       `flag:"name,configuration name"`
	Layers compVerSlice `flag:"layer,layer in component:version format; can be set multiple times"`
}

// Validate checks arguments sanity
func (a *ArgsAddConfiguration) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if len(a.Layers) == 0 {
		return errors.New("configuration should have at least one layer")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsUpdateConfiguration describes update configuration command arguments
type ArgsUpdateConfiguration struct {
	Name string `flag:"name,configuration name"`
	Comp string `flag:"component,component name to update"`
	Ver  string `flag:"version,new version of selected component"`
}

// Validate checks arguments sanity
func (a *ArgsUpdateConfiguration) Validate() error {
	if a.Name == "" || a.Comp == "" || a.Ver == "" {
		return errors.New("name, component and version should all be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsBumpConfiguration describes arguments for command to update single
// configuration layer to its most recent version
type ArgsBumpConfiguration struct {
	Name string `flag:"name,configuration name"`
	Comp string `flag:"component,component name to update"`
}

// Validate checks arguments sanity
func (a *ArgsBumpConfiguration) Validate() error {
	if a.Name == "" || a.Comp == "" {
		return errors.New("both name and component should be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsShowConfiguration describes show configuration command arguments
type ArgsShowConfiguration struct {
	Name    string `flag:"name,configuration name"`
	Verbose bool   `flag:"v,show extra details"`
}

// Validate checks arguments sanity
func (a *ArgsShowConfiguration) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if strings.ContainsRune(a.Name, '/') {
		return errors.New("name cannot contain / symbol")
	}
	return nil
}

// ArgsShowComponent describes show component command arguments
type ArgsShowComponent struct {
	Name string `flag:"name,component name"`
}

// Validate checks arguments sanity
func (a *ArgsShowComponent) Validate() error {
	if a.Name == "" {
		return errors.New("name should be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	return nil
}

// ArgsAddVersionByHash describes arguments to add component version command
// when version is added by its hash from previously downloaded file
type ArgsAddVersionByHash struct {
	Name    string `flag:"name,component name"`
	Version string `flag:"version,unique version id"`
	Hash    string `flag:"hash,sha256 content hash in hex representation (64 chars)"`
}

// Validate checks arguments sanity
func (a *ArgsAddVersionByHash) Validate() error {
	if a.Name == "" || a.Version == "" || a.Hash == "" {
		return errors.New("name, version and hash should all be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	if len(a.Hash) != 64 {
		return errors.New("hash should be a hex representation of content sha256 sum, 64 chars long")
	}
	return nil
}

// ArgsAddVersionByFile describes arguments to add component version command
// when version is added by uploading file
type ArgsAddVersionByFile struct {
	Name    string `flag:"name,component name"`
	Version string `flag:"version,unique version id"`
	File    string `flag:"file,tar.gz file to upload"`
}

// Validate checks arguments sanity
func (a *ArgsAddVersionByFile) Validate() error {
	if a.Name == "" || a.Version == "" || a.File == "" {
		return errors.New("name, version and file should all be set")
	}
	if strings.ContainsRune(a.Name, ':') {
		return errors.New("name cannot contain : symbol")
	}
	return nil
}

// compVer holds single layer specification as passed by operator
type compVer struct {
	Comp, Ver string
}

// compVerSlice implements flag.Value interface
type compVerSlice []compVer

func (c *compVerSlice) String() string { return "" }
func (c *compVerSlice) Set(value string) error {
	flds := strings.SplitN(value, ":", 2)
	if len(flds) != 2 {
		return errors.New("invalid value")
	}
	for _, v := range *c {
		// XXX: this may not the best way to check for dupes, but
		// normally number of layers is expected to be small, so leave
		// this as is for now
		if v.Comp == flds[0] {
			return errors.Errorf("duplicate component %q", flds[0])
		}
	}
	*c = append(*c, compVer{Comp: flds[0], Ver: flds[1]})
	return nil
}

// CommandsListing is used to print listing of all supported commands with their
// short description
const CommandsListing = `
addver          add new component version from previously uploaded file
addconf         add new configuration from existing component versions
bumpconf        update single layer of configuration to most recent uploaded version
changeconf      update single layer of configuration to specific version
showconf        show configuration
showcomp        show component versions
components      list all known components
configurations  list all known configurations
delver          delete component version
delcomp         delete all component versions
delconf         delete configuration

use -h flag to get more help on a specific command
`
<|endoftext|>