<commit_before>package gossl

/*
#include "openssl/ssl.h"
#include "openssl/err.h"
extern int get_errno(void);

*/
import "C"
import "unsafe"
import "syscall"
import "github.com/shanemhansen/gossl/sslerr"
import "errors"

type SSL struct {
	SSL *C.SSL
}

func NewSSL(context *Context) *SSL {
	ssl := &SSL{C.SSL_new(context.Ctx)}
	return ssl
}

func (self *SSL) Free() {
	C.SSL_free(self.SSL)
}
func (self *SSL) SetBIO(readbio *BIO, writebio *BIO) {
	C.SSL_set_bio(self.SSL,
		(*C.BIO)(unsafe.Pointer(readbio.BIO)),
		(*C.BIO)(unsafe.Pointer(writebio.BIO)))
	C.SSL_set_accept_state(self.SSL)
}
func (self *SSL) SetAcceptState() {
	C.SSL_set_accept_state(self.SSL)
}
func (self *SSL) Shutdown() error {
	//shutdown should happen in 2 steps
	//see http://www.openssl.org/docs/ssl/SSL_shutdown.html
	defer self.Free()
	ret := C.SSL_shutdown(self.SSL)
	if int(ret) == 0 {
		ret = C.SSL_shutdown(self.SSL)
		if int(ret) != 1 {
			return self.getError(ret)
		}

	}
	return nil

}
func (self *SSL) Handshake() error {
	ret := C.SSL_do_handshake(self.SSL)
	return self.getError(ret)
}
func (self *SSL) Read(b []byte) (int, error) {
	length := len(b)
	ret := C.SSL_read(self.SSL, unsafe.Pointer(&b[0]), C.int(length))
	return length, self.getError(ret)
}
func (self *SSL) Write(b []byte) (int, error) {
	length := len(b)
	ret := C.SSL_write(self.SSL, unsafe.Pointer(&b[0]), C.int(length))
	return length, self.getError(ret)
}
func (self *SSL) getError(ret C.int) error {
	err := C.SSL_get_error(self.SSL, ret)
	switch err {
	case C.SSL_ERROR_NONE:
	case C.SSL_ERROR_ZERO_RETURN:
		return nil
	case C.SSL_ERROR_SYSCALL:
		if int(C.ERR_peek_error()) != 0 {
			return syscall.Errno(C.get_errno())
		}

	default:
		msg := sslerr.SSLErrorMessage()
		return errors.New(msg)
	}
	return nil
}
<commit_msg>return proper length and errors<commit_after>package gossl

/*
#include "openssl/ssl.h"
#include "openssl/err.h"
extern int get_errno(void);

*/
import "C"
import "unsafe"
import "syscall"
import "github.com/shanemhansen/gossl/sslerr"
import "errors"

type SSL struct {
	SSL *C.SSL
}

func NewSSL(context *Context) *SSL {
	ssl := &SSL{C.SSL_new(context.Ctx)}
	return ssl
}

func (self *SSL) Free() {
	C.SSL_free(self.SSL)
}
func (self *SSL) SetBIO(readbio *BIO, writebio *BIO) {
	C.SSL_set_bio(self.SSL,
		(*C.BIO)(unsafe.Pointer(readbio.BIO)),
		(*C.BIO)(unsafe.Pointer(writebio.BIO)))
	C.SSL_set_accept_state(self.SSL)
}
func (self *SSL) SetAcceptState() {
	C.SSL_set_accept_state(self.SSL)
}
func (self *SSL) Shutdown() error {
	//shutdown should happen in 2 steps
	//see http://www.openssl.org/docs/ssl/SSL_shutdown.html
	defer self.Free()
	ret := C.SSL_shutdown(self.SSL)
	if int(ret) == 0 {
		ret = C.SSL_shutdown(self.SSL)
		if int(ret) != 1 {
			return self.getError(ret)
		}

	}
	return nil

}
func (self *SSL) Handshake() error {
	ret := C.SSL_do_handshake(self.SSL)
	return self.getError(ret)
}
func (self *SSL) Read(b []byte) (int, error) {
	length := len(b)
	ret := C.SSL_read(self.SSL, unsafe.Pointer(&b[0]), C.int(length))
	if err := self.getError(ret); err != nil {
		return 0, err
	}

	return int(ret), nil
}
func (self *SSL) Write(b []byte) (int, error) {
	length := len(b)
	ret := C.SSL_write(self.SSL, unsafe.Pointer(&b[0]), C.int(length))
	if err := self.getError(ret); err != nil {
		return 0, err
	}

	return int(ret), nil
}
func (self *SSL) getError(ret C.int) error {
	err := C.SSL_get_error(self.SSL, ret)
	switch err {
	case C.SSL_ERROR_NONE:
		return nil
	case C.SSL_ERROR_ZERO_RETURN:
		return io.EOF
	case C.SSL_ERROR_SYSCALL:
		if int(C.ERR_peek_error()) != 0 {
			return syscall.Errno(C.get_errno())
		}

	default:
		msg := sslerr.SSLErrorMessage()
		return errors.New(msg)
	}
	return nil
}
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/99designs/aws-vault/Godeps/_workspace/src/github.com/mitchellh/cli"
	"github.com/99designs/aws-vault/keyring"
	"github.com/99designs/aws-vault/vault"
)

type RemoveCommand struct {
	Ui             cli.Ui
	Keyring        keyring.Keyring
	DefaultProfile string
}

func (c *RemoveCommand) Run(args []string) int {
	var (
		profileName string
	)
	flagSet := flag.NewFlagSet("rm", flag.ExitOnError)
	flagSet.StringVar(&profileName, "profile", c.DefaultProfile, "")
	flagSet.StringVar(&profileName, "p", c.DefaultProfile, "")
	flagSet.Usage = func() { c.Ui.Output(c.Help()) }

	if err := flagSet.Parse(args); err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	r, err := c.Ui.Ask(fmt.Sprintf("Delete credentials for profile %q? (Y|n)", profileName))
	if err != nil {
		c.Ui.Error(err.Error())
		return 2
	} else if r == "N" || r == "n" {
		return 3
	}

	if err := c.Keyring.Remove(vault.ServiceName, profileName); err != nil {
		c.Ui.Error(err.Error())
		return 4
	}

	c.Ui.Info(fmt.Sprintf("\nRemoved credentials for profile %q from vault", profileName))
	return 0
}

func (c *RemoveCommand) Help() string {
	helpText := `
Usage: aws-vault rm <keyname>
  Removes credentials from vault
`
	return strings.TrimSpace(helpText)
}

func (c *RemoveCommand) Synopsis() string {
	return "Remove credentials from vault"
}
<commit_msg>Remove sessions when using rm<commit_after>package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/99designs/aws-vault/Godeps/_workspace/src/github.com/mitchellh/cli"
	"github.com/99designs/aws-vault/keyring"
	"github.com/99designs/aws-vault/vault"
)

type RemoveCommand struct {
	Ui             cli.Ui
	Keyring        keyring.Keyring
	DefaultProfile string
}

func (c *RemoveCommand) Run(args []string) int {
	var (
		profileName string
	)
	flagSet := flag.NewFlagSet("rm", flag.ExitOnError)
	flagSet.StringVar(&profileName, "profile", c.DefaultProfile, "")
	flagSet.StringVar(&profileName, "p", c.DefaultProfile, "")
	flagSet.Usage = func() { c.Ui.Output(c.Help()) }

	if err := flagSet.Parse(args); err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	r, err := c.Ui.Ask(fmt.Sprintf("Delete credentials for profile %q? (Y|n)", profileName))
	if err != nil {
		c.Ui.Error(err.Error())
		return 2
	} else if r == "N" || r == "n" {
		return 3
	}

	if err := c.Keyring.Remove(vault.ServiceName, profileName); err != nil {
		c.Ui.Error(err.Error())
		return 4
	}

	// remove session
	_, err = c.Keyring.Get(vault.SessionServiceName, profileName)
	sessionExists := (err == nil)
	if sessionExists {
		if err := c.Keyring.Remove(vault.SessionServiceName, profileName); err != nil {
			c.Ui.Error(err.Error())
			return 5
		}
	}

	c.Ui.Info(fmt.Sprintf("\nRemoved credentials and sessions for profile %q from vault", profileName))

	return 0
}

func (c *RemoveCommand) Help() string {
	helpText := `
Usage: aws-vault rm <keyname>
  Removes credentials from vault
`
	return strings.TrimSpace(helpText)
}

func (c *RemoveCommand) Synopsis() string {
	return "Remove credentials from vault"
}
<|endoftext|>


<commit_before>package command

import (
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	// maxFailedTGs is the maximum number of task groups we show failure reasons
	// for before defering to eval-status
	maxFailedTGs = 5
)

type StatusCommand struct {
	Meta
	length    int
	evals     bool
	allAllocs bool
	verbose   bool
}

func (c *StatusCommand) Help() string {
	helpText := `
Usage: nomad status [options] <job>

  Display status information about jobs. If no job ID is given,
  a list of all known jobs will be dumped.

General Options:

  ` + generalOptionsUsage() + `

Status Options:

  -short
    Display short output. Used only when a single job is being
    queried, and drops verbose information about allocations.

  -evals
    Display the evaluations associated with the job.

  -all-allocs
    Display all allocations matching the job ID, including those from an older
    instance of the job.

  -verbose
    Display full information.
`
	return strings.TrimSpace(helpText)
}

func (c *StatusCommand) Synopsis() string {
	return "Display status information about jobs"
}

func (c *StatusCommand) Run(args []string) int {
	var short bool

	flags := c.Meta.FlagSet("status", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&short, "short", false, "")
	flags.BoolVar(&c.evals, "evals", false, "")
	flags.BoolVar(&c.allAllocs, "all-allocs", false, "")
	flags.BoolVar(&c.verbose, "verbose", false, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Check that we either got no jobs or exactly one.
	args = flags.Args()
	if len(args) > 1 {
		c.Ui.Error(c.Help())
		return 1
	}

	// Truncate the id unless full length is requested
	c.length = shortId
	if c.verbose {
		c.length = fullId
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Invoke list mode if no job ID.
	if len(args) == 0 {
		jobs, _, err := client.Jobs().List(nil)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error querying jobs: %s", err))
			return 1
		}

		if len(jobs) == 0 {
			// No output if we have no jobs
			c.Ui.Output("No running jobs")
		} else {
			c.Ui.Output(createStatusListOutput(jobs))
		}
		return 0
	}

	// Try querying the job
	jobID := args[0]
	jobs, _, err := client.Jobs().PrefixList(jobID)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying job: %s", err))
		return 1
	}
	if len(jobs) == 0 {
		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
		return 1
	}
	if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID {
		c.Ui.Output(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs)))
		return 0
	}
	// Prefix lookup matched a single job
	job, _, err := client.Jobs().Info(jobs[0].ID, nil)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying job: %s", err))
		return 1
	}

	periodic := job.IsPeriodic()
	parameterized := job.IsParameterized()

	// Format the job info
	basic := []string{
		fmt.Sprintf("ID|%s", *job.ID),
		fmt.Sprintf("Name|%s", *job.Name),
		fmt.Sprintf("Type|%s", *job.Type),
		fmt.Sprintf("Priority|%d", *job.Priority),
		fmt.Sprintf("Datacenters|%s", strings.Join(job.Datacenters, ",")),
		fmt.Sprintf("Status|%s", *job.Status),
		fmt.Sprintf("Periodic|%v", periodic),
		fmt.Sprintf("Parameterized|%v", parameterized),
	}

	if periodic {
		now := time.Now().UTC()
		next := job.Periodic.Next(now)
		basic = append(basic, fmt.Sprintf("Next Periodic Launch|%s",
			fmt.Sprintf("%s (%s from now)",
				formatTime(next), formatTimeDifference(now, next, time.Second))))
	}

	c.Ui.Output(formatKV(basic))

	// Exit early
	if short {
		return 0
	}

	// Print periodic job information
	if periodic {
		if err := c.outputPeriodicInfo(client, job); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
	} else if parameterized {
		if err := c.outputParameterizedInfo(client, job); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
	} else {
		if err := c.outputJobInfo(client, job); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
	}

	return 0
}

// outputPeriodicInfo prints information about the passed periodic job. If a
// request fails, an error is returned.
func (c *StatusCommand) outputPeriodicInfo(client *api.Client, job *api.Job) error {
	// Output the summary
	if err := c.outputJobSummary(client, job); err != nil {
		return err
	}

	// Generate the prefix that matches launched jobs from the periodic job.
	prefix := fmt.Sprintf("%s%s", job.ID, structs.PeriodicLaunchSuffix)
	children, _, err := client.Jobs().PrefixList(prefix)
	if err != nil {
		return fmt.Errorf("Error querying job: %s", err)
	}

	if len(children) == 0 {
		c.Ui.Output("\nNo instances of periodic job found")
		return nil
	}

	out := make([]string, 1)
	out[0] = "ID|Status"
	for _, child := range children {
		// Ensure that we are only showing jobs whose parent is the requested
		// job.
		if child.ParentID != *job.ID {
			continue
		}

		out = append(out, fmt.Sprintf("%s|%s",
			child.ID,
			child.Status))
	}

	c.Ui.Output(c.Colorize().Color("\n[bold]Previously Launched Jobs[reset]"))
	c.Ui.Output(formatList(out))
	return nil
}

// outputParameterizedInfo prints information about a parameterized job. If a
// request fails, an error is returned.
func (c *StatusCommand) outputParameterizedInfo(client *api.Client, job *api.Job) error {
	// Output parameterized job details
	c.Ui.Output(c.Colorize().Color("\n[bold]Parameterized Job[reset]"))
	parameterizedJob := make([]string, 3)
	parameterizedJob[0] = fmt.Sprintf("Payload|%s", job.ParameterizedJob.Payload)
	parameterizedJob[1] = fmt.Sprintf("Required Metadata|%v", strings.Join(job.ParameterizedJob.MetaRequired, ", "))
	parameterizedJob[2] = fmt.Sprintf("Optional Metadata|%v", strings.Join(job.ParameterizedJob.MetaOptional, ", "))
	c.Ui.Output(formatKV(parameterizedJob))

	// Output the summary
	if err := c.outputJobSummary(client, job); err != nil {
		return err
	}

	// Generate the prefix that matches launched jobs from the periodic job.
	prefix := fmt.Sprintf("%s%s", job.ID, structs.DispatchLaunchSuffix)
	children, _, err := client.Jobs().PrefixList(prefix)
	if err != nil {
		return fmt.Errorf("Error querying job: %s", err)
	}

	if len(children) == 0 {
		c.Ui.Output("\nNo dispatched instances of parameterized job found")
		return nil
	}

	out := make([]string, 1)
	out[0] = "ID|Status"
	for _, child := range children {
		// Ensure that we are only showing jobs whose parent is the requested
		// job.
		if child.ParentID != *job.ID {
			continue
		}

		out = append(out, fmt.Sprintf("%s|%s",
			child.ID,
			child.Status))
	}

	c.Ui.Output(c.Colorize().Color("\n[bold]Dispatched Jobs[reset]"))
	c.Ui.Output(formatList(out))
	return nil
}

// outputJobInfo prints information about the passed non-periodic job. If a
// request fails, an error is returned.
func (c *StatusCommand) outputJobInfo(client *api.Client, job *api.Job) error {
	var evals, allocs []string

	// Query the allocations
	jobAllocs, _, err := client.Jobs().Allocations(*job.ID, c.allAllocs, nil)
	if err != nil {
		return fmt.Errorf("Error querying job allocations: %s", err)
	}

	// Query the evaluations
	jobEvals, _, err := client.Jobs().Evaluations(*job.ID, nil)
	if err != nil {
		return fmt.Errorf("Error querying job evaluations: %s", err)
	}

	// Output the summary
	if err := c.outputJobSummary(client, job); err != nil {
		return err
	}

	// Determine latest evaluation with failures whose follow up hasn't
	// completed, this is done while formatting
	var latestFailedPlacement *api.Evaluation
	blockedEval := false

	// Format the evals
	evals = make([]string, len(jobEvals)+1)
	evals[0] = "ID|Priority|Triggered By|Status|Placement Failures"
	for i, eval := range jobEvals {
		failures, _ := evalFailureStatus(eval)
		evals[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s",
			limit(eval.ID, c.length),
			eval.Priority,
			eval.TriggeredBy,
			eval.Status,
			failures,
		)

		if eval.Status == "blocked" {
			blockedEval = true
		}

		if len(eval.FailedTGAllocs) == 0 {
			// Skip evals without failures
			continue
		}

		if latestFailedPlacement == nil || latestFailedPlacement.CreateIndex < eval.CreateIndex {
			latestFailedPlacement = eval
		}
	}

	if c.verbose || c.evals {
		c.Ui.Output(c.Colorize().Color("\n[bold]Evaluations[reset]"))
		c.Ui.Output(formatList(evals))
	}

	if blockedEval && latestFailedPlacement != nil {
		c.outputFailedPlacements(latestFailedPlacement)
	}

	// Format the allocs
	c.Ui.Output(c.Colorize().Color("\n[bold]Allocations[reset]"))
	if len(jobAllocs) > 0 {
		allocs = make([]string, len(jobAllocs)+1)
		allocs[0] = "ID|Eval ID|Node ID|Task Group|Desired|Status|Created At"
		for i, alloc := range jobAllocs {
			allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s",
				limit(alloc.ID, c.length),
				limit(alloc.EvalID, c.length),
				limit(alloc.NodeID, c.length),
				alloc.TaskGroup,
				alloc.DesiredStatus,
				alloc.ClientStatus,
				formatUnixNanoTime(alloc.CreateTime))
		}

		c.Ui.Output(formatList(allocs))
	} else {
		c.Ui.Output("No allocations placed")
	}
	return nil
}

// outputJobSummary displays the given jobs summary and children job summary
// where appropriate
func (c *StatusCommand) outputJobSummary(client *api.Client, job *api.Job) error {
	// Query the summary
	summary, _, err := client.Jobs().Summary(*job.ID, nil)
	if err != nil {
		return fmt.Errorf("Error querying job summary: %s", err)
	}

	if summary == nil {
		return nil
	}

	periodic := job.IsPeriodic()
	parameterizedJob := job.IsParameterized()

	// Print the summary
	if !periodic && !parameterizedJob {
		c.Ui.Output(c.Colorize().Color("\n[bold]Summary[reset]"))
		summaries := make([]string, len(summary.Summary)+1)
		summaries[0] = "Task Group|Queued|Starting|Running|Failed|Complete|Lost"
		taskGroups := make([]string, 0, len(summary.Summary))
		for taskGroup := range summary.Summary {
			taskGroups = append(taskGroups, taskGroup)
		}
		sort.Strings(taskGroups)
		for idx, taskGroup := range taskGroups {
			tgs := summary.Summary[taskGroup]
			summaries[idx+1] = fmt.Sprintf("%s|%d|%d|%d|%d|%d|%d",
				taskGroup, tgs.Queued, tgs.Starting,
				tgs.Running, tgs.Failed,
				tgs.Complete, tgs.Lost,
			)
		}
		c.Ui.Output(formatList(summaries))
	}

	// Always display the summary if we are periodic or parameterized, but
	// only display if the summary is non-zero on normal jobs
	if summary.Children != nil && (parameterizedJob || periodic || summary.Children.Sum() > 0) {
		if parameterizedJob {
			c.Ui.Output(c.Colorize().Color("\n[bold]Parameterized Job Summary[reset]"))
		} else {
			c.Ui.Output(c.Colorize().Color("\n[bold]Children Job Summary[reset]"))
		}
		summaries := make([]string, 2)
		summaries[0] = "Pending|Running|Dead"
		summaries[1] = fmt.Sprintf("%d|%d|%d",
			summary.Children.Pending, summary.Children.Running, summary.Children.Dead)
		c.Ui.Output(formatList(summaries))
	}

	return nil
}

func (c *StatusCommand) outputFailedPlacements(failedEval *api.Evaluation) {
	if failedEval == nil || len(failedEval.FailedTGAllocs) == 0 {
		return
	}

	c.Ui.Output(c.Colorize().Color("\n[bold]Placement Failure[reset]"))

	sorted := sortedTaskGroupFromMetrics(failedEval.FailedTGAllocs)
	for i, tg := range sorted {
		if i >= maxFailedTGs {
			break
		}

		c.Ui.Output(fmt.Sprintf("Task Group %q:", tg))
		metrics := failedEval.FailedTGAllocs[tg]
		c.Ui.Output(formatAllocMetrics(metrics, false, "  "))
		if i != len(sorted)-1 {
			c.Ui.Output("")
		}
	}

	if len(sorted) > maxFailedTGs {
		trunc := fmt.Sprintf("\nPlacement failures truncated. To see remainder run:\nnomad eval-status %s", failedEval.ID)
		c.Ui.Output(trunc)
	}
}

// list general information about a list of jobs
func createStatusListOutput(jobs []*api.JobListStub) string {
	out := make([]string, len(jobs)+1)
	out[0] = "ID|Type|Priority|Status"
	for i, job := range jobs {
		out[i+1] = fmt.Sprintf("%s|%s|%d|%s",
			job.ID,
			job.Type,
			job.Priority,
			job.Status)
	}
	return formatList(out)
}
<commit_msg>Fix CLI handling of parameterized/periodic jobs<commit_after>package command

import (
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	// maxFailedTGs is the maximum number of task groups we show failure reasons
	// for before defering to eval-status
	maxFailedTGs = 5
)

type StatusCommand struct {
	Meta
	length    int
	evals     bool
	allAllocs bool
	verbose   bool
}

func (c *StatusCommand) Help() string {
	helpText := `
Usage: nomad status [options] <job>

  Display status information about jobs. If no job ID is given,
  a list of all known jobs will be dumped.

General Options:

  ` + generalOptionsUsage() + `

Status Options:

  -short
    Display short output. Used only when a single job is being
    queried, and drops verbose information about allocations.

  -evals
    Display the evaluations associated with the job.

  -all-allocs
    Display all allocations matching the job ID, including those from an older
    instance of the job.

  -verbose
    Display full information.
`
	return strings.TrimSpace(helpText)
}

func (c *StatusCommand) Synopsis() string {
	return "Display status information about jobs"
}

func (c *StatusCommand) Run(args []string) int {
	var short bool

	flags := c.Meta.FlagSet("status", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&short, "short", false, "")
	flags.BoolVar(&c.evals, "evals", false, "")
	flags.BoolVar(&c.allAllocs, "all-allocs", false, "")
	flags.BoolVar(&c.verbose, "verbose", false, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Check that we either got no jobs or exactly one.
	args = flags.Args()
	if len(args) > 1 {
		c.Ui.Error(c.Help())
		return 1
	}

	// Truncate the id unless full length is requested
	c.length = shortId
	if c.verbose {
		c.length = fullId
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Invoke list mode if no job ID.
	if len(args) == 0 {
		jobs, _, err := client.Jobs().List(nil)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error querying jobs: %s", err))
			return 1
		}

		if len(jobs) == 0 {
			// No output if we have no jobs
			c.Ui.Output("No running jobs")
		} else {
			c.Ui.Output(createStatusListOutput(jobs))
		}
		return 0
	}

	// Try querying the job
	jobID := args[0]
	jobs, _, err := client.Jobs().PrefixList(jobID)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying job: %s", err))
		return 1
	}
	if len(jobs) == 0 {
		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
		return 1
	}
	if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID {
		c.Ui.Output(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs)))
		return 0
	}
	// Prefix lookup matched a single job
	job, _, err := client.Jobs().Info(jobs[0].ID, nil)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying job: %s", err))
		return 1
	}

	periodic := job.IsPeriodic()
	parameterized := job.IsParameterized()

	// Format the job info
	basic := []string{
		fmt.Sprintf("ID|%s", *job.ID),
		fmt.Sprintf("Name|%s", *job.Name),
		fmt.Sprintf("Type|%s", *job.Type),
		fmt.Sprintf("Priority|%d", *job.Priority),
		fmt.Sprintf("Datacenters|%s", strings.Join(job.Datacenters, ",")),
		fmt.Sprintf("Status|%s", *job.Status),
		fmt.Sprintf("Periodic|%v", periodic),
		fmt.Sprintf("Parameterized|%v", parameterized),
	}

	if periodic {
		now := time.Now().UTC()
		next := job.Periodic.Next(now)
		basic = append(basic, fmt.Sprintf("Next Periodic Launch|%s",
			fmt.Sprintf("%s (%s from now)",
				formatTime(next), formatTimeDifference(now, next, time.Second))))
	}

	c.Ui.Output(formatKV(basic))

	// Exit early
	if short {
		return 0
	}

	// Print periodic job information
	if periodic {
		if err := c.outputPeriodicInfo(client, job); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
	} else if parameterized {
		if err := c.outputParameterizedInfo(client, job); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
	} else {
		if err := c.outputJobInfo(client, job); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
	}

	return 0
}

// outputPeriodicInfo prints information about the passed periodic job. If a
// request fails, an error is returned.
func (c *StatusCommand) outputPeriodicInfo(client *api.Client, job *api.Job) error {
	// Output the summary
	if err := c.outputJobSummary(client, job); err != nil {
		return err
	}

	// Generate the prefix that matches launched jobs from the periodic job.
	prefix := fmt.Sprintf("%s%s", *job.ID, structs.PeriodicLaunchSuffix)
	children, _, err := client.Jobs().PrefixList(prefix)
	if err != nil {
		return fmt.Errorf("Error querying job: %s", err)
	}

	if len(children) == 0 {
		c.Ui.Output("\nNo instances of periodic job found")
		return nil
	}

	out := make([]string, 1)
	out[0] = "ID|Status"
	for _, child := range children {
		// Ensure that we are only showing jobs whose parent is the requested
		// job.
		if child.ParentID != *job.ID {
			continue
		}

		out = append(out, fmt.Sprintf("%s|%s",
			child.ID,
			child.Status))
	}

	c.Ui.Output(c.Colorize().Color("\n[bold]Previously Launched Jobs[reset]"))
	c.Ui.Output(formatList(out))
	return nil
}

// outputParameterizedInfo prints information about a parameterized job. If a
// request fails, an error is returned.
func (c *StatusCommand) outputParameterizedInfo(client *api.Client, job *api.Job) error {
	// Output parameterized job details
	c.Ui.Output(c.Colorize().Color("\n[bold]Parameterized Job[reset]"))
	parameterizedJob := make([]string, 3)
	parameterizedJob[0] = fmt.Sprintf("Payload|%s", job.ParameterizedJob.Payload)
	parameterizedJob[1] = fmt.Sprintf("Required Metadata|%v", strings.Join(job.ParameterizedJob.MetaRequired, ", "))
	parameterizedJob[2] = fmt.Sprintf("Optional Metadata|%v", strings.Join(job.ParameterizedJob.MetaOptional, ", "))
	c.Ui.Output(formatKV(parameterizedJob))

	// Output the summary
	if err := c.outputJobSummary(client, job); err != nil {
		return err
	}

	// Generate the prefix that matches launched jobs from the parameterized job.
	prefix := fmt.Sprintf("%s%s", *job.ID, structs.DispatchLaunchSuffix)
	children, _, err := client.Jobs().PrefixList(prefix)
	if err != nil {
		return fmt.Errorf("Error querying job: %s", err)
	}

	if len(children) == 0 {
		c.Ui.Output("\nNo dispatched instances of parameterized job found")
		return nil
	}

	out := make([]string, 1)
	out[0] = "ID|Status"
	for _, child := range children {
		// Ensure that we are only showing jobs whose parent is the requested
		// job.
		if child.ParentID != *job.ID {
			continue
		}

		out = append(out, fmt.Sprintf("%s|%s",
			child.ID,
			child.Status))
	}

	c.Ui.Output(c.Colorize().Color("\n[bold]Dispatched Jobs[reset]"))
	c.Ui.Output(formatList(out))
	return nil
}

// outputJobInfo prints information about the passed non-periodic job. If a
// request fails, an error is returned.
func (c *StatusCommand) outputJobInfo(client *api.Client, job *api.Job) error {
	var evals, allocs []string

	// Query the allocations
	jobAllocs, _, err := client.Jobs().Allocations(*job.ID, c.allAllocs, nil)
	if err != nil {
		return fmt.Errorf("Error querying job allocations: %s", err)
	}

	// Query the evaluations
	jobEvals, _, err := client.Jobs().Evaluations(*job.ID, nil)
	if err != nil {
		return fmt.Errorf("Error querying job evaluations: %s", err)
	}

	// Output the summary
	if err := c.outputJobSummary(client, job); err != nil {
		return err
	}

	// Determine latest evaluation with failures whose follow up hasn't
	// completed, this is done while formatting
	var latestFailedPlacement *api.Evaluation
	blockedEval := false

	// Format the evals
	evals = make([]string, len(jobEvals)+1)
	evals[0] = "ID|Priority|Triggered By|Status|Placement Failures"
	for i, eval := range jobEvals {
		failures, _ := evalFailureStatus(eval)
		evals[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s",
			limit(eval.ID, c.length),
			eval.Priority,
			eval.TriggeredBy,
			eval.Status,
			failures,
		)

		if eval.Status == "blocked" {
			blockedEval = true
		}

		if len(eval.FailedTGAllocs) == 0 {
			// Skip evals without failures
			continue
		}

		if latestFailedPlacement == nil || latestFailedPlacement.CreateIndex < eval.CreateIndex {
			latestFailedPlacement = eval
		}
	}

	if c.verbose || c.evals {
		c.Ui.Output(c.Colorize().Color("\n[bold]Evaluations[reset]"))
		c.Ui.Output(formatList(evals))
	}

	if blockedEval && latestFailedPlacement != nil {
		c.outputFailedPlacements(latestFailedPlacement)
	}

	// Format the allocs
	c.Ui.Output(c.Colorize().Color("\n[bold]Allocations[reset]"))
	if len(jobAllocs) > 0 {
		allocs = make([]string, len(jobAllocs)+1)
		allocs[0] = "ID|Eval ID|Node ID|Task Group|Desired|Status|Created At"
		for i, alloc := range jobAllocs {
			allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s",
				limit(alloc.ID, c.length),
				limit(alloc.EvalID, c.length),
				limit(alloc.NodeID, c.length),
				alloc.TaskGroup,
				alloc.DesiredStatus,
				alloc.ClientStatus,
				formatUnixNanoTime(alloc.CreateTime))
		}

		c.Ui.Output(formatList(allocs))
	} else {
		c.Ui.Output("No allocations placed")
	}
	return nil
}

// outputJobSummary displays the given jobs summary and children job summary
// where appropriate
func (c *StatusCommand) outputJobSummary(client *api.Client, job *api.Job) error {
	// Query the summary
	summary, _, err := client.Jobs().Summary(*job.ID, nil)
	if err != nil {
		return fmt.Errorf("Error querying job summary: %s", err)
	}

	if summary == nil {
		return nil
	}

	periodic := job.IsPeriodic()
	parameterizedJob := job.IsParameterized()

	// Print the summary
	if !periodic && !parameterizedJob {
		c.Ui.Output(c.Colorize().Color("\n[bold]Summary[reset]"))
		summaries := make([]string, len(summary.Summary)+1)
		summaries[0] = "Task Group|Queued|Starting|Running|Failed|Complete|Lost"
		taskGroups := make([]string, 0, len(summary.Summary))
		for taskGroup := range summary.Summary {
			taskGroups = append(taskGroups, taskGroup)
		}
		sort.Strings(taskGroups)
		for idx, taskGroup := range taskGroups {
			tgs := summary.Summary[taskGroup]
			summaries[idx+1] = fmt.Sprintf("%s|%d|%d|%d|%d|%d|%d",
				taskGroup, tgs.Queued, tgs.Starting,
				tgs.Running, tgs.Failed,
				tgs.Complete, tgs.Lost,
			)
		}
		c.Ui.Output(formatList(summaries))
	}

	// Always display the summary if we are periodic or parameterized, but
	// only display if the summary is non-zero on normal jobs
	if summary.Children != nil && (parameterizedJob || periodic || summary.Children.Sum() > 0) {
		if parameterizedJob {
			c.Ui.Output(c.Colorize().Color("\n[bold]Parameterized Job Summary[reset]"))
		} else {
			c.Ui.Output(c.Colorize().Color("\n[bold]Children Job Summary[reset]"))
		}
		summaries := make([]string, 2)
		summaries[0] = "Pending|Running|Dead"
		summaries[1] = fmt.Sprintf("%d|%d|%d",
			summary.Children.Pending, summary.Children.Running, summary.Children.Dead)
		c.Ui.Output(formatList(summaries))
	}

	return nil
}

func (c *StatusCommand) outputFailedPlacements(failedEval *api.Evaluation) {
	if failedEval == nil || len(failedEval.FailedTGAllocs) == 0 {
		return
	}

	c.Ui.Output(c.Colorize().Color("\n[bold]Placement Failure[reset]"))

	sorted := sortedTaskGroupFromMetrics(failedEval.FailedTGAllocs)
	for i, tg := range sorted {
		if i >= maxFailedTGs {
			break
		}

		c.Ui.Output(fmt.Sprintf("Task Group %q:", tg))
		metrics := failedEval.FailedTGAllocs[tg]
		c.Ui.Output(formatAllocMetrics(metrics, false, "  "))
		if i != len(sorted)-1 {
			c.Ui.Output("")
		}
	}

	if len(sorted) > maxFailedTGs {
		trunc := fmt.Sprintf("\nPlacement failures truncated. To see remainder run:\nnomad eval-status %s", failedEval.ID)
		c.Ui.Output(trunc)
	}
}

// list general information about a list of jobs
func createStatusListOutput(jobs []*api.JobListStub) string {
	out := make([]string, len(jobs)+1)
	out[0] = "ID|Type|Priority|Status"
	for i, job := range jobs {
		out[i+1] = fmt.Sprintf("%s|%s|%d|%s",
			job.ID,
			job.Type,
			job.Priority,
			job.Status)
	}
	return formatList(out)
}
<|endoftext|>


<commit_before>package tag

import (
	"crypto/sha1"
	"encoding/binary"
	"fmt"
	"hash"
	"io"
	"os"
)

// Sum creates a checksum of the audio file data provided by the io.ReadSeeker which is metadata
// (ID3, MP4) invariant.
func Sum(r io.ReadSeeker) (string, error) {
	b, err := readBytes(r, 11)
	if err != nil {
		return "", err
	}

	_, err = r.Seek(-11, os.SEEK_CUR)
	if err != nil {
		return "", fmt.Errorf("could not seek back to original position: %v", err)
	}

	if string(b[4:11]) == "ftypM4A" {
		return SumAtoms(r)
	}

	if string(b[0:3]) == "ID3" {
		return SumID3v2(r)
	}

	h, err := SumID3v1(r)
	if err != nil {
		if err == ErrNotID3v1 {
			return SumAll(r)
		}
		return "", err
	}
	return h, nil
}

// SumAll returns a checksum of the content from the reader (until EOF).
func SumAll(r io.ReadSeeker) (string, error) {
	h := sha1.New()
	_, err := io.Copy(h, r)
	if err != nil {
		return "", nil
	}
	return hashSum(h), nil
}

// SumAtoms constructs a checksum of MP4 audio file data provided by the io.ReadSeeker which is
// metadata invariant.
func SumAtoms(r io.ReadSeeker) (string, error) {
	for {
		var size uint32
		err := binary.Read(r, binary.BigEndian, &size)
		if err != nil {
			if err == io.EOF {
				return "", fmt.Errorf("reached EOF before audio data")
			}
			return "", err
		}

		name, err := readString(r, 4)
		if err != nil {
			return "", err
		}

		switch name {
		case "meta":
			// next_item_id (int32)
			_, err := r.Seek(4, os.SEEK_CUR)
			if err != nil {
				return "", err
			}
			fallthrough

		case "moov", "udta", "ilst":
			continue

		case "mdat": // stop when we get to the data
			h := sha1.New()
			_, err := io.CopyN(h, r, int64(size-8))
			if err != nil {
				return "", fmt.Errorf("error reading audio data: %v", err)
			}
			return hashSum(h), nil
		}

		_, err = r.Seek(int64(size-8), os.SEEK_CUR)
		if err != nil {
			return "", fmt.Errorf("error reading '%v' tag: %v", name, err)
		}
	}
}

// SumID3v1 constructs a checksum of MP3 audio file data (assumed to have ID3v1 tags) provided
// by the io.ReadSeeker which is metadata invariant.
func SumID3v1(r io.ReadSeeker) (string, error) {
	// Need to stop before we hit potential ID3v1 data.
	n, err := r.Seek(-128, os.SEEK_END)
	if err != nil {
		return "", fmt.Errorf("error seeking to the end of the file (minus ID3v1 header): %v", err)
	}

	// TODO: improve this check???
	if n <= 0 {
		return "", fmt.Errorf("file size must be greater than 128 bytes (ID3v1 header size) for MP3")
	}

	// Seek back to the original position now!
	_, err = r.Seek(-1*n, os.SEEK_SET)
	if err != nil {
		return "", fmt.Errorf("error seeking back to the start of the data: %v", err)
	}

	h := sha1.New()
	_, err = io.CopyN(h, r, n)
	if err != nil {
		return "", fmt.Errorf("error reading %v bytes: %v", n, err)
	}
	return hashSum(h), nil
}

// SumID3v2 constructs a checksum of MP3 audio file data (assumed to have ID3v2 tags) provided by the
// io.ReadSeeker which is metadata invariant.
func SumID3v2(r io.ReadSeeker) (string, error) {
	header, err := readID3v2Header(r)
	if err != nil {
		return "", fmt.Errorf("error reading ID3v2 header: %v", err)
	}

	_, err = r.Seek(int64(header.Size), os.SEEK_CUR)
	if err != nil {
		return "", fmt.Errorf("error seeking to end of ID3V2 header: %v", err)
	}

	// Need to stop before we hit potential ID3v1 data.
	n, err := r.Seek(-128, os.SEEK_END)
	if err != nil {
		return "", fmt.Errorf("error seeking to the end of the file (minus ID3v1 header): %v", err)
	}

	// TODO: remove this check?????
	if n < 0 {
		return "", fmt.Errorf("file size must be greater than 128 bytes for MP3: %v bytes", n)
	}

	// Seek back to the original position now!
	_, err = r.Seek(-1*n, os.SEEK_SET)
	if err != nil {
		return "", fmt.Errorf("error seeking back to the start of the data: %v", err)
	}

	h := sha1.New()
	_, err = io.CopyN(h, r, n)
	if err != nil {
		return "", fmt.Errorf("error reading %v bytes: %v", n, err)
	}
	return hashSum(h), nil
}

func hashSum(h hash.Hash) string {
	return fmt.Sprintf("%x", h.Sum([]byte{}))
}
<commit_msg>Refactor common code to skip last 128 bytes from ReadSeeker.<commit_after>package tag

import (
	"crypto/sha1"
	"encoding/binary"
	"fmt"
	"hash"
	"io"
	"os"
)

// Sum creates a checksum of the audio file data provided by the io.ReadSeeker which is metadata
// (ID3, MP4) invariant.
func Sum(r io.ReadSeeker) (string, error) {
	b, err := readBytes(r, 11)
	if err != nil {
		return "", err
	}

	_, err = r.Seek(-11, os.SEEK_CUR)
	if err != nil {
		return "", fmt.Errorf("could not seek back to original position: %v", err)
	}

	if string(b[4:11]) == "ftypM4A" {
		return SumAtoms(r)
	}

	if string(b[0:3]) == "ID3" {
		return SumID3v2(r)
	}

	h, err := SumID3v1(r)
	if err != nil {
		if err == ErrNotID3v1 {
			return SumAll(r)
		}
		return "", err
	}
	return h, nil
}

// SumAll returns a checksum of the content from the reader (until EOF).
func SumAll(r io.ReadSeeker) (string, error) {
	h := sha1.New()
	_, err := io.Copy(h, r)
	if err != nil {
		return "", nil
	}
	return hashSum(h), nil
}

// SumAtoms constructs a checksum of MP4 audio file data provided by the io.ReadSeeker which is
// metadata invariant.
func SumAtoms(r io.ReadSeeker) (string, error) {
	for {
		var size uint32
		err := binary.Read(r, binary.BigEndian, &size)
		if err != nil {
			if err == io.EOF {
				return "", fmt.Errorf("reached EOF before audio data")
			}
			return "", err
		}

		name, err := readString(r, 4)
		if err != nil {
			return "", err
		}

		switch name {
		case "meta":
			// next_item_id (int32)
			_, err := r.Seek(4, os.SEEK_CUR)
			if err != nil {
				return "", err
			}
			fallthrough

		case "moov", "udta", "ilst":
			continue

		case "mdat": // stop when we get to the data
			h := sha1.New()
			_, err := io.CopyN(h, r, int64(size-8))
			if err != nil {
				return "", fmt.Errorf("error reading audio data: %v", err)
			}
			return hashSum(h), nil
		}

		_, err = r.Seek(int64(size-8), os.SEEK_CUR)
		if err != nil {
			return "", fmt.Errorf("error reading '%v' tag: %v", name, err)
		}
	}
}

func sizeToEndOffset(r io.ReadSeeker, offset int64) (int64, error) {
	n, err := r.Seek(-128, os.SEEK_END)
	if err != nil {
		return 0, fmt.Errorf("error seeking end offset (%d bytes): %v", offset, err)
	}

	_, err = r.Seek(-n, os.SEEK_CUR)
	if err != nil {
		return 0, fmt.Errorf("error seeking back to original position: %v", err)
	}
	return n, nil
}

// SumID3v1 constructs a checksum of MP3 audio file data (assumed to have ID3v1 tags) provided
// by the io.ReadSeeker which is metadata invariant.
func SumID3v1(r io.ReadSeeker) (string, error) {
	n, err := sizeToEndOffset(r, 128)
	if err != nil {
		return "", fmt.Errorf("error determining read size to ID3v1 header: %v", err)
	}

	// TODO: improve this check???
	if n <= 0 {
		return "", fmt.Errorf("file size must be greater than 128 bytes (ID3v1 header size) for MP3")
	}

	h := sha1.New()
	_, err = io.CopyN(h, r, n)
	if err != nil {
		return "", fmt.Errorf("error reading %v bytes: %v", n, err)
	}
	return hashSum(h), nil
}

// SumID3v2 constructs a checksum of MP3 audio file data (assumed to have ID3v2 tags) provided by the
// io.ReadSeeker which is metadata invariant.
func SumID3v2(r io.ReadSeeker) (string, error) {
	header, err := readID3v2Header(r)
	if err != nil {
		return "", fmt.Errorf("error reading ID3v2 header: %v", err)
	}

	_, err = r.Seek(int64(header.Size), os.SEEK_CUR)
	if err != nil {
		return "", fmt.Errorf("error seeking to end of ID3V2 header: %v", err)
	}

	n, err := sizeToEndOffset(r, 128)
	if err != nil {
		return "", fmt.Errorf("error determining read size to ID3v1 header: %v", err)
	}

	// TODO: remove this check?????
	if n < 0 {
		return "", fmt.Errorf("file size must be greater than 128 bytes for MP3: %v bytes", n)
	}

	h := sha1.New()
	_, err = io.CopyN(h, r, n)
	if err != nil {
		return "", fmt.Errorf("error reading %v bytes: %v", n, err)
	}
	return hashSum(h), nil
}

func hashSum(h hash.Hash) string {
	return fmt.Sprintf("%x", h.Sum([]byte{}))
}
<|endoftext|>


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

package common

import (
	"fmt"
	"log/syslog"
	"net"
	"os"
	"regexp"
	"strconv"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/Sirupsen/logrus/hooks/syslog"
	"github.com/bshuster-repo/logrus-logstash-hook"
	"github.com/evalphobia/logrus_fluent"
)

const (
	fAddr  = "fluentd.address"
	fTag   = "fluentd.tag"
	fLevel = "fluentd.level"

	SLevel = "syslog.level"

	lAddr     = "logstash.address"
	lLevel    = "logstash.level"
	lProtocol = "logstash.protocol"

	Syslog   = "syslog"
	Fluentd  = "fluentd"
	Logstash = "logstash"
)

// syslogOpts is the set of supported options for syslog configuration.
var syslogOpts = map[string]bool{
	"syslog.level": true,
}

// fluentDOpts is the set of supported options for fluentD configuration.
var fluentDOpts = map[string]bool{
	fAddr:  true,
	fTag:   true,
	fLevel: true,
}

// logstashOpts is the set of supported options for logstash configuration.
var logstashOpts = map[string]bool{
	lAddr:     true,
	lLevel:    true,
	lProtocol: true,
}

// syslogLevelMap maps logrus.Level values to syslog.Priority levels.
var syslogLevelMap = map[logrus.Level]syslog.Priority{
	logrus.PanicLevel: syslog.LOG_ALERT,
	logrus.FatalLevel: syslog.LOG_CRIT,
	logrus.ErrorLevel: syslog.LOG_ERR,
	logrus.WarnLevel:  syslog.LOG_WARNING,
	logrus.InfoLevel:  syslog.LOG_INFO,
	logrus.DebugLevel: syslog.LOG_DEBUG,
}

// setFireLevels returns a slice of logrus.Level values higher in priority
// and including level, excluding any levels lower in priority.
func setFireLevels(level logrus.Level) []logrus.Level {
	switch level {
	case logrus.PanicLevel:
		return []logrus.Level{logrus.PanicLevel}
	case logrus.FatalLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel}
	case logrus.ErrorLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}
	case logrus.WarnLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel}
	case logrus.InfoLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, logrus.InfoLevel}
	case logrus.DebugLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, logrus.InfoLevel, logrus.DebugLevel}
	default:
		logrus.Infof("logrus level %v is not supported at this time; defaulting to info level", level)
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, logrus.InfoLevel}
	}
}

// SetupLogging sets up each logging service provided in loggers and configures
// each logger with the provided logOpts.
func SetupLogging(loggers []string, logOpts map[string]string, tag string, debug bool) error {
	setupFormatter()

	// Set default logger to output to stdout if no loggers are provided.
	if len(loggers) == 0 {
		logrus.SetOutput(os.Stdout)
	}

	if debug {
		logrus.SetLevel(logrus.DebugLevel)
	} else {
		logrus.SetLevel(logrus.InfoLevel)
	}

	// Iterate through all provided loggers and configure them according
	// to user-provided settings.
	for _, logger := range loggers {
		valuesToValidate := getLogDriverConfig(logger, logOpts)
		switch logger {
		case Syslog:
			valuesToValidate := getLogDriverConfig(Syslog, logOpts)
			err := validateOpts(Syslog, valuesToValidate, syslogOpts)
			if err != nil {
				return err
			}
			setupSyslog(valuesToValidate, tag, debug)
		case Fluentd:
			err := validateOpts(logger, valuesToValidate, fluentDOpts)
			if err != nil {
				return err
			}
			setupFluentD(valuesToValidate, debug)
			//TODO - need to finish logstash integration.
		/*case Logstash:
		fmt.Printf("SetupLogging: in logstash case\n")
		err := validateOpts(logger, valuesToValidate, logstashOpts)
		fmt.Printf("SetupLogging: validating options for logstash complete\n")
		if err != nil {
			fmt.Printf("SetupLogging: error validating logstash opts %v\n", err.Error())
			return err
		}
		fmt.Printf("SetupLogging: about to setup logstash\n")
		setupLogstash(valuesToValidate)
		*/
		default:
			return fmt.Errorf("provided log driver %q is not a supported log driver", logger)
		}
	}
	return nil
}

// setupSyslog sets up and configures syslog with the provided options in
// logOpts. If some options are not provided, sensible defaults are used.
func setupSyslog(logOpts map[string]string, tag string, debug bool) {
	logLevel, ok := logOpts[SLevel]
	if !ok {
		if debug {
			logLevel = "debug"
		} else {
			logLevel = "info"
		}
	}

	//Validate provided log level.
	level, err := logrus.ParseLevel(logLevel)
	if err != nil {
		logrus.Fatal(err)
	}

	logrus.SetLevel(level)
	// Create syslog hook.
	h, err := logrus_syslog.NewSyslogHook("", "", syslogLevelMap[level], tag)
	if err != nil {
		logrus.Fatal(err)
	}
	logrus.AddHook(h)
}

// setupFormatter sets up the text formatting for logs output by logrus.
func setupFormatter() {
	fileFormat := new(logrus.TextFormatter)
	fileFormat.DisableColors = true
	switch os.Getenv("INITSYSTEM") {
	case "SYSTEMD":
		fileFormat.DisableTimestamp = true
		fileFormat.FullTimestamp = true
	default:
		fileFormat.TimestampFormat = time.RFC3339
	}
	logrus.SetFormatter(fileFormat)
}

// setupFluentD sets up and configures FluentD with the provided options in
// logOpts. If some options are not provided, sensible defaults are used.
func setupFluentD(logOpts map[string]string, debug bool) {
	//If no logging level set for fluentd, use debug value if it is set.
	// Logging level set for fluentd takes precedence over debug flag
	// fluent.level provided.
	logLevel, ok := logOpts[fLevel]
	if !ok {
		if debug {
			logLevel = "debug"
		} else {
			logLevel = "info"
		}
	}
	level, err := logrus.ParseLevel(logLevel)
	if err != nil {
		logrus.Fatal(err)
	}

	hostAndPort, ok := logOpts[fAddr]
	if !ok {
		hostAndPort = "localhost:24224"
	}

	host, strPort, err := net.SplitHostPort(hostAndPort)
	port, err := strconv.Atoi(strPort)
	if err != nil {
		logrus.Fatal(err)
	}

	h, err := logrus_fluent.New(host, port)
	if err != nil {
		logrus.Fatal(err)
	}

	tag, ok := logOpts[fTag]
	if ok {
		h.SetTag(tag)
	}

	// set custom fire level
	h.SetLevels(setFireLevels(level))
	logrus.AddHook(h)
}

// setupLogstash sets up and configures Logstash with the provided options in
// logOpts. If some options are not provided, sensible defaults are used.
/// TODO fix me later - needs to be tested with a working logstash setup.
func setupLogstash(logOpts map[string]string) {
	hostAndPort, ok := logOpts[lAddr]
	if !ok {
		hostAndPort = "172.17.0.2:999"
	}

	protocol, ok := logOpts[lProtocol]
	if !ok {
		protocol = "tcp"
	}

	h, err := logrustash.NewHook(protocol, hostAndPort, "cilium")
	if err != nil {
		logrus.Fatal(err)
	}

	logrus.AddHook(h)
}

// validateOpts iterates through all of the keys in logOpts, and errors out if
// the key in logOpts is not a key in supportedOpts.
func validateOpts(logDriver string, logOpts map[string]string, supportedOpts map[string]bool) error {
	for k := range logOpts {
		if !supportedOpts[k] {
			return fmt.Errorf("provided configuration value %q is not supported as a logging option for log driver %s", k, logDriver)
		}
	}
	return nil
}

// getLogDriverConfig returns a map containing the key-value pairs that start
// with string logDriver from map logOpts.
func getLogDriverConfig(logDriver string, logOpts map[string]string) map[string]string {
	keysToValidate := make(map[string]string)
	for k, v := range logOpts {
		ok, err := regexp.MatchString(logDriver+".*", k)
		if err != nil {
			logrus.Fatal(err)
		}
		if ok {
			keysToValidate[k] = v
		}
	}
	return keysToValidate
}
<commit_msg>logging: Disable formatter to have simple format in TTY<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common

import (
	"fmt"
	"log/syslog"
	"net"
	"os"
	"regexp"
	"strconv"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/Sirupsen/logrus/hooks/syslog"
	"github.com/bshuster-repo/logrus-logstash-hook"
	"github.com/evalphobia/logrus_fluent"
)

const (
	fAddr  = "fluentd.address"
	fTag   = "fluentd.tag"
	fLevel = "fluentd.level"

	SLevel = "syslog.level"

	lAddr     = "logstash.address"
	lLevel    = "logstash.level"
	lProtocol = "logstash.protocol"

	Syslog   = "syslog"
	Fluentd  = "fluentd"
	Logstash = "logstash"
)

// syslogOpts is the set of supported options for syslog configuration.
var syslogOpts = map[string]bool{
	"syslog.level": true,
}

// fluentDOpts is the set of supported options for fluentD configuration.
var fluentDOpts = map[string]bool{
	fAddr:  true,
	fTag:   true,
	fLevel: true,
}

// logstashOpts is the set of supported options for logstash configuration.
var logstashOpts = map[string]bool{
	lAddr:     true,
	lLevel:    true,
	lProtocol: true,
}

// syslogLevelMap maps logrus.Level values to syslog.Priority levels.
var syslogLevelMap = map[logrus.Level]syslog.Priority{
	logrus.PanicLevel: syslog.LOG_ALERT,
	logrus.FatalLevel: syslog.LOG_CRIT,
	logrus.ErrorLevel: syslog.LOG_ERR,
	logrus.WarnLevel:  syslog.LOG_WARNING,
	logrus.InfoLevel:  syslog.LOG_INFO,
	logrus.DebugLevel: syslog.LOG_DEBUG,
}

// setFireLevels returns a slice of logrus.Level values higher in priority
// and including level, excluding any levels lower in priority.
func setFireLevels(level logrus.Level) []logrus.Level {
	switch level {
	case logrus.PanicLevel:
		return []logrus.Level{logrus.PanicLevel}
	case logrus.FatalLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel}
	case logrus.ErrorLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}
	case logrus.WarnLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel}
	case logrus.InfoLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, logrus.InfoLevel}
	case logrus.DebugLevel:
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, logrus.InfoLevel, logrus.DebugLevel}
	default:
		logrus.Infof("logrus level %v is not supported at this time; defaulting to info level", level)
		return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel, logrus.InfoLevel}
	}
}

// SetupLogging sets up each logging service provided in loggers and configures
// each logger with the provided logOpts.
func SetupLogging(loggers []string, logOpts map[string]string, tag string, debug bool) error {
	// FIXME: Disabled for now
	//setupFormatter()

	// Set default logger to output to stdout if no loggers are provided.
	if len(loggers) == 0 {
		logrus.SetOutput(os.Stdout)
	}

	if debug {
		logrus.SetLevel(logrus.DebugLevel)
	} else {
		logrus.SetLevel(logrus.InfoLevel)
	}

	// Iterate through all provided loggers and configure them according
	// to user-provided settings.
	for _, logger := range loggers {
		valuesToValidate := getLogDriverConfig(logger, logOpts)
		switch logger {
		case Syslog:
			valuesToValidate := getLogDriverConfig(Syslog, logOpts)
			err := validateOpts(Syslog, valuesToValidate, syslogOpts)
			if err != nil {
				return err
			}
			setupSyslog(valuesToValidate, tag, debug)
		case Fluentd:
			err := validateOpts(logger, valuesToValidate, fluentDOpts)
			if err != nil {
				return err
			}
			setupFluentD(valuesToValidate, debug)
			//TODO - need to finish logstash integration.
		/*case Logstash:
		fmt.Printf("SetupLogging: in logstash case\n")
		err := validateOpts(logger, valuesToValidate, logstashOpts)
		fmt.Printf("SetupLogging: validating options for logstash complete\n")
		if err != nil {
			fmt.Printf("SetupLogging: error validating logstash opts %v\n", err.Error())
			return err
		}
		fmt.Printf("SetupLogging: about to setup logstash\n")
		setupLogstash(valuesToValidate)
		*/
		default:
			return fmt.Errorf("provided log driver %q is not a supported log driver", logger)
		}
	}
	return nil
}

// setupSyslog sets up and configures syslog with the provided options in
// logOpts. If some options are not provided, sensible defaults are used.
func setupSyslog(logOpts map[string]string, tag string, debug bool) {
	logLevel, ok := logOpts[SLevel]
	if !ok {
		if debug {
			logLevel = "debug"
		} else {
			logLevel = "info"
		}
	}

	//Validate provided log level.
	level, err := logrus.ParseLevel(logLevel)
	if err != nil {
		logrus.Fatal(err)
	}

	logrus.SetLevel(level)
	// Create syslog hook.
	h, err := logrus_syslog.NewSyslogHook("", "", syslogLevelMap[level], tag)
	if err != nil {
		logrus.Fatal(err)
	}
	logrus.AddHook(h)
}

// setupFormatter sets up the text formatting for logs output by logrus.
func setupFormatter() {
	fileFormat := new(logrus.TextFormatter)
	fileFormat.DisableColors = true
	switch os.Getenv("INITSYSTEM") {
	case "SYSTEMD":
		fileFormat.DisableTimestamp = true
		fileFormat.FullTimestamp = true
	default:
		fileFormat.TimestampFormat = time.RFC3339
	}
	logrus.SetFormatter(fileFormat)
}

// setupFluentD sets up and configures FluentD with the provided options in
// logOpts. If some options are not provided, sensible defaults are used.
func setupFluentD(logOpts map[string]string, debug bool) {
	//If no logging level set for fluentd, use debug value if it is set.
	// Logging level set for fluentd takes precedence over debug flag
	// fluent.level provided.
	logLevel, ok := logOpts[fLevel]
	if !ok {
		if debug {
			logLevel = "debug"
		} else {
			logLevel = "info"
		}
	}
	level, err := logrus.ParseLevel(logLevel)
	if err != nil {
		logrus.Fatal(err)
	}

	hostAndPort, ok := logOpts[fAddr]
	if !ok {
		hostAndPort = "localhost:24224"
	}

	host, strPort, err := net.SplitHostPort(hostAndPort)
	port, err := strconv.Atoi(strPort)
	if err != nil {
		logrus.Fatal(err)
	}

	h, err := logrus_fluent.New(host, port)
	if err != nil {
		logrus.Fatal(err)
	}

	tag, ok := logOpts[fTag]
	if ok {
		h.SetTag(tag)
	}

	// set custom fire level
	h.SetLevels(setFireLevels(level))
	logrus.AddHook(h)
}

// setupLogstash sets up and configures Logstash with the provided options in
// logOpts. If some options are not provided, sensible defaults are used.
/// TODO fix me later - needs to be tested with a working logstash setup.
func setupLogstash(logOpts map[string]string) {
	hostAndPort, ok := logOpts[lAddr]
	if !ok {
		hostAndPort = "172.17.0.2:999"
	}

	protocol, ok := logOpts[lProtocol]
	if !ok {
		protocol = "tcp"
	}

	h, err := logrustash.NewHook(protocol, hostAndPort, "cilium")
	if err != nil {
		logrus.Fatal(err)
	}

	logrus.AddHook(h)
}

// validateOpts iterates through all of the keys in logOpts, and errors out if
// the key in logOpts is not a key in supportedOpts.
func validateOpts(logDriver string, logOpts map[string]string, supportedOpts map[string]bool) error {
	for k := range logOpts {
		if !supportedOpts[k] {
			return fmt.Errorf("provided configuration value %q is not supported as a logging option for log driver %s", k, logDriver)
		}
	}
	return nil
}

// getLogDriverConfig returns a map containing the key-value pairs that start
// with string logDriver from map logOpts.
func getLogDriverConfig(logDriver string, logOpts map[string]string) map[string]string {
	keysToValidate := make(map[string]string)
	for k, v := range logOpts {
		ok, err := regexp.MatchString(logDriver+".*", k)
		if err != nil {
			logrus.Fatal(err)
		}
		if ok {
			keysToValidate[k] = v
		}
	}
	return keysToValidate
}
<|endoftext|>


<commit_before>// Package app provides application support for context and MongoDB access.
// Current Status Codes:
//		200 OK           : StatusOK                  : Call is success and returning data.
//		204 No Content   : StatusNoContent           : Call is success and returns no data.
//		400 Bad Request  : StatusBadRequest          : Invalid post data (syntax or semantics).
//		401 Unauthorized : StatusUnauthorized        : Authentication failure.
//		404 Not Found    : StatusNotFound            : Invalid URL or identifier.
//		500 Internal     : StatusInternalServerError : Application specific beyond scope of user.
package app

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

	"github.com/ardanlabs/kit/auth"
	"github.com/ardanlabs/kit/db"
	"github.com/ardanlabs/kit/log"
)

// Invalid describes a validation error belonging to a specific field.
type Invalid struct {
	Fld string `json:"field_name"`
	Err string `json:"error"`
}

// jsonError is the response for errors that occur within the API.
type jsonError struct {
	Error  string    `json:"error"`
	Fields []Invalid `json:"fields,omitempty"`
}

//==============================================================================

// Context contains data associated with a single request.
type Context struct {
	DB *db.DB
	http.ResponseWriter
	Request   *http.Request
	Params    map[string]string
	SessionID string
	User      *auth.User
	Status    int
}

// Error handles all error responses for the API.
func (c *Context) Error(err error) {
	switch err {
	case ErrNotFound:
		c.RespondError(err.Error(), http.StatusNotFound)
	case ErrInvalidID:
		c.RespondError(err.Error(), http.StatusBadRequest)
	case ErrValidation:
		c.RespondError(err.Error(), http.StatusBadRequest)
	default:
		c.RespondError(err.Error(), http.StatusInternalServerError)
	}
}

// Respond sends JSON to the client.
// If code is StatusNoContent, v is expected to be nil.
func (c *Context) Respond(data interface{}, code int) {
	log.User(c.SessionID, "api : Respond", "Started : Code[%d]", code)

	c.Status = code

	if code == http.StatusNoContent {
		c.WriteHeader(http.StatusNoContent)
		return
	}

	// Set application default header values.
	c.Header().Set("Content-Type", "application/json")

	// Load any user defined header values.
	if app.userHeaders != nil {
		for key, value := range app.userHeaders {
			log.User("startup", "Init", "Setting user headers : %s:%s", key, value)
			c.Header().Set(key, value)
		}
	}

	c.WriteHeader(code)

	// Marshal the data into a JSON string.
	jsonData, err := json.Marshal(data)
	if err != nil {
		jsonData = []byte("{}")
	}

	// Look for a JSONP marker
	if cb := c.Request.URL.Query().Get("callback"); cb != "" {

		// We need to wrap the result in a function call.
		// callback_value({"data_1": "hello world", "data_2": ["the","sun","is","shining"]});
		fmt.Fprintf(c, "%s(%s)", cb, string(jsonData))

		log.User(c.SessionID, "api : Respond", "Completed")
		return
	}

	// We can send the result straight through.
	fmt.Fprintf(c, string(jsonData))

	log.User(c.SessionID, "api : Respond", "Completed")
}

// RespondInvalid sends JSON describing field validation errors.
func (c *Context) RespondInvalid(fields []Invalid) {
	v := jsonError{
		Error:  "field validation failure",
		Fields: fields,
	}
	c.Respond(v, http.StatusBadRequest)
}

// RespondError sends JSON describing the error
func (c *Context) RespondError(error string, code int) {
	c.Respond(jsonError{Error: error}, code)
}
<commit_msg>Replace the use for fmt for io<commit_after>// Package app provides application support for context and MongoDB access.
// Current Status Codes:
//		200 OK           : StatusOK                  : Call is success and returning data.
//		204 No Content   : StatusNoContent           : Call is success and returns no data.
//		400 Bad Request  : StatusBadRequest          : Invalid post data (syntax or semantics).
//		401 Unauthorized : StatusUnauthorized        : Authentication failure.
//		404 Not Found    : StatusNotFound            : Invalid URL or identifier.
//		500 Internal     : StatusInternalServerError : Application specific beyond scope of user.
package app

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

	"github.com/ardanlabs/kit/auth"
	"github.com/ardanlabs/kit/db"
	"github.com/ardanlabs/kit/log"
)

// Invalid describes a validation error belonging to a specific field.
type Invalid struct {
	Fld string `json:"field_name"`
	Err string `json:"error"`
}

// jsonError is the response for errors that occur within the API.
type jsonError struct {
	Error  string    `json:"error"`
	Fields []Invalid `json:"fields,omitempty"`
}

//==============================================================================

// Context contains data associated with a single request.
type Context struct {
	DB *db.DB
	http.ResponseWriter
	Request   *http.Request
	Params    map[string]string
	SessionID string
	User      *auth.User
	Status    int
}

// Error handles all error responses for the API.
func (c *Context) Error(err error) {
	switch err {
	case ErrNotFound:
		c.RespondError(err.Error(), http.StatusNotFound)
	case ErrInvalidID:
		c.RespondError(err.Error(), http.StatusBadRequest)
	case ErrValidation:
		c.RespondError(err.Error(), http.StatusBadRequest)
	default:
		c.RespondError(err.Error(), http.StatusInternalServerError)
	}
}

// Respond sends JSON to the client.
// If code is StatusNoContent, v is expected to be nil.
func (c *Context) Respond(data interface{}, code int) {
	log.User(c.SessionID, "api : Respond", "Started : Code[%d]", code)

	c.Status = code

	if code == http.StatusNoContent {
		c.WriteHeader(http.StatusNoContent)
		return
	}

	// Set application default header values.
	c.Header().Set("Content-Type", "application/json")

	// Load any user defined header values.
	if app.userHeaders != nil {
		for key, value := range app.userHeaders {
			log.User("startup", "Init", "Setting user headers : %s:%s", key, value)
			c.Header().Set(key, value)
		}
	}

	c.WriteHeader(code)

	// Marshal the data into a JSON string.
	jsonData, err := json.Marshal(data)
	if err != nil {
		jsonData = []byte("{}")
	}

	// Look for a JSONP marker
	if cb := c.Request.URL.Query().Get("callback"); cb != "" {

		// We need to wrap the result in a function call.
		// callback_value({"data_1": "hello world", "data_2": ["the","sun","is","shining"]});
		io.WriteString(c, cb+"("+string(jsonData)+")")

		log.User(c.SessionID, "api : Respond", "Completed")
		return
	}

	// We can send the result straight through.
	io.WriteString(c, string(jsonData))

	log.User(c.SessionID, "api : Respond", "Completed")
}

// RespondInvalid sends JSON describing field validation errors.
func (c *Context) RespondInvalid(fields []Invalid) {
	v := jsonError{
		Error:  "field validation failure",
		Fields: fields,
	}
	c.Respond(v, http.StatusBadRequest)
}

// RespondError sends JSON describing the error
func (c *Context) RespondError(error string, code int) {
	c.Respond(jsonError{Error: error}, code)
}
<|endoftext|>


<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
	"bufio"
	"errors"
	"io"
	"io/ioutil"
	"os"

	"github.com/bogem/id3v2/bwpool"
	"github.com/bogem/id3v2/util"
)

// Tag stores all frames of opened file.
type Tag struct {
	frames    map[string]Framer
	sequences map[string]sequencer
	commonIDs map[string]string

	file         *os.File
	originalSize int64
	version      byte
}

// AddFrame adds f to tag with appropriate id. If id is "" or f is nil,
// AddFrame will not add f to tag.
//
// If you want to add attached picture, comment or unsynchronised lyrics/text
// transcription frames, better use AddAttachedPicture, AddCommentFrame
// or AddUnsynchronisedLyricsFrame methods respectively.
func (t *Tag) AddFrame(id string, f Framer) {
	if id == "" || f == nil {
		return
	}

	if id == t.CommonID("Attached picture") || id == t.CommonID("Comments") ||
		id == t.CommonID("Unsynchronised lyrics/text transcription") {
		t.checkSequence(id)
		t.addFrameToSequence(id, f)
	} else {
		t.frames[id] = f
	}
}

func (t *Tag) checkSequence(id string) {
	if t.sequences[id] == nil {
		switch id {
		case t.CommonID("Attached picture"):
			t.sequences[id] = newPictureSequence()
		case t.CommonID("Comments"):
			t.sequences[id] = newCommentSequence()
		case t.CommonID("Unsynchronised lyrics/text transcription"):
			t.sequences[id] = newUSLFSequence()
		}
	}
}

func (t *Tag) addFrameToSequence(id string, f Framer) {
	t.sequences[id].AddFrame(f)
}

func (t *Tag) AddAttachedPicture(pf PictureFrame) {
	id := t.CommonID("Attached picture")
	t.AddFrame(id, pf)
}

func (t *Tag) AddCommentFrame(cf CommentFrame) {
	id := t.CommonID("Comments")
	t.AddFrame(id, cf)
}

func (t *Tag) AddUnsynchronisedLyricsFrame(uslf UnsynchronisedLyricsFrame) {
	id := t.CommonID("Unsynchronised lyrics/text transcription")
	t.AddFrame(id, uslf)
}

// CommonID returns ID3v2.3 or ID3v2.4 (in appropriate to version of Tag) frame ID
// from given description.
// For example, CommonID("Language") will return "TLAN".
func (t Tag) CommonID(description string) string {
	return t.commonIDs[description]
}

// AllFrames returns map, that contains all frames in tag, that could be parsed.
// The key of this map is an ID of frame and value is an array of frames.
func (t *Tag) AllFrames() map[string][]Framer {
	frames := make(map[string][]Framer)

	for id := range t.frames {
		frames[id] = t.GetFrames(id)
	}

	for id := range t.sequences {
		frames[id] = t.GetFrames(id)
	}

	return frames
}

// DeleteAllFrames deletes all frames in tag.
func (t *Tag) DeleteAllFrames() {
	t.frames = make(map[string]Framer)
	t.sequences = make(map[string]sequencer)
}

// DeleteFrames deletes frames in tag with given id.
func (t *Tag) DeleteFrames(id string) {
	delete(t.frames, id)
	delete(t.sequences, id)
}

// GetLastFrame returns last frame from slice, that is returned from GetFrames function.
// GetLastFrame is suitable for frames, that can be only one in whole tag.
// For example, for text frames.
//
// Example of usage:
//	bpmFramer := tag.GetLastFrame(tag.CommonID("BPM"))
//	if bpmFramer != nil {
//		bpm, ok := bpmFramer.(id3v2.TextFrame)
//		if !ok {
//			log.Fatal("Couldn't assert bpm frame")
//		}
//		fmt.Println(bpm.Text)
//	}
func (t *Tag) GetLastFrame(id string) Framer {
	fs := t.GetFrames(id)
	if len(fs) == 0 || fs == nil {
		return nil
	}
	return fs[len(fs)-1]
}

// GetFrames returns frames with corresponding id.
// It returns nil if there is no frames with given id.
//
// Example of usage:
//	pictures := tag.GetFrames(tag.CommonID("Attached picture"))
//	if pictures != nil {
//		for _, f := range pictures {
//			pic, ok := f.(id3v2.PictureFrame)
//			if !ok {
//				log.Fatal("Couldn't assert picture frame")
//			}
//
//			// Do some operations with picture frame:
//			fmt.Println(pic.Description) // For example, print description of picture frame
//			image, err := ioutil.ReadAll(pic.Picture) // Or read a picture from picture frame
//			if err != nil {
//				log.Fatal("Error while reading a picture from picture frame: ", err)
//			}
//		}
//	}
func (t *Tag) GetFrames(id string) []Framer {
	if f, exists := t.frames[id]; exists {
		return []Framer{f}
	} else if s, exists := t.sequences[id]; exists {
		return s.Frames()
	}
	return nil
}

// GetTextFrame returns text frame with corresponding id.
func (t Tag) GetTextFrame(id string) TextFrame {
	f := t.GetLastFrame(id)
	if f == nil {
		return TextFrame{}
	}
	tf := f.(TextFrame)
	return tf
}

// Count returns the number of frames in tag.
func (t Tag) Count() int {
	n := len(t.frames)
	for _, s := range t.sequences {
		n += s.Count()
	}
	return n
}

// HasAnyFrames checks if there is at least one frame in tag.
// It's much faster than tag.Count() > 0.
func (t Tag) HasAnyFrames() bool {
	return len(t.frames) > 0 || len(t.sequences) > 0
}

func (t Tag) Title() string {
	f := t.GetTextFrame(t.CommonID("Title/Songname/Content description"))
	return f.Text
}

func (t *Tag) SetTitle(title string) {
	t.AddFrame(t.CommonID("Title/Songname/Content description"), TextFrame{Encoding: ENUTF8, Text: title})
}

func (t Tag) Artist() string {
	f := t.GetTextFrame(t.CommonID("Lead artist/Lead performer/Soloist/Performing group"))
	return f.Text
}

func (t *Tag) SetArtist(artist string) {
	t.AddFrame(t.CommonID("Lead artist/Lead performer/Soloist/Performing group"), TextFrame{Encoding: ENUTF8, Text: artist})
}

func (t Tag) Album() string {
	f := t.GetTextFrame(t.CommonID("Album/Movie/Show title"))
	return f.Text
}

func (t *Tag) SetAlbum(album string) {
	t.AddFrame(t.CommonID("Album/Movie/Show title"), TextFrame{Encoding: ENUTF8, Text: album})
}

func (t Tag) Year() string {
	f := t.GetTextFrame(t.CommonID("Year"))
	return f.Text
}

func (t *Tag) SetYear(year string) {
	t.AddFrame(t.CommonID("Year"), TextFrame{Encoding: ENUTF8, Text: year})
}

func (t Tag) Genre() string {
	f := t.GetTextFrame(t.CommonID("Content type"))
	return f.Text
}

func (t *Tag) SetGenre(genre string) {
	t.AddFrame(t.CommonID("Content type"), TextFrame{Encoding: ENUTF8, Text: genre})
}

// Save writes tag to the file. If there are no frames in tag, Save will write
// only music part without any ID3v2 information.
func (t *Tag) Save() error {
	// Create a temp file for mp3 file, which will contain new tag
	newFile, err := ioutil.TempFile("", "")
	if err != nil {
		return err
	}

	// If there is at least one frame, write it
	if t.HasAnyFrames() {
		// Form size of new frames
		framesSize, err := util.FormSize(t.allFramesSize())
		if err != nil {
			return err
		}

		// Write to new file new tag header
		if _, err = newFile.Write(formTagHeader(framesSize, t.version)); err != nil {
			return err
		}

		// Write to new file new frames
		if err = t.writeAllFrames(newFile); err != nil {
			return err
		}
	}

	// Seek to a music part of original file
	originalFile := t.file
	if _, err = originalFile.Seek(t.originalSize, os.SEEK_SET); err != nil {
		return err
	}

	// Write to new file the music part
	if _, err = io.Copy(newFile, originalFile); err != nil {
		return err
	}

	// Get original file mode
	originalFileStat, err := originalFile.Stat()
	if err != nil {
		return err
	}

	// Set original file mode to new file
	if err = os.Chmod(newFile.Name(), originalFileStat.Mode()); err != nil {
		return err
	}

	// Close files to allow replacing
	newFile.Close()
	originalFile.Close()

	// Replace original file with new file
	if err = os.Rename(newFile.Name(), originalFile.Name()); err != nil {
		return err
	}

	// Make sure we clean up the temp file if it's still around
	os.Remove(newFile.Name())

	// Set t.file to new file with original name
	t.file, err = os.Open(originalFile.Name())
	if err != nil {
		return err
	}

	return nil
}

// Close closes the tag's file, rendering it unusable for I/O.
// It returns an error, if any.
func (t *Tag) Close() error {
	return t.file.Close()
}

var errBlankID = errors.New("blank ID")

func (t Tag) allFramesSize() int {
	var n int

	n += t.Count() * frameHeaderSize

	for _, frames := range t.AllFrames() {
		for _, f := range frames {
			n += f.Size()
		}
	}

	return n
}

func (t Tag) writeAllFrames(w io.Writer) error {
	bw := bwpool.Get(w)
	defer bwpool.Put(bw)

	for id, frames := range t.AllFrames() {
		for _, f := range frames {
			err := writeFrame(bw, id, f)
			if err == errBlankID {
				continue
			}
			if err != nil {
				return err
			}
		}
	}

	return bw.Flush()
}

func writeFrame(bw *bufio.Writer, id string, frame Framer) error {
	if id == "" {
		return errBlankID
	}

	if err := writeFrameHeader(bw, id, frame.Size()); err != nil {
		return err
	}
	if _, err := frame.WriteTo(bw); err != nil {
		return err
	}

	return nil
}

func writeFrameHeader(bw *bufio.Writer, id string, frameSize int) error {
	size, err := util.FormSize(frameSize)
	if err != nil {
		return err
	}

	bw.WriteString(id)
	bw.Write(size)
	bw.Write([]byte{0, 0})
	return nil
}
<commit_msg>Delete not relevant comments<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
	"bufio"
	"errors"
	"io"
	"io/ioutil"
	"os"

	"github.com/bogem/id3v2/bwpool"
	"github.com/bogem/id3v2/util"
)

// Tag stores all frames of opened file.
type Tag struct {
	frames    map[string]Framer
	sequences map[string]sequencer
	commonIDs map[string]string

	file         *os.File
	originalSize int64
	version      byte
}

// AddFrame adds f to tag with appropriate id. If id is "" or f is nil,
// AddFrame will not add f to tag.
//
// If you want to add attached picture, comment or unsynchronised lyrics/text
// transcription frames, better use AddAttachedPicture, AddCommentFrame
// or AddUnsynchronisedLyricsFrame methods respectively.
func (t *Tag) AddFrame(id string, f Framer) {
	if id == "" || f == nil {
		return
	}

	if id == t.CommonID("Attached picture") || id == t.CommonID("Comments") ||
		id == t.CommonID("Unsynchronised lyrics/text transcription") {
		t.checkSequence(id)
		t.addFrameToSequence(id, f)
	} else {
		t.frames[id] = f
	}
}

func (t *Tag) checkSequence(id string) {
	if t.sequences[id] == nil {
		switch id {
		case t.CommonID("Attached picture"):
			t.sequences[id] = newPictureSequence()
		case t.CommonID("Comments"):
			t.sequences[id] = newCommentSequence()
		case t.CommonID("Unsynchronised lyrics/text transcription"):
			t.sequences[id] = newUSLFSequence()
		}
	}
}

func (t *Tag) addFrameToSequence(id string, f Framer) {
	t.sequences[id].AddFrame(f)
}

func (t *Tag) AddAttachedPicture(pf PictureFrame) {
	id := t.CommonID("Attached picture")
	t.AddFrame(id, pf)
}

func (t *Tag) AddCommentFrame(cf CommentFrame) {
	id := t.CommonID("Comments")
	t.AddFrame(id, cf)
}

func (t *Tag) AddUnsynchronisedLyricsFrame(uslf UnsynchronisedLyricsFrame) {
	id := t.CommonID("Unsynchronised lyrics/text transcription")
	t.AddFrame(id, uslf)
}

// CommonID returns ID3v2.3 or ID3v2.4 (in appropriate to version of Tag) frame ID
// from given description.
// For example, CommonID("Language") will return "TLAN".
func (t Tag) CommonID(description string) string {
	return t.commonIDs[description]
}

// AllFrames returns map, that contains all frames in tag, that could be parsed.
// The key of this map is an ID of frame and value is an array of frames.
func (t *Tag) AllFrames() map[string][]Framer {
	frames := make(map[string][]Framer)

	for id := range t.frames {
		frames[id] = t.GetFrames(id)
	}

	for id := range t.sequences {
		frames[id] = t.GetFrames(id)
	}

	return frames
}

// DeleteAllFrames deletes all frames in tag.
func (t *Tag) DeleteAllFrames() {
	t.frames = make(map[string]Framer)
	t.sequences = make(map[string]sequencer)
}

// DeleteFrames deletes frames in tag with given id.
func (t *Tag) DeleteFrames(id string) {
	delete(t.frames, id)
	delete(t.sequences, id)
}

// GetLastFrame returns last frame from slice, that is returned from GetFrames function.
// GetLastFrame is suitable for frames, that can be only one in whole tag.
// For example, for text frames.
//
// Example of usage:
//	bpmFramer := tag.GetLastFrame(tag.CommonID("BPM"))
//	if bpmFramer != nil {
//		bpm, ok := bpmFramer.(id3v2.TextFrame)
//		if !ok {
//			log.Fatal("Couldn't assert bpm frame")
//		}
//		fmt.Println(bpm.Text)
//	}
func (t *Tag) GetLastFrame(id string) Framer {
	fs := t.GetFrames(id)
	if len(fs) == 0 || fs == nil {
		return nil
	}
	return fs[len(fs)-1]
}

// GetFrames returns frames with corresponding id.
// It returns nil if there is no frames with given id.
//
// Example of usage:
//	pictures := tag.GetFrames(tag.CommonID("Attached picture"))
//	if pictures != nil {
//		for _, f := range pictures {
//			pic, ok := f.(id3v2.PictureFrame)
//			if !ok {
//				log.Fatal("Couldn't assert picture frame")
//			}
//
//			// Do some operations with picture frame:
//			fmt.Println(pic.Description) // For example, print description of picture frame
//		}
//	}
func (t *Tag) GetFrames(id string) []Framer {
	if f, exists := t.frames[id]; exists {
		return []Framer{f}
	} else if s, exists := t.sequences[id]; exists {
		return s.Frames()
	}
	return nil
}

// GetTextFrame returns text frame with corresponding id.
func (t Tag) GetTextFrame(id string) TextFrame {
	f := t.GetLastFrame(id)
	if f == nil {
		return TextFrame{}
	}
	tf := f.(TextFrame)
	return tf
}

// Count returns the number of frames in tag.
func (t Tag) Count() int {
	n := len(t.frames)
	for _, s := range t.sequences {
		n += s.Count()
	}
	return n
}

// HasAnyFrames checks if there is at least one frame in tag.
// It's much faster than tag.Count() > 0.
func (t Tag) HasAnyFrames() bool {
	return len(t.frames) > 0 || len(t.sequences) > 0
}

func (t Tag) Title() string {
	f := t.GetTextFrame(t.CommonID("Title/Songname/Content description"))
	return f.Text
}

func (t *Tag) SetTitle(title string) {
	t.AddFrame(t.CommonID("Title/Songname/Content description"), TextFrame{Encoding: ENUTF8, Text: title})
}

func (t Tag) Artist() string {
	f := t.GetTextFrame(t.CommonID("Lead artist/Lead performer/Soloist/Performing group"))
	return f.Text
}

func (t *Tag) SetArtist(artist string) {
	t.AddFrame(t.CommonID("Lead artist/Lead performer/Soloist/Performing group"), TextFrame{Encoding: ENUTF8, Text: artist})
}

func (t Tag) Album() string {
	f := t.GetTextFrame(t.CommonID("Album/Movie/Show title"))
	return f.Text
}

func (t *Tag) SetAlbum(album string) {
	t.AddFrame(t.CommonID("Album/Movie/Show title"), TextFrame{Encoding: ENUTF8, Text: album})
}

func (t Tag) Year() string {
	f := t.GetTextFrame(t.CommonID("Year"))
	return f.Text
}

func (t *Tag) SetYear(year string) {
	t.AddFrame(t.CommonID("Year"), TextFrame{Encoding: ENUTF8, Text: year})
}

func (t Tag) Genre() string {
	f := t.GetTextFrame(t.CommonID("Content type"))
	return f.Text
}

func (t *Tag) SetGenre(genre string) {
	t.AddFrame(t.CommonID("Content type"), TextFrame{Encoding: ENUTF8, Text: genre})
}

// Save writes tag to the file. If there are no frames in tag, Save will write
// only music part without any ID3v2 information.
func (t *Tag) Save() error {
	// Create a temp file for mp3 file, which will contain new tag
	newFile, err := ioutil.TempFile("", "")
	if err != nil {
		return err
	}

	// If there is at least one frame, write it
	if t.HasAnyFrames() {
		// Form size of new frames
		framesSize, err := util.FormSize(t.allFramesSize())
		if err != nil {
			return err
		}

		// Write to new file new tag header
		if _, err = newFile.Write(formTagHeader(framesSize, t.version)); err != nil {
			return err
		}

		// Write to new file new frames
		if err = t.writeAllFrames(newFile); err != nil {
			return err
		}
	}

	// Seek to a music part of original file
	originalFile := t.file
	if _, err = originalFile.Seek(t.originalSize, os.SEEK_SET); err != nil {
		return err
	}

	// Write to new file the music part
	if _, err = io.Copy(newFile, originalFile); err != nil {
		return err
	}

	// Get original file mode
	originalFileStat, err := originalFile.Stat()
	if err != nil {
		return err
	}

	// Set original file mode to new file
	if err = os.Chmod(newFile.Name(), originalFileStat.Mode()); err != nil {
		return err
	}

	// Close files to allow replacing
	newFile.Close()
	originalFile.Close()

	// Replace original file with new file
	if err = os.Rename(newFile.Name(), originalFile.Name()); err != nil {
		return err
	}

	// Make sure we clean up the temp file if it's still around
	os.Remove(newFile.Name())

	// Set t.file to new file with original name
	t.file, err = os.Open(originalFile.Name())
	if err != nil {
		return err
	}

	return nil
}

// Close closes the tag's file, rendering it unusable for I/O.
// It returns an error, if any.
func (t *Tag) Close() error {
	return t.file.Close()
}

var errBlankID = errors.New("blank ID")

func (t Tag) allFramesSize() int {
	var n int

	n += t.Count() * frameHeaderSize

	for _, frames := range t.AllFrames() {
		for _, f := range frames {
			n += f.Size()
		}
	}

	return n
}

func (t Tag) writeAllFrames(w io.Writer) error {
	bw := bwpool.Get(w)
	defer bwpool.Put(bw)

	for id, frames := range t.AllFrames() {
		for _, f := range frames {
			err := writeFrame(bw, id, f)
			if err == errBlankID {
				continue
			}
			if err != nil {
				return err
			}
		}
	}

	return bw.Flush()
}

func writeFrame(bw *bufio.Writer, id string, frame Framer) error {
	if id == "" {
		return errBlankID
	}

	if err := writeFrameHeader(bw, id, frame.Size()); err != nil {
		return err
	}
	if _, err := frame.WriteTo(bw); err != nil {
		return err
	}

	return nil
}

func writeFrameHeader(bw *bufio.Writer, id string, frameSize int) error {
	size, err := util.FormSize(frameSize)
	if err != nil {
		return err
	}

	bw.WriteString(id)
	bw.Write(size)
	bw.Write([]byte{0, 0})
	return nil
}
<|endoftext|>


<commit_before>package dgkala

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

	"github.com/buger/jsonparser"
)

const (
	incredibleOffersAPIAddress = "https://service2.digikala.com/api/IncredibleOffer/GetIncredibleOffer"
	searchAPIAddress           = "https://search.digikala.com/api/search?keyword=%s"
	staticFilesPath            = "https://file.digikala.com/digikala/%s"
	productByIDAPIAddress      = "https://service2.digikala.com/api/ProductCache/GetProductById/%d"
)

type requestHeader map[string]string

// ProductExistsStatus is a iota type for product existing status for buying
type ProductExistsStatus int

const (
	_ ProductExistsStatus = iota
	_
	// Available means the product is available to buy
	Available
	// OutOfStock means the product is not available  now and is out of stock
	OutOfStock
	// Discontinued means the product is discontinued
	Discontinued
)

// ImagePaths contains a product images in various sizes
type ImagePaths struct {
	Original, Size70, Size110, Size180, Size220 string
}

// IncredibleOffer is a struct containing
// DGKala incredible offer properties
type IncredibleOffer struct {
	ID                 uint
	ProductID          uint
	Title              string
	ImagePaths         ImagePaths
	BannerPath         string
	BannerPathMobile   string
	BannerPathTablet   string
	Row                uint
	ProductTitleFa     string
	ProductTitleEn     string
	Discount           uint
	Price              uint
	OnlyForApplication bool
	OnlyForMembers     bool
}

type incredibleOffersResponse struct {
	Data   []IncredibleOffer
	Status string
}

// ProductColor is a struct with properties of products colors
type ProductColor struct {
	Title string
	Hex   string
	Code  string
}

// ProductSearchResult is a struct containing a product details for a search result
type ProductSearchResult struct {
	ID                  int64
	EnglishTitle        string
	PersianTitle        string
	Image               string
	ExistsStatus        ProductExistsStatus
	IsActive            bool
	URL                 string
	Rate                int64
	MinimumPrice        int64
	MaximumPrice        int64
	Likes               int64
	LastPeriodLikes     int64
	Views               int64
	LastPeriodViews     int64
	IsSpecialOffer      bool
	RegisteredDateTime  time.Time
	HasVideo            bool
	Colors              []ProductColor
	UserRatingCount     int64
	Favorites           int64
	LastPeriodFavorites int64
	LastPeriodSales     int64
	HasGift             bool
	HTMLDetails         string
}

// SearchResult returns a struct containing results of the search for a keyword
type SearchResult struct {
	ResponseTime int64
	Count        int64
	Results      []ProductSearchResult
}

// ProductByIDResult returns a struct containing results of the request for product details by ID
type ProductByIDResult struct {
	Data ProductByID
}

// ProductByID is a struct containing a product details when you get it by ID
type ProductByID struct {
	ID                uint `json:"ProductId"`
	EnglishTitle      string
	PersianTitle      string
	Description       string
	ImagePaths        ImagePaths
	IsIncredibleOffer bool
	Strengths         string
	Weaknesses        string
	MinPrice          uint
}

func sendRequest(address string, headers requestHeader) (*http.Response, error) {
	request, err := http.NewRequest(http.MethodGet, address, nil)
	if err != nil {
		return nil, err
	}

	for key, value := range headers {
		request.Header.Add(key, value)
	}

	var client http.Client
	return client.Do(request)
}

func getStaticResourceAddress(resourcePath string) string {
	return fmt.Sprintf(staticFilesPath, resourcePath)
}

func getSearchAPIAddress(keyword string) string {
	query := url.QueryEscape(keyword)
	return fmt.Sprintf(searchAPIAddress, query)
}

func getProductByIDAPIAddress(productID int) string {
	return fmt.Sprintf(productByIDAPIAddress, productID)
}

func getRequestHeaders() requestHeader {
	return map[string]string{"ApplicationVersion": "1.4.1"}
}

// IncredibleOffers get a slice of DGKala IncredibleOffer items
func IncredibleOffers() ([]IncredibleOffer, error) {
	headers := getRequestHeaders()
	response, err := sendRequest(incredibleOffersAPIAddress, headers)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	var offersResponse incredibleOffersResponse
	err = json.Unmarshal(body, &offersResponse)
	if err != nil {
		return nil, err
	}
	incredibleOffers := offersResponse.Data
	return incredibleOffers, nil
}

// Search for a product in DGKala and return a slice of DGKala SearchResult items
func Search(keyword string) (SearchResult, error) {
	searchAddress := getSearchAPIAddress(keyword)
	httpResponse, err := sendRequest(searchAddress, requestHeader{})
	if err != nil {
		return SearchResult{}, err
	}

	responseBody, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return SearchResult{}, err
	}

	responseTime, err := jsonparser.GetInt(responseBody, "took")
	if err != nil {
		return SearchResult{}, err
	}

	count, err := jsonparser.GetInt(responseBody, "hits", "total")
	if err != nil {
		return SearchResult{}, err
	}

	productSearchResults := []ProductSearchResult{}
	realResultsJSONPath := []string{"hits", "hits"}
	parentJSONResultKey := "_source"
	jsonparser.ArrayEach(responseBody, func(value []byte, _ jsonparser.ValueType, _ int, _ error) {
		ID, _ := jsonparser.GetInt(value, parentJSONResultKey, "Id")
		englishTitle, _ := jsonparser.GetString(value, parentJSONResultKey, "EnTitle")
		persianTitle, _ := jsonparser.GetString(value, parentJSONResultKey, "FaTitle")
		imagePath, _ := jsonparser.GetString(value, parentJSONResultKey, "ImagePath")
		image := getStaticResourceAddress(imagePath)
		existsStatusInt, _ := jsonparser.GetInt(value, parentJSONResultKey, "ExistStatus")
		existsStatus := ProductExistsStatus(existsStatusInt)
		isActive, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "IsActive")
		URL, _ := jsonparser.GetString(value, parentJSONResultKey, "UrlCode")
		rate, _ := jsonparser.GetInt(value, parentJSONResultKey, "Rate")
		minimumPrice, _ := jsonparser.GetInt(value, parentJSONResultKey, "MinPrice")
		maximumPrice, _ := jsonparser.GetInt(value, parentJSONResultKey, "MaxPrice")
		likes, _ := jsonparser.GetInt(value, parentJSONResultKey, "LikeCounter")
		lastPeriodLikes, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodLikeCounter")
		views, _ := jsonparser.GetInt(value, parentJSONResultKey, "ViewCounter")
		lastPeriodViews, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodViewCounter")
		isSpecialOffer, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "IsSpecialOffer")
		regDateTimeString, _ := jsonparser.GetString(value, parentJSONResultKey, "RegDateTime")
		registeredDateTime, _ := time.Parse("2006-01-02T15:04:05", regDateTimeString)
		hasVideo, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "HasVideo")
		colors := []ProductColor{}
		jsonparser.ArrayEach(value, func(colorsValue []byte, _ jsonparser.ValueType, _ int, _ error) {
			colorTitle, _ := jsonparser.GetString(colorsValue, "ColorTitle")
			colorHex, _ := jsonparser.GetString(colorsValue, "ColorHex")
			colorCode, _ := jsonparser.GetString(colorsValue, "ColorCode")
			currentColor := ProductColor{
				colorTitle,
				colorHex,
				colorCode,
			}
			colors = append(colors, currentColor)
		}, parentJSONResultKey, "ProductColorList")
		userRatingCount, _ := jsonparser.GetInt(value, parentJSONResultKey, "UserRating")
		favorites, _ := jsonparser.GetInt(value, parentJSONResultKey, "FavoriteCounter")
		lastPeriodFavorites, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodFavoriteCounter")
		lastPeriodSales, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodSaleCounter")
		hasGift, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "HasGift")
		hTMLDetails, _ := jsonparser.GetString(value, parentJSONResultKey, "DetailSource")

		currentProductSearchResult := ProductSearchResult{
			ID,
			englishTitle,
			persianTitle,
			image,
			existsStatus,
			isActive,
			URL,
			rate,
			minimumPrice,
			maximumPrice,
			likes,
			lastPeriodLikes,
			views,
			lastPeriodViews,
			isSpecialOffer,
			registeredDateTime,
			hasVideo,
			colors,
			userRatingCount,
			favorites,
			lastPeriodFavorites,
			lastPeriodSales,
			hasGift,
			hTMLDetails,
		}
		productSearchResults = append(productSearchResults, currentProductSearchResult)
	}, realResultsJSONPath...)

	result := SearchResult{
		responseTime,
		count,
		productSearchResults,
	}

	return result, nil
}

// GetProductByID returns a product by getting it's ID
func GetProductByID(productID int) (ProductByID, error) {
	headers := getRequestHeaders()
	apiAddress := getProductByIDAPIAddress(productID)

	httpResponse, err := sendRequest(apiAddress, headers)

	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return ProductByID{}, err
	}
	var productByIDResult ProductByIDResult
	err = json.Unmarshal(body, &productByIDResult)
	if err != nil {
		return ProductByID{}, err
	}
	product := productByIDResult.Data
	return product, nil
}
<commit_msg>fix(ProductByID): fix wrong struct JSON keys for title<commit_after>package dgkala

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

	"github.com/buger/jsonparser"
)

const (
	incredibleOffersAPIAddress = "https://service2.digikala.com/api/IncredibleOffer/GetIncredibleOffer"
	searchAPIAddress           = "https://search.digikala.com/api/search?keyword=%s"
	staticFilesPath            = "https://file.digikala.com/digikala/%s"
	productByIDAPIAddress      = "https://service2.digikala.com/api/ProductCache/GetProductById/%d"
)

type requestHeader map[string]string

// ProductExistsStatus is a iota type for product existing status for buying
type ProductExistsStatus int

const (
	_ ProductExistsStatus = iota
	_
	// Available means the product is available to buy
	Available
	// OutOfStock means the product is not available  now and is out of stock
	OutOfStock
	// Discontinued means the product is discontinued
	Discontinued
)

// ImagePaths contains a product images in various sizes
type ImagePaths struct {
	Original, Size70, Size110, Size180, Size220 string
}

// IncredibleOffer is a struct containing
// DGKala incredible offer properties
type IncredibleOffer struct {
	ID                 uint
	ProductID          uint
	Title              string
	ImagePaths         ImagePaths
	BannerPath         string
	BannerPathMobile   string
	BannerPathTablet   string
	Row                uint
	ProductTitleFa     string
	ProductTitleEn     string
	Discount           uint
	Price              uint
	OnlyForApplication bool
	OnlyForMembers     bool
}

type incredibleOffersResponse struct {
	Data   []IncredibleOffer
	Status string
}

// ProductColor is a struct with properties of products colors
type ProductColor struct {
	Title string
	Hex   string
	Code  string
}

// ProductSearchResult is a struct containing a product details for a search result
type ProductSearchResult struct {
	ID                  int64
	EnglishTitle        string
	PersianTitle        string
	Image               string
	ExistsStatus        ProductExistsStatus
	IsActive            bool
	URL                 string
	Rate                int64
	MinimumPrice        int64
	MaximumPrice        int64
	Likes               int64
	LastPeriodLikes     int64
	Views               int64
	LastPeriodViews     int64
	IsSpecialOffer      bool
	RegisteredDateTime  time.Time
	HasVideo            bool
	Colors              []ProductColor
	UserRatingCount     int64
	Favorites           int64
	LastPeriodFavorites int64
	LastPeriodSales     int64
	HasGift             bool
	HTMLDetails         string
}

// SearchResult returns a struct containing results of the search for a keyword
type SearchResult struct {
	ResponseTime int64
	Count        int64
	Results      []ProductSearchResult
}

// ProductByIDResult returns a struct containing results of the request for product details by ID
type ProductByIDResult struct {
	Data ProductByID
}

// ProductByID is a struct containing a product details when you get it by ID
type ProductByID struct {
	ID                uint   `json:"ProductId"`
	EnglishTitle      string `json:"EnTitle"`
	PersianTitle      string `json:"FaTitle"`
	Description       string
	ImagePaths        ImagePaths
	IsIncredibleOffer bool
	Strengths         string
	Weaknesses        string
	MinPrice          uint
}

func sendRequest(address string, headers requestHeader) (*http.Response, error) {
	request, err := http.NewRequest(http.MethodGet, address, nil)
	if err != nil {
		return nil, err
	}

	for key, value := range headers {
		request.Header.Add(key, value)
	}

	var client http.Client
	return client.Do(request)
}

func getStaticResourceAddress(resourcePath string) string {
	return fmt.Sprintf(staticFilesPath, resourcePath)
}

func getSearchAPIAddress(keyword string) string {
	query := url.QueryEscape(keyword)
	return fmt.Sprintf(searchAPIAddress, query)
}

func getProductByIDAPIAddress(productID int) string {
	return fmt.Sprintf(productByIDAPIAddress, productID)
}

func getRequestHeaders() requestHeader {
	return map[string]string{"ApplicationVersion": "1.4.1"}
}

// IncredibleOffers get a slice of DGKala IncredibleOffer items
func IncredibleOffers() ([]IncredibleOffer, error) {
	headers := getRequestHeaders()
	response, err := sendRequest(incredibleOffersAPIAddress, headers)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	var offersResponse incredibleOffersResponse
	err = json.Unmarshal(body, &offersResponse)
	if err != nil {
		return nil, err
	}
	incredibleOffers := offersResponse.Data
	return incredibleOffers, nil
}

// Search for a product in DGKala and return a slice of DGKala SearchResult items
func Search(keyword string) (SearchResult, error) {
	searchAddress := getSearchAPIAddress(keyword)
	httpResponse, err := sendRequest(searchAddress, requestHeader{})
	if err != nil {
		return SearchResult{}, err
	}

	responseBody, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return SearchResult{}, err
	}

	responseTime, err := jsonparser.GetInt(responseBody, "took")
	if err != nil {
		return SearchResult{}, err
	}

	count, err := jsonparser.GetInt(responseBody, "hits", "total")
	if err != nil {
		return SearchResult{}, err
	}

	productSearchResults := []ProductSearchResult{}
	realResultsJSONPath := []string{"hits", "hits"}
	parentJSONResultKey := "_source"
	jsonparser.ArrayEach(responseBody, func(value []byte, _ jsonparser.ValueType, _ int, _ error) {
		ID, _ := jsonparser.GetInt(value, parentJSONResultKey, "Id")
		englishTitle, _ := jsonparser.GetString(value, parentJSONResultKey, "EnTitle")
		persianTitle, _ := jsonparser.GetString(value, parentJSONResultKey, "FaTitle")
		imagePath, _ := jsonparser.GetString(value, parentJSONResultKey, "ImagePath")
		image := getStaticResourceAddress(imagePath)
		existsStatusInt, _ := jsonparser.GetInt(value, parentJSONResultKey, "ExistStatus")
		existsStatus := ProductExistsStatus(existsStatusInt)
		isActive, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "IsActive")
		URL, _ := jsonparser.GetString(value, parentJSONResultKey, "UrlCode")
		rate, _ := jsonparser.GetInt(value, parentJSONResultKey, "Rate")
		minimumPrice, _ := jsonparser.GetInt(value, parentJSONResultKey, "MinPrice")
		maximumPrice, _ := jsonparser.GetInt(value, parentJSONResultKey, "MaxPrice")
		likes, _ := jsonparser.GetInt(value, parentJSONResultKey, "LikeCounter")
		lastPeriodLikes, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodLikeCounter")
		views, _ := jsonparser.GetInt(value, parentJSONResultKey, "ViewCounter")
		lastPeriodViews, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodViewCounter")
		isSpecialOffer, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "IsSpecialOffer")
		regDateTimeString, _ := jsonparser.GetString(value, parentJSONResultKey, "RegDateTime")
		registeredDateTime, _ := time.Parse("2006-01-02T15:04:05", regDateTimeString)
		hasVideo, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "HasVideo")
		colors := []ProductColor{}
		jsonparser.ArrayEach(value, func(colorsValue []byte, _ jsonparser.ValueType, _ int, _ error) {
			colorTitle, _ := jsonparser.GetString(colorsValue, "ColorTitle")
			colorHex, _ := jsonparser.GetString(colorsValue, "ColorHex")
			colorCode, _ := jsonparser.GetString(colorsValue, "ColorCode")
			currentColor := ProductColor{
				colorTitle,
				colorHex,
				colorCode,
			}
			colors = append(colors, currentColor)
		}, parentJSONResultKey, "ProductColorList")
		userRatingCount, _ := jsonparser.GetInt(value, parentJSONResultKey, "UserRating")
		favorites, _ := jsonparser.GetInt(value, parentJSONResultKey, "FavoriteCounter")
		lastPeriodFavorites, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodFavoriteCounter")
		lastPeriodSales, _ := jsonparser.GetInt(value, parentJSONResultKey, "LastPeriodSaleCounter")
		hasGift, _ := jsonparser.GetBoolean(value, parentJSONResultKey, "HasGift")
		hTMLDetails, _ := jsonparser.GetString(value, parentJSONResultKey, "DetailSource")

		currentProductSearchResult := ProductSearchResult{
			ID,
			englishTitle,
			persianTitle,
			image,
			existsStatus,
			isActive,
			URL,
			rate,
			minimumPrice,
			maximumPrice,
			likes,
			lastPeriodLikes,
			views,
			lastPeriodViews,
			isSpecialOffer,
			registeredDateTime,
			hasVideo,
			colors,
			userRatingCount,
			favorites,
			lastPeriodFavorites,
			lastPeriodSales,
			hasGift,
			hTMLDetails,
		}
		productSearchResults = append(productSearchResults, currentProductSearchResult)
	}, realResultsJSONPath...)

	result := SearchResult{
		responseTime,
		count,
		productSearchResults,
	}

	return result, nil
}

// GetProductByID returns a product by getting it's ID
func GetProductByID(productID int) (ProductByID, error) {
	headers := getRequestHeaders()
	apiAddress := getProductByIDAPIAddress(productID)

	httpResponse, err := sendRequest(apiAddress, headers)

	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return ProductByID{}, err
	}
	var productByIDResult ProductByIDResult
	err = json.Unmarshal(body, &productByIDResult)
	if err != nil {
		return ProductByID{}, err
	}
	product := productByIDResult.Data
	return product, nil
}
<|endoftext|>