<commit_before>// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/linux"
	"github.com/goreleaser/goreleaser/internal/pipe"
	"github.com/goreleaser/goreleaser/internal/semerrgroup"
	"github.com/goreleaser/goreleaser/internal/tmpl"
	"github.com/goreleaser/goreleaser/pkg/context"
	yaml "gopkg.in/yaml.v2"
)

// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")

// ErrNoDescription is shown when no description provided
var ErrNoDescription = errors.New("no description provided for snapcraft")

// ErrNoSummary is shown when no summary provided
var ErrNoSummary = errors.New("no summary provided for snapcraft")

// Metadata to generate the snap package
type Metadata struct {
	Name          string
	Version       string
	Summary       string
	Description   string
	Grade         string `yaml:",omitempty"`
	Confinement   string `yaml:",omitempty"`
	Architectures []string
	Apps          map[string]AppMetadata
}

// AppMetadata for the binaries that will be in the snap package
type AppMetadata struct {
	Command string
	Plugs   []string `yaml:",omitempty"`
	Daemon  string   `yaml:",omitempty"`
}

const defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"

// Pipe for snapcraft packaging
type Pipe struct{}

func (Pipe) String() string {
	return "Snapcraft Packages"
}

// Default sets the pipe defaults
func (Pipe) Default(ctx *context.Context) error {
	var snap = &ctx.Config.Snapcraft
	if snap.NameTemplate == "" {
		snap.NameTemplate = defaultNameTemplate
	}
	return nil
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	if ctx.Config.Snapcraft.Summary == "" && ctx.Config.Snapcraft.Description == "" {
		return pipe.Skip("no summary nor description were provided")
	}
	if ctx.Config.Snapcraft.Summary == "" {
		return ErrNoSummary
	}
	if ctx.Config.Snapcraft.Description == "" {
		return ErrNoDescription
	}
	_, err := exec.LookPath("snapcraft")
	if err != nil {
		return ErrNoSnapcraft
	}

	var g = semerrgroup.New(ctx.Parallelism)
	for platform, binaries := range ctx.Artifacts.Filter(
		artifact.And(
			artifact.ByGoos("linux"),
			artifact.ByType(artifact.Binary),
		),
	).GroupByPlatform() {
		arch := linux.Arch(platform)
		if arch == "armel" {
			log.WithField("arch", arch).Warn("ignored unsupported arch")
			continue
		}
		binaries := binaries
		g.Go(func() error {
			return create(ctx, arch, binaries)
		})
	}
	return g.Wait()
}

// Publish packages
func (Pipe) Publish(ctx *context.Context) error {
	snaps := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List()
	var g = semerrgroup.New(ctx.Parallelism)
	for _, snap := range snaps {
		snap := snap
		g.Go(func() error {
			return push(ctx, snap)
		})
	}
	return g.Wait()
}

func create(ctx *context.Context, arch string, binaries []artifact.Artifact) error {
	var log = log.WithField("arch", arch)
	folder, err := tmpl.New(ctx).
		WithArtifact(binaries[0], ctx.Config.Snapcraft.Replacements).
		Apply(ctx.Config.Snapcraft.NameTemplate)
	if err != nil {
		return err
	}
	// prime is the directory that then will be compressed to make the .snap package.
	var folderDir = filepath.Join(ctx.Config.Dist, folder)
	var primeDir = filepath.Join(folderDir, "prime")
	var metaDir = filepath.Join(primeDir, "meta")
	// #nosec
	if err = os.MkdirAll(metaDir, 0755); err != nil {
		return err
	}

	var file = filepath.Join(primeDir, "meta", "snap.yaml")
	log.WithField("file", file).Debug("creating snap metadata")

	var metadata = &Metadata{
		Version:       ctx.Version,
		Summary:       ctx.Config.Snapcraft.Summary,
		Description:   ctx.Config.Snapcraft.Description,
		Grade:         ctx.Config.Snapcraft.Grade,
		Confinement:   ctx.Config.Snapcraft.Confinement,
		Architectures: []string{arch},
		Apps:          make(map[string]AppMetadata),
	}

	metadata.Name = ctx.Config.ProjectName
	if ctx.Config.Snapcraft.Name != "" {
		metadata.Name = ctx.Config.Snapcraft.Name
	}

	for _, binary := range binaries {
		log.WithField("path", binary.Path).
			WithField("name", binary.Name).
			Debug("passed binary to snapcraft")
		appMetadata := AppMetadata{
			Command: binary.Name,
		}
		if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[binary.Name]; ok {
			appMetadata.Plugs = configAppMetadata.Plugs
			appMetadata.Daemon = configAppMetadata.Daemon
			appMetadata.Command = strings.Join([]string{
				appMetadata.Command,
				configAppMetadata.Args,
			}, " ")
		}
		metadata.Apps[binary.Name] = appMetadata

		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
		if err = os.Link(binary.Path, destBinaryPath); err != nil {
			return err
		}
	}

	if _, ok := metadata.Apps[metadata.Name]; !ok {
		metadata.Apps[metadata.Name] = metadata.Apps[binaries[0].Name]
	}

	out, err := yaml.Marshal(metadata)
	if err != nil {
		return err
	}

	if err = ioutil.WriteFile(file, out, 0644); err != nil {
		return err
	}

	var snap = filepath.Join(ctx.Config.Dist, folder+".snap")
	log.WithField("snap", snap).Info("creating")
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snap)
	if out, err = cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to generate snap package: %s", string(out))
	}
	if !ctx.Config.Snapcraft.Publish {
		return nil
	}
	ctx.Artifacts.Add(artifact.Artifact{
		Type:   artifact.PublishableSnapcraft,
		Name:   folder + ".snap",
		Path:   snap,
		Goos:   binaries[0].Goos,
		Goarch: binaries[0].Goarch,
		Goarm:  binaries[0].Goarm,
	})
	return nil
}

func push(ctx *context.Context, snap artifact.Artifact) error {
	log.WithField("snap", snap.Name).Info("pushing")
	// TODO: customize --release based on snap.Grade?
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "push", "--release=stable", snap.Path)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to push %s package: %s", snap.Path, string(out))
	}
	snap.Type = artifact.Snapcraft
	ctx.Artifacts.Add(snap)
	return nil
}
<commit_msg>chore: improved log<commit_after>// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/linux"
	"github.com/goreleaser/goreleaser/internal/pipe"
	"github.com/goreleaser/goreleaser/internal/semerrgroup"
	"github.com/goreleaser/goreleaser/internal/tmpl"
	"github.com/goreleaser/goreleaser/pkg/context"
	yaml "gopkg.in/yaml.v2"
)

// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")

// ErrNoDescription is shown when no description provided
var ErrNoDescription = errors.New("no description provided for snapcraft")

// ErrNoSummary is shown when no summary provided
var ErrNoSummary = errors.New("no summary provided for snapcraft")

// Metadata to generate the snap package
type Metadata struct {
	Name          string
	Version       string
	Summary       string
	Description   string
	Grade         string `yaml:",omitempty"`
	Confinement   string `yaml:",omitempty"`
	Architectures []string
	Apps          map[string]AppMetadata
}

// AppMetadata for the binaries that will be in the snap package
type AppMetadata struct {
	Command string
	Plugs   []string `yaml:",omitempty"`
	Daemon  string   `yaml:",omitempty"`
}

const defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"

// Pipe for snapcraft packaging
type Pipe struct{}

func (Pipe) String() string {
	return "Snapcraft Packages"
}

// Default sets the pipe defaults
func (Pipe) Default(ctx *context.Context) error {
	var snap = &ctx.Config.Snapcraft
	if snap.NameTemplate == "" {
		snap.NameTemplate = defaultNameTemplate
	}
	return nil
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	if ctx.Config.Snapcraft.Summary == "" && ctx.Config.Snapcraft.Description == "" {
		return pipe.Skip("no summary nor description were provided")
	}
	if ctx.Config.Snapcraft.Summary == "" {
		return ErrNoSummary
	}
	if ctx.Config.Snapcraft.Description == "" {
		return ErrNoDescription
	}
	_, err := exec.LookPath("snapcraft")
	if err != nil {
		return ErrNoSnapcraft
	}

	var g = semerrgroup.New(ctx.Parallelism)
	for platform, binaries := range ctx.Artifacts.Filter(
		artifact.And(
			artifact.ByGoos("linux"),
			artifact.ByType(artifact.Binary),
		),
	).GroupByPlatform() {
		arch := linux.Arch(platform)
		if arch == "armel" {
			log.WithField("arch", arch).Warn("ignored unsupported arch")
			continue
		}
		binaries := binaries
		g.Go(func() error {
			return create(ctx, arch, binaries)
		})
	}
	return g.Wait()
}

// Publish packages
func (Pipe) Publish(ctx *context.Context) error {
	snaps := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List()
	var g = semerrgroup.New(ctx.Parallelism)
	for _, snap := range snaps {
		snap := snap
		g.Go(func() error {
			return push(ctx, snap)
		})
	}
	return g.Wait()
}

func create(ctx *context.Context, arch string, binaries []artifact.Artifact) error {
	var log = log.WithField("arch", arch)
	folder, err := tmpl.New(ctx).
		WithArtifact(binaries[0], ctx.Config.Snapcraft.Replacements).
		Apply(ctx.Config.Snapcraft.NameTemplate)
	if err != nil {
		return err
	}
	// prime is the directory that then will be compressed to make the .snap package.
	var folderDir = filepath.Join(ctx.Config.Dist, folder)
	var primeDir = filepath.Join(folderDir, "prime")
	var metaDir = filepath.Join(primeDir, "meta")
	// #nosec
	if err = os.MkdirAll(metaDir, 0755); err != nil {
		return err
	}

	var file = filepath.Join(primeDir, "meta", "snap.yaml")
	log.WithField("file", file).Debug("creating snap metadata")

	var metadata = &Metadata{
		Version:       ctx.Version,
		Summary:       ctx.Config.Snapcraft.Summary,
		Description:   ctx.Config.Snapcraft.Description,
		Grade:         ctx.Config.Snapcraft.Grade,
		Confinement:   ctx.Config.Snapcraft.Confinement,
		Architectures: []string{arch},
		Apps:          make(map[string]AppMetadata),
	}

	metadata.Name = ctx.Config.ProjectName
	if ctx.Config.Snapcraft.Name != "" {
		metadata.Name = ctx.Config.Snapcraft.Name
	}

	for _, binary := range binaries {
		log.WithField("path", binary.Path).
			WithField("name", binary.Name).
			Debug("passed binary to snapcraft")
		appMetadata := AppMetadata{
			Command: binary.Name,
		}
		if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[binary.Name]; ok {
			appMetadata.Plugs = configAppMetadata.Plugs
			appMetadata.Daemon = configAppMetadata.Daemon
			appMetadata.Command = strings.Join([]string{
				appMetadata.Command,
				configAppMetadata.Args,
			}, " ")
		}
		metadata.Apps[binary.Name] = appMetadata

		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
		if err = os.Link(binary.Path, destBinaryPath); err != nil {
			return err
		}
	}

	if _, ok := metadata.Apps[metadata.Name]; !ok {
		metadata.Apps[metadata.Name] = metadata.Apps[binaries[0].Name]
	}

	out, err := yaml.Marshal(metadata)
	if err != nil {
		return err
	}

	if err = ioutil.WriteFile(file, out, 0644); err != nil {
		return err
	}

	var snap = filepath.Join(ctx.Config.Dist, folder+".snap")
	log.WithField("snap", snap).Info("creating")
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snap)
	if out, err = cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to generate snap package: %s", string(out))
	}
	if !ctx.Config.Snapcraft.Publish {
		return nil
	}
	ctx.Artifacts.Add(artifact.Artifact{
		Type:   artifact.PublishableSnapcraft,
		Name:   folder + ".snap",
		Path:   snap,
		Goos:   binaries[0].Goos,
		Goarch: binaries[0].Goarch,
		Goarm:  binaries[0].Goarm,
	})
	return nil
}

func push(ctx *context.Context, snap artifact.Artifact) error {
	log.WithField("snap", snap.Name).Info("pushing snap")
	// TODO: customize --release based on snap.Grade?
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "push", "--release=stable", snap.Path)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to push %s package: %s", snap.Path, string(out))
	}
	snap.Type = artifact.Snapcraft
	ctx.Artifacts.Add(snap)
	return nil
}
<|endoftext|>


<commit_before>// Package ipfix decodes IPFIX packets
//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//: All Rights Reserved
//:
//: file:    decoder.go
//: details: TODO
//: author:  Mehrdad Arshad Rad
//: date:    02/01/2017
//:
//: 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 ipfix

import (
	"errors"
	"io"
	"net"
)

// Decoder represents IPFIX payload and remote address
type Decoder struct {
	raddr  net.IP
	reader *Reader
}

// MessageHeader represents IPFIX message header
type MessageHeader struct {
	Version    uint16 // Version of IPFIX to which this Message conforms
	Length     uint16 // Total length of the IPFIX Message, measured in octets
	ExportTime uint32 // Time at which the IPFIX Message Header leaves the Exporter
	SequenceNo uint32 // Incremental sequence counter modulo 2^32
	DomainID   uint32 // A 32-bit id that is locally unique to the Exporting Process
}

// TemplateHeader represents template fields
type TemplateHeader struct {
	TemplateID      uint16
	FieldCount      uint16
	ScopeFieldCount uint16
}

// TemplateRecords represents template records
type TemplateRecords struct {
	TemplateID           uint16
	FieldCount           uint16
	FieldSpecifiers      []TemplateFieldSpecifier
	ScopeFieldCount      uint16
	ScopeFieldSpecifiers []TemplateFieldSpecifier
}

// TemplateFieldSpecifier represents field properties
type TemplateFieldSpecifier struct {
	ElementID    uint16
	Length       uint16
	EnterpriseNo uint32
}

// Message represents IPFIX decoded data
type Message struct {
	AgentID  string
	Header   MessageHeader
	DataSets [][]DecodedField
}

// DecodedField represents a decoded field
type DecodedField struct {
	ID    uint16
	Value interface{}
}

// SetHeader represents set header fields
type SetHeader struct {
	SetID  uint16
	Length uint16
}

var (
	rpcChan = make(chan RPCRequest, 1)

	errInvalidVersion    = errors.New("invalid ipfix version")
	errUnknownTemplateID = errors.New("unknown template id")
)

// NewDecoder constructs a decoder
func NewDecoder(raddr net.IP, b []byte) *Decoder {
	return &Decoder{raddr, NewReader(b)}
}

// Decode decodes the IPFIX raw data
func (d *Decoder) Decode(mem MemCache) (*Message, error) {
	var (
		msg = new(Message)
		err error
	)

	// IPFIX Message Header decoding
	if err = msg.Header.unmarshal(d.reader); err != nil {
		return nil, err
	}
	// IPFIX Message Header validation
	if err = msg.Header.validate(); err != nil {
		return nil, err
	}

	// Add source IP address as Agent ID
	msg.AgentID = d.raddr.String()

	for d.reader.Len() > 4 {

		setHeader := new(SetHeader)
		setHeader.unmarshal(d.reader)

		if setHeader.Length < 4 {
			return nil, io.ErrUnexpectedEOF
		}

		switch {
		case setHeader.SetID == 2:
			// Template set
			tr := TemplateRecords{}
			tr.unmarshal(d.reader)
			mem.insert(tr.TemplateID, d.raddr, tr)
		case setHeader.SetID == 3:
			// Option set
			tr := TemplateRecords{}
			tr.unmarshalOpts(d.reader)
			mem.insert(tr.TemplateID, d.raddr, tr)
		case setHeader.SetID >= 4 && setHeader.SetID <= 255:
			// Reserved
		default:
			// data
			for d.reader.Len() > 0 {
				tr, ok := mem.retrieve(setHeader.SetID, d.raddr)
				if !ok {
					select {
					case rpcChan <- RPCRequest{}:
					default:
					}
					return msg, errUnknownTemplateID
				}

				data := decodeData(d.reader, tr)
				msg.DataSets = append(msg.DataSets, data)
			}
		}
	}

	return msg, nil
}

// RFC 7011 - part 3.1. Message Header Format
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Version Number          |            Length             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Export Time                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Sequence Number                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Observation Domain ID                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (h *MessageHeader) unmarshal(r *Reader) error {
	var err error

	if h.Version, err = r.Uint16(); err != nil {
		return err
	}

	if h.Length, err = r.Uint16(); err != nil {
		return err
	}

	if h.ExportTime, err = r.Uint32(); err != nil {
		return err
	}

	if h.SequenceNo, err = r.Uint32(); err != nil {
		return err
	}

	if h.DomainID, err = r.Uint32(); err != nil {
		return err
	}

	return nil
}

func (h *MessageHeader) validate() error {
	if h.Version != 0x000a {
		return errInvalidVersion
	}

	// TODO: needs more validation

	return nil
}

// RFC 7011 - part 3.3.2 Set Header Format
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          Set ID               |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (h *SetHeader) unmarshal(r *Reader) error {
	var err error

	if h.SetID, err = r.Uint16(); err != nil {
		return err
	}

	if h.Length, err = r.Uint16(); err != nil {
		return err
	}

	return nil
}

// RFC 7011
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Set ID = (2 or 3)       |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Template ID           |         Field Count           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (t *TemplateHeader) unmarshal(r *Reader) error {
	var err error

	if t.TemplateID, err = r.Uint16(); err != nil {
		return err
	}

	if t.FieldCount, err = r.Uint16(); err != nil {
		return err
	}

	return nil

}

// RFC 7011 3.4.2.2.  Options Template Record Format
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          Set ID = 3           |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Template ID           |         Field Count = N + M   |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Scope Field Count = N     |0|  Scope 1 Infor. Element id. |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (t *TemplateHeader) unmarshalOpts(r *Reader) error {
	var err error

	if t.TemplateID, err = r.Uint16(); err != nil {
		return err
	}

	if t.FieldCount, err = r.Uint16(); err != nil {
		return err
	}

	if t.ScopeFieldCount, err = r.Uint16(); err != nil {
		return err
	}

	return nil

}

// RFC 7011
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |E|  Information Element ident. |        Field Length           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                      Enterprise Number                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (f *TemplateFieldSpecifier) unmarshal(r *Reader) error {
	var err error

	if f.ElementID, err = r.Uint16(); err != nil {
		return err
	}

	if f.Length, err = r.Uint16(); err != nil {
		return err
	}

	if f.ElementID > 0x8000 {
		f.ElementID = f.ElementID & 0x7fff
		if f.EnterpriseNo, err = r.Uint32(); err != nil {
			return err
		}
	}

	return nil
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          Set ID = 2           |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |      Template ID = 256        |         Field Count = N       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |1| Information Element id. 1.1 |        Field Length 1.1       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Enterprise Number  1.1                     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |0| Information Element id. 1.2 |        Field Length 1.2       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |             ...               |              ...              |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (tr *TemplateRecords) unmarshal(r *Reader) {
	var (
		th = TemplateHeader{}
		tf = TemplateFieldSpecifier{}
	)

	th.unmarshal(r)
	tr.TemplateID = th.TemplateID
	tr.FieldCount = th.FieldCount

	for i := th.FieldCount; i > 0; i-- {
		tf.unmarshal(r)
		tr.FieldSpecifiers = append(tr.FieldSpecifiers, tf)
	}
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |          Set ID = 3           |          Length               |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |         Template ID = X       |         Field Count = N + M   |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope Field Count = N     |0|  Scope 1 Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope 1 Field Length      |0|  Scope 2 Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope 2 Field Length      |             ...               |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |            ...                |1|  Scope N Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope N Field Length      |   Scope N Enterprise Number  ...
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ...  Scope N Enterprise Number   |1| Option 1 Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |    Option 1 Field Length      |  Option 1 Enterprise Number  ...
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ... Option 1 Enterprise Number   |              ...              |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |             ...               |0| Option M Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Option M Field Length     |      Padding (optional)       |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (tr *TemplateRecords) unmarshalOpts(r *Reader) {
	var (
		th = TemplateHeader{}
		tf = TemplateFieldSpecifier{}
	)

	th.unmarshalOpts(r)
	tr.TemplateID = th.TemplateID
	tr.FieldCount = th.FieldCount
	tr.ScopeFieldCount = th.ScopeFieldCount

	for i := th.ScopeFieldCount; i > 0; i-- {
		tf.unmarshal(r)
		tr.ScopeFieldSpecifiers = append(tr.FieldSpecifiers, tf)
	}

	for i := th.FieldCount - th.ScopeFieldCount; i > 0; i-- {
		tf.unmarshal(r)
		tr.FieldSpecifiers = append(tr.FieldSpecifiers, tf)
	}
}

func decodeData(r *Reader, tr TemplateRecords) []DecodedField {
	var (
		fields []DecodedField
		b      []byte
	)

	for i := 0; i < len(tr.FieldSpecifiers); i++ {
		b, _ = r.Read(int(tr.FieldSpecifiers[i].Length))
		m := ipfixInfoModel[elementKey{
			tr.FieldSpecifiers[i].EnterpriseNo,
			tr.FieldSpecifiers[i].ElementID,
		}]
		fields = append(fields, DecodedField{
			ID:    m.FieldID,
			Value: interpret(b, m.Type),
		})
	}

	return fields
}
<commit_msg>init id and raddr to rpc request<commit_after>// Package ipfix decodes IPFIX packets
//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//: All Rights Reserved
//:
//: file:    decoder.go
//: details: TODO
//: author:  Mehrdad Arshad Rad
//: date:    02/01/2017
//:
//: 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 ipfix

import (
	"errors"
	"io"
	"net"
)

// Decoder represents IPFIX payload and remote address
type Decoder struct {
	raddr  net.IP
	reader *Reader
}

// MessageHeader represents IPFIX message header
type MessageHeader struct {
	Version    uint16 // Version of IPFIX to which this Message conforms
	Length     uint16 // Total length of the IPFIX Message, measured in octets
	ExportTime uint32 // Time at which the IPFIX Message Header leaves the Exporter
	SequenceNo uint32 // Incremental sequence counter modulo 2^32
	DomainID   uint32 // A 32-bit id that is locally unique to the Exporting Process
}

// TemplateHeader represents template fields
type TemplateHeader struct {
	TemplateID      uint16
	FieldCount      uint16
	ScopeFieldCount uint16
}

// TemplateRecords represents template records
type TemplateRecords struct {
	TemplateID           uint16
	FieldCount           uint16
	FieldSpecifiers      []TemplateFieldSpecifier
	ScopeFieldCount      uint16
	ScopeFieldSpecifiers []TemplateFieldSpecifier
}

// TemplateFieldSpecifier represents field properties
type TemplateFieldSpecifier struct {
	ElementID    uint16
	Length       uint16
	EnterpriseNo uint32
}

// Message represents IPFIX decoded data
type Message struct {
	AgentID  string
	Header   MessageHeader
	DataSets [][]DecodedField
}

// DecodedField represents a decoded field
type DecodedField struct {
	ID    uint16
	Value interface{}
}

// SetHeader represents set header fields
type SetHeader struct {
	SetID  uint16
	Length uint16
}

var (
	rpcChan = make(chan RPCRequest, 1)

	errInvalidVersion    = errors.New("invalid ipfix version")
	errUnknownTemplateID = errors.New("unknown template id")
)

// NewDecoder constructs a decoder
func NewDecoder(raddr net.IP, b []byte) *Decoder {
	return &Decoder{raddr, NewReader(b)}
}

// Decode decodes the IPFIX raw data
func (d *Decoder) Decode(mem MemCache) (*Message, error) {
	var (
		msg = new(Message)
		err error
	)

	// IPFIX Message Header decoding
	if err = msg.Header.unmarshal(d.reader); err != nil {
		return nil, err
	}
	// IPFIX Message Header validation
	if err = msg.Header.validate(); err != nil {
		return nil, err
	}

	// Add source IP address as Agent ID
	msg.AgentID = d.raddr.String()

	for d.reader.Len() > 4 {

		setHeader := new(SetHeader)
		setHeader.unmarshal(d.reader)

		if setHeader.Length < 4 {
			return nil, io.ErrUnexpectedEOF
		}

		switch {
		case setHeader.SetID == 2:
			// Template set
			tr := TemplateRecords{}
			tr.unmarshal(d.reader)
			mem.insert(tr.TemplateID, d.raddr, tr)
		case setHeader.SetID == 3:
			// Option set
			tr := TemplateRecords{}
			tr.unmarshalOpts(d.reader)
			mem.insert(tr.TemplateID, d.raddr, tr)
		case setHeader.SetID >= 4 && setHeader.SetID <= 255:
			// Reserved
		default:
			// data
			for d.reader.Len() > 0 {
				tr, ok := mem.retrieve(setHeader.SetID, d.raddr)
				if !ok {
					select {
					case rpcChan <- RPCRequest{
						ID: setHeader.SetID,
						IP: d.raddr,
					}:
					default:
					}
					return msg, errUnknownTemplateID
				}

				data := decodeData(d.reader, tr)
				msg.DataSets = append(msg.DataSets, data)
			}
		}
	}

	return msg, nil
}

// RFC 7011 - part 3.1. Message Header Format
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Version Number          |            Length             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Export Time                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Sequence Number                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Observation Domain ID                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (h *MessageHeader) unmarshal(r *Reader) error {
	var err error

	if h.Version, err = r.Uint16(); err != nil {
		return err
	}

	if h.Length, err = r.Uint16(); err != nil {
		return err
	}

	if h.ExportTime, err = r.Uint32(); err != nil {
		return err
	}

	if h.SequenceNo, err = r.Uint32(); err != nil {
		return err
	}

	if h.DomainID, err = r.Uint32(); err != nil {
		return err
	}

	return nil
}

func (h *MessageHeader) validate() error {
	if h.Version != 0x000a {
		return errInvalidVersion
	}

	// TODO: needs more validation

	return nil
}

// RFC 7011 - part 3.3.2 Set Header Format
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          Set ID               |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (h *SetHeader) unmarshal(r *Reader) error {
	var err error

	if h.SetID, err = r.Uint16(); err != nil {
		return err
	}

	if h.Length, err = r.Uint16(); err != nil {
		return err
	}

	return nil
}

// RFC 7011
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Set ID = (2 or 3)       |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Template ID           |         Field Count           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (t *TemplateHeader) unmarshal(r *Reader) error {
	var err error

	if t.TemplateID, err = r.Uint16(); err != nil {
		return err
	}

	if t.FieldCount, err = r.Uint16(); err != nil {
		return err
	}

	return nil

}

// RFC 7011 3.4.2.2.  Options Template Record Format
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          Set ID = 3           |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Template ID           |         Field Count = N + M   |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Scope Field Count = N     |0|  Scope 1 Infor. Element id. |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (t *TemplateHeader) unmarshalOpts(r *Reader) error {
	var err error

	if t.TemplateID, err = r.Uint16(); err != nil {
		return err
	}

	if t.FieldCount, err = r.Uint16(); err != nil {
		return err
	}

	if t.ScopeFieldCount, err = r.Uint16(); err != nil {
		return err
	}

	return nil

}

// RFC 7011
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |E|  Information Element ident. |        Field Length           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                      Enterprise Number                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (f *TemplateFieldSpecifier) unmarshal(r *Reader) error {
	var err error

	if f.ElementID, err = r.Uint16(); err != nil {
		return err
	}

	if f.Length, err = r.Uint16(); err != nil {
		return err
	}

	if f.ElementID > 0x8000 {
		f.ElementID = f.ElementID & 0x7fff
		if f.EnterpriseNo, err = r.Uint32(); err != nil {
			return err
		}
	}

	return nil
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          Set ID = 2           |          Length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |      Template ID = 256        |         Field Count = N       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |1| Information Element id. 1.1 |        Field Length 1.1       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Enterprise Number  1.1                     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |0| Information Element id. 1.2 |        Field Length 1.2       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |             ...               |              ...              |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (tr *TemplateRecords) unmarshal(r *Reader) {
	var (
		th = TemplateHeader{}
		tf = TemplateFieldSpecifier{}
	)

	th.unmarshal(r)
	tr.TemplateID = th.TemplateID
	tr.FieldCount = th.FieldCount

	for i := th.FieldCount; i > 0; i-- {
		tf.unmarshal(r)
		tr.FieldSpecifiers = append(tr.FieldSpecifiers, tf)
	}
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |          Set ID = 3           |          Length               |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |         Template ID = X       |         Field Count = N + M   |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope Field Count = N     |0|  Scope 1 Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope 1 Field Length      |0|  Scope 2 Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope 2 Field Length      |             ...               |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |            ...                |1|  Scope N Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Scope N Field Length      |   Scope N Enterprise Number  ...
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ...  Scope N Enterprise Number   |1| Option 1 Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |    Option 1 Field Length      |  Option 1 Enterprise Number  ...
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ... Option 1 Enterprise Number   |              ...              |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |             ...               |0| Option M Infor. Element id. |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Option M Field Length     |      Padding (optional)       |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

func (tr *TemplateRecords) unmarshalOpts(r *Reader) {
	var (
		th = TemplateHeader{}
		tf = TemplateFieldSpecifier{}
	)

	th.unmarshalOpts(r)
	tr.TemplateID = th.TemplateID
	tr.FieldCount = th.FieldCount
	tr.ScopeFieldCount = th.ScopeFieldCount

	for i := th.ScopeFieldCount; i > 0; i-- {
		tf.unmarshal(r)
		tr.ScopeFieldSpecifiers = append(tr.FieldSpecifiers, tf)
	}

	for i := th.FieldCount - th.ScopeFieldCount; i > 0; i-- {
		tf.unmarshal(r)
		tr.FieldSpecifiers = append(tr.FieldSpecifiers, tf)
	}
}

func decodeData(r *Reader, tr TemplateRecords) []DecodedField {
	var (
		fields []DecodedField
		b      []byte
	)

	for i := 0; i < len(tr.FieldSpecifiers); i++ {
		b, _ = r.Read(int(tr.FieldSpecifiers[i].Length))
		m := ipfixInfoModel[elementKey{
			tr.FieldSpecifiers[i].EnterpriseNo,
			tr.FieldSpecifiers[i].ElementID,
		}]
		fields = append(fields, DecodedField{
			ID:    m.FieldID,
			Value: interpret(b, m.Type),
		})
	}

	return fields
}
<|endoftext|>


<commit_before>package httpcache

import (
	"github.com/lostisland/go-sawyer"
	"github.com/lostisland/go-sawyer/hypermedia"
	"net/http"
	"os"
	"path/filepath"
)

const (
	responseFilename = "response"
	bodyFilename     = "body"
	fileCreateFlag   = os.O_RDWR | os.O_CREATE | os.O_EXCL
)

type FileCache struct {
	path string
}

func NewFileCache(path string) *FileCache {
	return &FileCache{path}
}

func (c *FileCache) Get(req *http.Request, v interface{}) *sawyer.Response {
	path := c.requestPath(req)

	responseFile, err := os.Open(filepath.Join(path, responseFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer responseFile.Close()

	bodyFile, err := os.Open(filepath.Join(path, bodyFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer bodyFile.Close()

	return DecodeFrom(v, responseFile, bodyFile)
}

func (c *FileCache) Set(req *http.Request, res *sawyer.Response, v interface{}) error {
	path := c.requestPath(req)
	if err := os.MkdirAll(path, 0755); err != nil {
		return err
	}

	responseFile, err := os.OpenFile(filepath.Join(path, responseFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer responseFile.Close()

	bodyFile, err := os.OpenFile(filepath.Join(path, bodyFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer bodyFile.Close()

	return EncodeTo(v, res, responseFile, bodyFile)
}

func (c *FileCache) Rels(req *http.Request) hypermedia.Relations {
	path := c.requestPath(req)

	responseFile, err := os.Create(filepath.Join(path, responseFilename))
	if err != nil {
		return hypermedia.Relations{}
	}
	defer responseFile.Close()

	return Decode(responseFile).Rels
}

func (c *FileCache) requestPath(r *http.Request) string {
	return filepath.Join(c.path, RequestSha(r))
}
<commit_msg>write the actual key so you can see what the requests are<commit_after>package httpcache

import (
	"github.com/lostisland/go-sawyer"
	"github.com/lostisland/go-sawyer/hypermedia"
	"net/http"
	"os"
	"path/filepath"
)

const (
	keyFilename      = "key"
	responseFilename = "response"
	bodyFilename     = "body"
	fileCreateFlag   = os.O_RDWR | os.O_CREATE | os.O_EXCL
)

type FileCache struct {
	path string
}

func NewFileCache(path string) *FileCache {
	return &FileCache{path}
}

func (c *FileCache) Get(req *http.Request, v interface{}) *sawyer.Response {
	path := c.requestPath(req)

	responseFile, err := os.Open(filepath.Join(path, responseFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer responseFile.Close()

	bodyFile, err := os.Open(filepath.Join(path, bodyFilename))
	if err != nil {
		return ResponseError(err)
	}
	defer bodyFile.Close()

	return DecodeFrom(v, responseFile, bodyFile)
}

func (c *FileCache) Set(req *http.Request, res *sawyer.Response, v interface{}) error {
	path := c.requestPath(req)
	if err := os.MkdirAll(path, 0755); err != nil {
		return err
	}

	keyFile, err := os.OpenFile(filepath.Join(path, keyFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer keyFile.Close()
	keyFile.Write([]byte(RequestKey(req)))

	responseFile, err := os.OpenFile(filepath.Join(path, responseFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer responseFile.Close()

	bodyFile, err := os.OpenFile(filepath.Join(path, bodyFilename), fileCreateFlag, 0666)
	if err != nil {
		return err
	}
	defer bodyFile.Close()

	return EncodeTo(v, res, responseFile, bodyFile)
}

func (c *FileCache) Rels(req *http.Request) hypermedia.Relations {
	path := c.requestPath(req)

	responseFile, err := os.Create(filepath.Join(path, responseFilename))
	if err != nil {
		return hypermedia.Relations{}
	}
	defer responseFile.Close()

	return Decode(responseFile).Rels
}

func (c *FileCache) requestPath(r *http.Request) string {
	return filepath.Join(c.path, RequestSha(r))
}
<|endoftext|>


<commit_before>package httpcheck

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Method int

const (
	GET Method = iota
	POST
)

// A single HTTP action, with the expected results.
type Test struct {
	Url           string            // A fully specified URL including the protocol
	Content       string            ",omitempty" // Expected content as a regexp, e.g. "Hello World"
	Code          int               ",omitempty" // Expected HTTP response code
	Method        Method            ",omitempty" // HTTP method, i.e. GET (default) or POST
	Data          string            ",omitempty" // Optional post data
	Headers       map[string]string ",omitempty" // Optional headers to add to the request
	SkipSSLVerify bool              ",omitempty" // If true, SSL server verification is skipped
}

// Test makes a HTTP call and checks the response
func (t Test) Test() (err error) {
	err = t.Validate()

	var code int
	var body string
	if err == nil {
		code, body, err = t.DoRequest()
	}
	if err == nil {
		err = t.CheckCode(code)
	}
	if err == nil {
		err = t.CheckContent(body)
	}

	// Log an error
	if Verbose {
		if err == nil {
			fmt.Println(t.String() + " OK")
		} else {
			fmt.Println(err)
		}
	}
	return
}

func (t Test) Validate() error {
	scheme := regexp.MustCompile("^https?:")
	if !scheme.MatchString(t.Url) {
		return errors.New("URL is not absolute, must specify a base URL (-u): " + t.Url)
	}
	return nil
}

// DoRequest uses the global http object to send a HTTP request
func (t Test) DoRequest() (code int, body string, err error) {
	req, err := http.NewRequest(t.MethodName(), t.Url, strings.NewReader(t.Data))
	if err != nil {
		return 0, "", err
	}

	req.Header.Add("User-Agent", version)
	if t.Method == POST {
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	}
	for k, v := range t.Headers {
		req.Header.Add(k, v)
	}

	client.Timeout = time.Duration(int(RequestTimeout)) * time.Second
	resp, err := client.Do(req)
	if err != nil {
		return 0, "", err
	}

	rcvdbytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, "", err
	}
	body = string(rcvdbytes)
	code = resp.StatusCode

	defer resp.Body.Close()

	return code, body, nil
}

// CheckCode inspects the received HTTP response code
func (t Test) CheckCode(code int) error {
	expect := t.Code
	if expect == 0 {
		expect = 200
	}
	if code != expect {
		return t.NewError("Expected status code " + strconv.Itoa(expect) + ", received " + strconv.Itoa(code))
	}
	return nil
}

// CheckContent inspects the returned HTTP response body
func (t Test) CheckContent(body string) error {
	if t.Content == "" {
		return nil
	}

	match, err := regexp.MatchString(t.Content, body)
	if err != nil {
		return err
	}

	if !match {
		return t.NewError("Expected content '" + t.Content + "', not found in response (" + strconv.Itoa(len(body)) + " bytes)")
	}

	return nil
}

func (t Test) NewError(message string) error {
	return errors.New(t.String() + " FAIL: " + message)
}

func (t Test) String() string {
	return strings.Title(strings.ToLower(t.MethodName())) + " " + t.Url
}

func (t Test) MethodName() string {
	switch t.Method {
	case GET:
		return "GET"
	case POST:
		return "POST"
	default:
		return ""
	}
}
<commit_msg>test: change method from int to string to allow any user-specified method<commit_after>package httpcheck

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Method int

// A single HTTP action, with the expected results.
type Test struct {
	Url           string            // A fully specified URL including the protocol
	Content       string            ",omitempty" // Expected content as a regexp, e.g. "Hello World"
	Code          int               ",omitempty" // Expected HTTP response code
	Method        string            ",omitempty" // HTTP method, i.e. "GET" (default) or "POST"
	Data          string            ",omitempty" // Optional post data
	Headers       map[string]string ",omitempty" // Optional headers to add to the request
	SkipSSLVerify bool              ",omitempty" // If true, SSL server verification is skipped
}

// Test makes a HTTP call and checks the response
func (t Test) Test() (err error) {
	err = t.Validate()

	var code int
	var body string
	if err == nil {
		code, body, err = t.DoRequest()
	}
	if err == nil {
		err = t.CheckCode(code)
	}
	if err == nil {
		err = t.CheckContent(body)
	}

	// Log an error
	if Verbose {
		if err == nil {
			fmt.Println(t.String() + " OK")
		} else {
			fmt.Println(err)
		}
	}
	return
}

func (t Test) Validate() error {
	scheme := regexp.MustCompile("^https?:")
	if !scheme.MatchString(t.Url) {
		return errors.New("URL is not absolute, must specify a base URL (-u): " + t.Url)
	}
	return nil
}

// DoRequest uses the global http object to send a HTTP request
func (t Test) DoRequest() (code int, body string, err error) {
	req, err := http.NewRequest(t.MethodName(), t.Url, strings.NewReader(t.Data))
	if err != nil {
		return 0, "", err
	}

	req.Header.Add("User-Agent", version)
	if t.Method == "POST" {
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	}
	for k, v := range t.Headers {
		req.Header.Add(k, v)
	}

	client.Timeout = time.Duration(int(RequestTimeout)) * time.Second
	resp, err := client.Do(req)
	if err != nil {
		return 0, "", err
	}

	rcvdbytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, "", err
	}
	body = string(rcvdbytes)
	code = resp.StatusCode

	defer resp.Body.Close()

	return code, body, nil
}

// CheckCode inspects the received HTTP response code
func (t Test) CheckCode(code int) error {
	expect := t.Code
	if expect == 0 {
		expect = 200
	}
	if code != expect {
		return t.NewError("Expected status code " + strconv.Itoa(expect) + ", received " + strconv.Itoa(code))
	}
	return nil
}

// CheckContent inspects the returned HTTP response body
func (t Test) CheckContent(body string) error {
	if t.Content == "" {
		return nil
	}

	match, err := regexp.MatchString(t.Content, body)
	if err != nil {
		return err
	}

	if !match {
		return t.NewError("Expected content '" + t.Content + "', not found in response (" + strconv.Itoa(len(body)) + " bytes)")
	}

	return nil
}

func (t Test) NewError(message string) error {
	return errors.New(t.String() + " FAIL: " + message)
}

func (t Test) String() string {
	return strings.Title(strings.ToLower(t.MethodName())) + " " + t.Url
}

func (t Test) MethodName() string {
	if t.Method == "" {
		return "GET"
	}
	return t.Method
}
<|endoftext|>


<commit_before>package httpcheck

import (
	"encoding/json"
	"encoding/xml"
	"github.com/stretchr/testify/assert"
	"net/http"
	"testing"
)

type testPerson struct {
	Name string
	Age  int
}

type testHandler struct{}

func (t *testHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.URL.Path {
	case "/some":
		http.SetCookie(w, &http.Cookie{
			Name:  "some",
			Value: "cookie",
		})
		w.Header().Add("some", "header")
		w.WriteHeader(204)
	case "/json":
		body, err := json.Marshal(testPerson{
			Name: "Some",
			Age:  30,
		})

		if err != nil {
			panic(err)
		}

		w.Header().Set("Content-Type", "application/json")
		w.Write(body)

	case "/xml":
		body, err := xml.Marshal(testPerson{
			Name: "Some",
			Age:  30,
		})

		if err != nil {
			panic(err)
		}

		w.Header().Set("Content-Type", "application/xml")
		w.Write(body)
	case "/byte":
		w.Write([]byte("hello world"))
	case "/nothing":

	}
}

func makeTestChecker(t *testing.T) *Checker {
	handler := &testHandler{}
	port := ":3000"
	return New(t, handler, port)
}

func TestNew(t *testing.T) {
	handler := &testHandler{}
	addr := ":3000"
	checker := New(t, handler, addr)

	assert.NotNil(t, checker)
	assert.Exactly(t, t, checker.t)
	assert.Exactly(t, handler, checker.handler)
	assert.Exactly(t, addr, checker.addr)
	assert.NotNil(t, checker.server)
}

func TestTest(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")

	assert.NotNil(t, checker.request)
	assert.Exactly(t, "GET", checker.request.Method)
	assert.Exactly(t, "/some", checker.request.URL.Path)
}

func TestRequest(t *testing.T) {
	checker := makeTestChecker(t)
	request := &http.Request{
		Method: "GET",
	}

	checker.TestRequest(request)
	assert.NotNil(t, checker.request)
	assert.Exactly(t, "GET", checker.request.Method)
	assert.Nil(t, checker.request.URL)
}

func TestWithHeader(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")

	checker.WithHeader("key", "value")

	assert.Equal(t, checker.request.Header.Get("key"), "value")
	assert.Equal(t, "", checker.request.Header.Get("unknown"))
}

func TestWithCookie(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")

	checker.WithCookie("key", "value")

	cookie, err := checker.request.Cookie("key")
	assert.Nil(t, err)
	assert.Equal(t, cookie.Value, "value")

	cookie, err = checker.request.Cookie("unknown")
	assert.NotNil(t, err)
}

func TestCheck(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")
	checker.Check()

	assert.NotNil(t, checker.response)
	assert.Exactly(t, 204, checker.response.StatusCode)
}

func TestHasStatus(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasStatus(202)
	assert.True(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasStatus(204)
	assert.False(t, mockT.Failed())
}

func TestHasHeader(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasHeader("some", "header")
	assert.False(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasHeader("some", "unknown")
	assert.True(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasHeader("unknown", "header")
	assert.True(t, mockT.Failed())
}

func TestHasCookie(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.False(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("some", "unknown")
	assert.True(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("unknown", "cookie")
	assert.True(t, mockT.Failed())
}

func TestHasJson(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/json")
	checker.Check()

	person := &testPerson{
		Name: "Some",
		Age:  30,
	}
	checker.HasJson(person)
	assert.False(t, mockT.Failed())

	person = &testPerson{
		Name: "Unknown",
		Age:  30,
	}
	checker.HasJson(person)
	assert.True(t, mockT.Failed())
}

func TestHasXml(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/xml")
	checker.Check()

	person := &testPerson{
		Name: "Some",
		Age:  30,
	}
	checker.HasXml(person)
	assert.False(t, mockT.Failed())

	person = &testPerson{
		Name: "Unknown",
		Age:  30,
	}
	checker.HasXml(person)
	assert.True(t, mockT.Failed())
}

func TestHasBody(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/byte")
	checker.Check()

	checker.HasBody([]byte("hello world"))
}

func TestCb(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/json")
	checker.Check()

	called := false
	checker.Cb(func(response *http.Response) {
		called = true
	})

	assert.True(t, called)
}

func TestCookies(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.PersistCookie("some")

	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.False(t, mockT.Failed())

	checker.Test("GET", "/nothing")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.False(t, mockT.Failed())
}

func TestCookiesDelete(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)

	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.False(t, mockT.Failed())

	checker.Test("GET", "/nothing")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.True(t, mockT.Failed())
}
<commit_msg>refactor tests<commit_after>package httpcheck

import (
	"encoding/json"
	"encoding/xml"
	"github.com/stretchr/testify/assert"
	"net/http"
	"testing"
)

type testPerson struct {
	Name string
	Age  int
}

type testHandler struct{}

func (t *testHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.URL.Path {
	case "/some":
		http.SetCookie(w, &http.Cookie{
			Name:  "some",
			Value: "cookie",
		})
		w.Header().Add("some", "header")
		w.WriteHeader(204)
	case "/json":
		body, err := json.Marshal(testPerson{
			Name: "Some",
			Age:  30,
		})

		if err != nil {
			panic(err)
		}

		w.Header().Set("Content-Type", "application/json")
		w.Write(body)

	case "/xml":
		body, err := xml.Marshal(testPerson{
			Name: "Some",
			Age:  30,
		})

		if err != nil {
			panic(err)
		}

		w.Header().Set("Content-Type", "application/xml")
		w.Write(body)
	case "/byte":
		w.Write([]byte("hello world"))
	case "/cookies":
		http.SetCookie(w, &http.Cookie{
			Name:  "some",
			Value: "cookie",
		})
		http.SetCookie(w, &http.Cookie{
			Name:  "other",
			Value: "secondcookie",
		})
	case "/nothing":

	}
}

func makeTestChecker(t *testing.T) *Checker {
	handler := &testHandler{}
	port := ":3000"
	return New(t, handler, port)
}

func TestNew(t *testing.T) {
	handler := &testHandler{}
	addr := ":3000"
	checker := New(t, handler, addr)

	assert.NotNil(t, checker)
	assert.Exactly(t, t, checker.t)
	assert.Exactly(t, handler, checker.handler)
	assert.Exactly(t, addr, checker.addr)
	assert.NotNil(t, checker.server)
}

func TestTest(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")

	assert.NotNil(t, checker.request)
	assert.Exactly(t, "GET", checker.request.Method)
	assert.Exactly(t, "/some", checker.request.URL.Path)
}

func TestRequest(t *testing.T) {
	checker := makeTestChecker(t)
	request := &http.Request{
		Method: "GET",
	}

	checker.TestRequest(request)
	assert.NotNil(t, checker.request)
	assert.Exactly(t, "GET", checker.request.Method)
	assert.Nil(t, checker.request.URL)
}

func TestWithHeader(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")

	checker.WithHeader("key", "value")

	assert.Equal(t, checker.request.Header.Get("key"), "value")
	assert.Equal(t, "", checker.request.Header.Get("unknown"))
}

func TestWithCookie(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")

	checker.WithCookie("key", "value")

	cookie, err := checker.request.Cookie("key")
	assert.Nil(t, err)
	assert.Equal(t, cookie.Value, "value")

	cookie, err = checker.request.Cookie("unknown")
	assert.NotNil(t, err)
}

func TestCheck(t *testing.T) {
	checker := makeTestChecker(t)
	checker.Test("GET", "/some")
	checker.Check()

	assert.NotNil(t, checker.response)
	assert.Exactly(t, 204, checker.response.StatusCode)
}

func TestHasStatus(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasStatus(202)
	assert.True(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasStatus(204)
	assert.False(t, mockT.Failed())
}

func TestHasHeader(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasHeader("some", "header")
	assert.False(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasHeader("some", "unknown")
	assert.True(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasHeader("unknown", "header")
	assert.True(t, mockT.Failed())
}

func TestHasCookie(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.False(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("some", "unknown")
	assert.True(t, mockT.Failed())

	mockT = new(testing.T)
	checker = makeTestChecker(mockT)
	checker.Test("GET", "/some")
	checker.Check()

	checker.HasCookie("unknown", "cookie")
	assert.True(t, mockT.Failed())
}

func TestHasJson(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/json")
	checker.Check()

	person := &testPerson{
		Name: "Some",
		Age:  30,
	}
	checker.HasJson(person)
	assert.False(t, mockT.Failed())

	person = &testPerson{
		Name: "Unknown",
		Age:  30,
	}
	checker.HasJson(person)
	assert.True(t, mockT.Failed())
}

func TestHasXml(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/xml")
	checker.Check()

	person := &testPerson{
		Name: "Some",
		Age:  30,
	}
	checker.HasXml(person)
	assert.False(t, mockT.Failed())

	person = &testPerson{
		Name: "Unknown",
		Age:  30,
	}
	checker.HasXml(person)
	assert.True(t, mockT.Failed())
}

func TestHasBody(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/byte")
	checker.Check()

	checker.HasBody([]byte("hello world"))
}

func TestCb(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.Test("GET", "/json")
	checker.Check()

	called := false
	checker.Cb(func(response *http.Response) {
		called = true
	})

	assert.True(t, called)
}

func TestCookies(t *testing.T) {
	mockT := new(testing.T)
	checker := makeTestChecker(mockT)
	checker.PersistCookie("some")

	checker.Test("GET", "/cookies")
	checker.Check()

	checker.HasCookie("some", "cookie")
	checker.HasCookie("other", "secondcookie")
	assert.False(t, mockT.Failed())

	checker.Test("GET", "/nothing")
	checker.Check()

	checker.HasCookie("some", "cookie")
	assert.False(t, mockT.Failed())

	checker.HasCookie("other", "secondcookie")
	assert.True(t, mockT.Failed())
}
<|endoftext|>


<commit_before>// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import "fmt"

const (
	// SemVer is the semantic version of Oragono.
	SemVer = "1.3.0-unreleased"
)

var (
	// Commit is the current git commit.
	Commit = ""

	// Ver is the full version of Oragono, used in responses to clients.
	Ver = fmt.Sprintf("oragono-%s", SemVer)

	// maxLastArgLength is used to simply cap off the final argument when creating general messages where we need to select a limit.
	// for instance, in MONITOR lists, RPL_ISUPPORT lists, etc.
	maxLastArgLength = 400
	// maxTargets is the maximum number of targets for PRIVMSG and NOTICE.
	maxTargets = 4
)
<commit_msg>bump version to 2.0.0-rc1<commit_after>// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import "fmt"

const (
	// SemVer is the semantic version of Oragono.
	SemVer = "2.0.0-rc1"
)

var (
	// Commit is the current git commit.
	Commit = ""

	// Ver is the full version of Oragono, used in responses to clients.
	Ver = fmt.Sprintf("oragono-%s", SemVer)

	// maxLastArgLength is used to simply cap off the final argument when creating general messages where we need to select a limit.
	// for instance, in MONITOR lists, RPL_ISUPPORT lists, etc.
	maxLastArgLength = 400
	// maxTargets is the maximum number of targets for PRIVMSG and NOTICE.
	maxTargets = 4
)
<|endoftext|>


<commit_before>package sup

/*
	The maintainence actor.

	Your controller strategy code is running in another goroutine.  This one
	is in charge of operations like collecting child status, and is
	purely internal so it can reliably handle its own blocking behavior.
*/
func (svr *Supervisor) supmgr_actor() {
	stepFn := svr.supmgr_stepAccepting
	for {
		if stepFn == nil {
			break
		}
		stepFn = stepFn()
	}
}

/*
	Steps in the state machine of the supervisor's internal maint.

	This pattern is awfully nice:
	  - you can see the transitions by clear name (returns highlight them)
	  - you *don't* see the visual clutter of code for transitions that are
	     not possible for whatever state you're currently looking at
	  - even if things really go poorly, your stack trace clearly indicates
	     exactly which state you were in (it's in the function name after all).
*/
type supmgr_step func() supmgr_step

func (svr *Supervisor) supmgr_stepAccepting() supmgr_step {
	select {
	case reqSpawn := <-svr.ctrlChan_spawn:
		ctrlr := newController()
		svr.wards[ctrlr] = ctrlr
		ctrlr.doneLatch.WaitSelectably(svr.childBellcord)
		go func() {
			defer ctrlr.doneLatch.Trigger()
			reqSpawn.fn(ctrlr)
		}()
		reqSpawn.ret <- ctrlr
		return svr.supmgr_stepAccepting

	case childDone := <-svr.childBellcord:
		delete(svr.wards, childDone.(*controller))
		return svr.supmgr_stepAccepting

	case <-svr.ctrlChan_winddown:
		if len(svr.wards) == 0 {
			return svr.supmgr_stepTerminated
		}
		return svr.supmgr_stepWinddown
	}
	panic("go-sup bug: missing transition")
}

func (svr *Supervisor) supmgr_stepWinddown() supmgr_step {
	select {
	case _ = <-svr.ctrlChan_spawn:
		panic("supervisor already winding down") // TODO return a witness with an insta error instead?
		return svr.supmgr_stepWinddown
	case childDone := <-svr.childBellcord:
		delete(svr.wards, childDone.(*controller))
		if len(svr.wards) == 0 {
			return svr.supmgr_stepTerminated
		}
		return svr.supmgr_stepWinddown
	case <-svr.ctrlChan_winddown:
		panic("go-sup bug, winddown transition cannot occur twice")
	}
	panic("go-sup bug: missing transition")
}

func (svr *Supervisor) supmgr_stepTerminated() supmgr_step {
	// can we finally stop selecting?
	// ideally other people shouldn've have *any* writable channels into us
	//  that they could possibly block on at this point.
	svr.latch_done.Trigger()
	return nil
}
<commit_msg>Move the check for transition to terminated phase to top of winddown (before any selects), which simplifies things enormously since anyone can now transition directly there without duplicating the already-termination-ready check.<commit_after>package sup

/*
	The maintainence actor.

	Your controller strategy code is running in another goroutine.  This one
	is in charge of operations like collecting child status, and is
	purely internal so it can reliably handle its own blocking behavior.
*/
func (svr *Supervisor) supmgr_actor() {
	stepFn := svr.supmgr_stepAccepting
	for {
		if stepFn == nil {
			break
		}
		stepFn = stepFn()
	}
}

/*
	Steps in the state machine of the supervisor's internal maint.

	This pattern is awfully nice:
	  - you can see the transitions by clear name (returns highlight them)
	  - you *don't* see the visual clutter of code for transitions that are
	     not possible for whatever state you're currently looking at
	  - even if things really go poorly, your stack trace clearly indicates
	     exactly which state you were in (it's in the function name after all).
*/
type supmgr_step func() supmgr_step

func (svr *Supervisor) supmgr_stepAccepting() supmgr_step {
	select {
	case reqSpawn := <-svr.ctrlChan_spawn:
		ctrlr := newController()
		svr.wards[ctrlr] = ctrlr
		ctrlr.doneLatch.WaitSelectably(svr.childBellcord)
		go func() {
			defer ctrlr.doneLatch.Trigger()
			reqSpawn.fn(ctrlr)
		}()
		reqSpawn.ret <- ctrlr
		return svr.supmgr_stepAccepting

	case childDone := <-svr.childBellcord:
		delete(svr.wards, childDone.(*controller))
		return svr.supmgr_stepAccepting

	case <-svr.ctrlChan_winddown:
		return svr.supmgr_stepWinddown
	}
	panic("go-sup bug: missing transition")
}

func (svr *Supervisor) supmgr_stepWinddown() supmgr_step {
	if len(svr.wards) == 0 {
		return svr.supmgr_stepTerminated
	}
	select {
	case _ = <-svr.ctrlChan_spawn:
		panic("supervisor already winding down") // TODO return a witness with an insta error instead?
		return svr.supmgr_stepWinddown
	case childDone := <-svr.childBellcord:
		delete(svr.wards, childDone.(*controller))
		return svr.supmgr_stepWinddown
	case <-svr.ctrlChan_winddown:
		panic("go-sup bug, winddown transition cannot occur twice")
	}
	panic("go-sup bug: missing transition")
}

func (svr *Supervisor) supmgr_stepTerminated() supmgr_step {
	// can we finally stop selecting?
	// ideally other people shouldn've have *any* writable channels into us
	//  that they could possibly block on at this point.
	svr.latch_done.Trigger()
	return nil
}
<|endoftext|>


<commit_before>package api

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

	"github.com/cagedtornado/centralconfig/datastores"
)

var (
	WsHub = NewHub()
)

func ShowUI(rw http.ResponseWriter, req *http.Request) {
	http.Redirect(rw, req, "/ui/", 301)
}

//	Gets a specfic config item based on application and config item name
func GetConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	response, err := ds.Get(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty item):
	configItem := datastores.ConfigItem{}
	if response.Name != "" {
		configItem = response
		sendDataResponse(rw, "Config item found", configItem)
		return
	}

	sendDataResponse(rw, "No config item found with that application and name", configItem)
}

//	Set a specific config item
func SetConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	response, err := ds.Set(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
	} else {
		WsHub.Broadcast <- []byte(getWSResponse("Updated", response))
		sendDataResponse(rw, "Config item updated", response)
	}
}

//	Removes a specific config item
func RemoveConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	err = ds.Remove(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
	} else {
		WsHub.Broadcast <- []byte(getWSResponse("Removed", request))
		sendDataResponse(rw, "Config item removed", request)
	}
}

//	Gets all config information for a given application
func GetAllConfigForApp(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := &datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	configItems, err := ds.GetAllForApplication(request.Application)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty array):
	if len(configItems) > 0 {
		sendDataResponse(rw, "Config items found", configItems)
		return
	}

	sendDataResponse(rw, "No config items found with that application", configItems)
}

//	Gets all config information
func GetAllConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	configItems, err := ds.GetAll()
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty array):
	if len(configItems) > 0 {
		sendDataResponse(rw, "Config items found", configItems)
		return
	}

	sendDataResponse(rw, "No config items found", configItems)
}

//	Gets all applications
func GetAllApplications(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	applications, err := ds.GetAllApplications()
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty array):
	if len(applications) > 0 {
		sendDataResponse(rw, "Applications found", applications)
		return
	}

	sendDataResponse(rw, "No config items found", applications)
}

//	Initializes a store
func InitStore(rw http.ResponseWriter, req *http.Request) {
	fmt.Fprintln(rw, "InitStore method")
}

//	Used to send back an error:
func sendErrorResponse(rw http.ResponseWriter, err error, code int) {
	//	Our return value
	response := datastores.ConfigResponse{
		Status:  code,
		Message: "Error: " + err.Error()}

	//	Serialize to JSON & return the response:
	rw.WriteHeader(code)
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	json.NewEncoder(rw).Encode(response)
}

//	Used to send back a response with data
func sendDataResponse(rw http.ResponseWriter, message string, dataItems interface{}) {
	//	Our return value
	response := datastores.ConfigResponse{
		Status:  http.StatusOK,
		Message: message,
		Data:    dataItems}

	//	Serialize to JSON & return the response:
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	json.NewEncoder(rw).Encode(response)
}

//	Gets a JSON formatted WebSocket event response
func getWSResponse(messageType string, item datastores.ConfigItem) string {
	//	Our default return value:
	retval := ""

	//	Our WebSocket return value
	response := datastores.WebSocketResponse{
		Data: item,
		Type: messageType}

	//	Serialize to JSON and return as a string:
	responseBytes := new(bytes.Buffer)
	if err := json.NewEncoder(responseBytes).Encode(&response); err == nil {
		retval = responseBytes.String()
	}

	return retval
}
<commit_msg>Got a bit too aggresive with the refactoring... added back some pointers required for JSON deserialization<commit_after>package api

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

	"github.com/cagedtornado/centralconfig/datastores"
)

var (
	WsHub = NewHub()
)

func ShowUI(rw http.ResponseWriter, req *http.Request) {
	http.Redirect(rw, req, "/ui/", 301)
}

//	Gets a specfic config item based on application and config item name
func GetConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(&request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	response, err := ds.Get(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty item):
	configItem := datastores.ConfigItem{}
	if response.Name != "" {
		configItem = response
		sendDataResponse(rw, "Config item found", configItem)
		return
	}

	sendDataResponse(rw, "No config item found with that application and name", configItem)
}

//	Set a specific config item
func SetConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(&request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	response, err := ds.Set(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
	} else {
		WsHub.Broadcast <- []byte(getWSResponse("Updated", response))
		sendDataResponse(rw, "Config item updated", response)
	}
}

//	Removes a specific config item
func RemoveConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(&request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	err = ds.Remove(request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
	} else {
		WsHub.Broadcast <- []byte(getWSResponse("Removed", request))
		sendDataResponse(rw, "Config item removed", request)
	}
}

//	Gets all config information for a given application
func GetAllConfigForApp(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Decode the request:
	request := &datastores.ConfigItem{}
	err := json.NewDecoder(req.Body).Decode(&request)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusBadRequest)
		return
	}

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	configItems, err := ds.GetAllForApplication(request.Application)
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty array):
	if len(configItems) > 0 {
		sendDataResponse(rw, "Config items found", configItems)
		return
	}

	sendDataResponse(rw, "No config items found with that application", configItems)
}

//	Gets all config information
func GetAllConfig(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	configItems, err := ds.GetAll()
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty array):
	if len(configItems) > 0 {
		sendDataResponse(rw, "Config items found", configItems)
		return
	}

	sendDataResponse(rw, "No config items found", configItems)
}

//	Gets all applications
func GetAllApplications(rw http.ResponseWriter, req *http.Request) {
	//	req.Body is a ReadCloser -- we need to remember to close it:
	defer req.Body.Close()

	//	Get the current datastore:
	ds := datastores.GetConfigDatastore()

	//	Send the request to the datastore and get a response:
	applications, err := ds.GetAllApplications()
	if err != nil {
		sendErrorResponse(rw, err, http.StatusInternalServerError)
		return
	}

	//	If we found an item, return it (otherwise, return an empty array):
	if len(applications) > 0 {
		sendDataResponse(rw, "Applications found", applications)
		return
	}

	sendDataResponse(rw, "No config items found", applications)
}

//	Initializes a store
func InitStore(rw http.ResponseWriter, req *http.Request) {
	fmt.Fprintln(rw, "InitStore method")
}

//	Used to send back an error:
func sendErrorResponse(rw http.ResponseWriter, err error, code int) {
	//	Our return value
	response := datastores.ConfigResponse{
		Status:  code,
		Message: "Error: " + err.Error()}

	//	Serialize to JSON & return the response:
	rw.WriteHeader(code)
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	json.NewEncoder(rw).Encode(response)
}

//	Used to send back a response with data
func sendDataResponse(rw http.ResponseWriter, message string, dataItems interface{}) {
	//	Our return value
	response := datastores.ConfigResponse{
		Status:  http.StatusOK,
		Message: message,
		Data:    dataItems}

	//	Serialize to JSON & return the response:
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	json.NewEncoder(rw).Encode(response)
}

//	Gets a JSON formatted WebSocket event response
func getWSResponse(messageType string, item datastores.ConfigItem) string {
	//	Our default return value:
	retval := ""

	//	Our WebSocket return value
	response := datastores.WebSocketResponse{
		Data: item,
		Type: messageType}

	//	Serialize to JSON and return as a string:
	responseBytes := new(bytes.Buffer)
	if err := json.NewEncoder(responseBytes).Encode(&response); err == nil {
		retval = responseBytes.String()
	}

	return retval
}
<|endoftext|>


<commit_before>package api

import (
	"gopkg.in/kataras/iris.v6"
	"gopkg.in/kataras/iris.v6/adaptors/httprouter"
	"os"
	"gifjam/gifGrabber"
	"net/http"
	"log"
	"gopkg.in/kataras/iris.v6/adaptors/cors"
	"strconv"
	"io"
)

type obj map[string]interface{}

func StartApiServer() {
	app := iris.New()
	app.Adapt(iris.DevLogger(), httprouter.New(), cors.New(cors.Options{AllowedOrigins: []string{"*"}}))

	app.Post("/gif/:id", serveGif)
	app.Post("/gifs/visibility/:id/:visible", gifVisibility)
	app.Post("/gifs", GetGifs)

	// Setting address from ENV
	app.Listen(os.Getenv("GIFJAM_SERVER_HOST"))
}

func GetGifs(ctx *iris.Context) {
	offset, err := ctx.URLParamInt("offset")
	if err != nil {
		offset = 0
	}

	limit, err := ctx.URLParamInt("offset")
	if err != nil {
		limit = 10
	}

	images, err := gifGrabber.GetItems(offset, limit)
	if err != nil {
		log.Println("Unable to get images from database -> ", err.Error())
		ctx.JSON(http.StatusOK, obj{"error": "Unable to get images!"})
		return
	}

	ctx.JSON(http.StatusOK, obj{"images": images})
}

func serveGif(ctx *iris.Context) {
	file_id := ctx.Param("id")
	if len(file_id) != 24 {
		ctx.JSON(http.StatusNotFound, obj{"error": "Image Not Found!"})
		return
	}

	length, r, err := gifGrabber.GetFileIO(file_id)
	if err != nil {
		log.Println("Unable to get image from database -> ", err.Error())
		ctx.JSON(http.StatusInternalServerError, obj{"error": "Unable to read Image file"})
		return
	}

	defer r.Close()

	ctx.SetHeader("Content-Length", strconv.FormatInt(length, 10))
	ctx.SetHeader("Content-Type", "image/gif")
	io.Copy(ctx, r)
}

func gifVisibility(ctx *iris.Context) {
	file_id := ctx.Param("id")
	if len(file_id) != 24 {
		ctx.JSON(http.StatusNotFound, obj{"error": "Image Not Found!"})
		return
	}

	visible := false

	visible_param := ctx.Param("visible")
	if visible_param == "1" {
		visible = true
	}

	err := gifGrabber.SetVisibility(file_id, visible)
	if err != nil {
		log.Println("Unable to set image to visible -> ", err.Error())
		ctx.JSON(http.StatusInternalServerError, obj{"error": "Unable to set image to visible"})
		return
	}

	ctx.JSON(http.StatusOK, obj{})
}<commit_msg>gif download using get request<commit_after>package api

import (
	"gopkg.in/kataras/iris.v6"
	"gopkg.in/kataras/iris.v6/adaptors/httprouter"
	"os"
	"gifjam/gifGrabber"
	"net/http"
	"log"
	"gopkg.in/kataras/iris.v6/adaptors/cors"
	"strconv"
	"io"
)

type obj map[string]interface{}

func StartApiServer() {
	app := iris.New()
	app.Adapt(iris.DevLogger(), httprouter.New(), cors.New(cors.Options{AllowedOrigins: []string{"*"}}))

	app.Get("/gif/:id", serveGif)
	app.Post("/gifs/visibility/:id/:visible", gifVisibility)
	app.Post("/gifs", GetGifs)

	// Setting address from ENV
	app.Listen(os.Getenv("GIFJAM_SERVER_HOST"))
}

func GetGifs(ctx *iris.Context) {
	offset, err := ctx.URLParamInt("offset")
	if err != nil {
		offset = 0
	}

	limit, err := ctx.URLParamInt("offset")
	if err != nil {
		limit = 10
	}

	images, err := gifGrabber.GetItems(offset, limit)
	if err != nil {
		log.Println("Unable to get images from database -> ", err.Error())
		ctx.JSON(http.StatusOK, obj{"error": "Unable to get images!"})
		return
	}

	ctx.JSON(http.StatusOK, obj{"images": images})
}

func serveGif(ctx *iris.Context) {
	file_id := ctx.Param("id")
	if len(file_id) != 24 {
		ctx.JSON(http.StatusNotFound, obj{"error": "Image Not Found!"})
		return
	}

	length, r, err := gifGrabber.GetFileIO(file_id)
	if err != nil {
		log.Println("Unable to get image from database -> ", err.Error())
		ctx.JSON(http.StatusInternalServerError, obj{"error": "Unable to read Image file"})
		return
	}

	defer r.Close()

	ctx.SetHeader("Content-Length", strconv.FormatInt(length, 10))
	ctx.SetHeader("Content-Type", "image/gif")
	io.Copy(ctx, r)
}

func gifVisibility(ctx *iris.Context) {
	file_id := ctx.Param("id")
	if len(file_id) != 24 {
		ctx.JSON(http.StatusNotFound, obj{"error": "Image Not Found!"})
		return
	}

	visible := false

	visible_param := ctx.Param("visible")
	if visible_param == "1" {
		visible = true
	}

	err := gifGrabber.SetVisibility(file_id, visible)
	if err != nil {
		log.Println("Unable to set image to visible -> ", err.Error())
		ctx.JSON(http.StatusInternalServerError, obj{"error": "Unable to set image to visible"})
		return
	}

	ctx.JSON(http.StatusOK, obj{})
}<|endoftext|>


<commit_before>package main

import (
	"os"
	"os/signal"
	"syscall"
)

func sigintCleanup() int {
	// TODO: close the database connection and do other cleanup jobs
	return 0
}

func sigintCleanupSetup() error {
	logger.Infof("setting up SIGINT cleanup")

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGINT)
	go func() {
		<-c
		os.Exit(sigintCleanup())
	}()

	return nil
}
<commit_msg>sigint.go: close DB connection before exit<commit_after>package main

import (
	"os"
	"os/signal"
	"syscall"
)

func sigintCleanup() int {
	if db != nil {
		err := db.Close()
		if err == nil {
			logger.Errorf("cannot close database connection: %v", err)
			return 1
		}
	}

	return 0
}

func sigintCleanupSetup() error {
	logger.Infof("setting up SIGINT cleanup")

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGINT)
	go func() {
		<-c
		os.Exit(sigintCleanup())
	}()

	return nil
}
<|endoftext|>


<commit_before>package context

import (
	"github.com/WE-Development/mosel/commons"
	"time"
	"github.com/WE-Development/mosel/api"
	"database/sql"
	"fmt"
	"log"
)

type table struct {
	name        string
	createQuery string
}

type result map[string]map[string]map[string]string

type dataPersistence interface {
	Init() error
	Add(node string, t time.Time, info api.NodeInfo)
	GetAll() (result, error)
}

type sqlDataPersistence struct {
	db *sql.DB
	q  commons.SqlQueries

	dbState map[string]map[string][]string
}

func NewSqlDataPersistence(db *sql.DB, queries commons.SqlQueries) dataPersistence {
	return sqlDataPersistence{
		db:db,
		q:queries,
	}
}

func (pers sqlDataPersistence) query(name string, args ...interface{}) (*sql.Rows, error) {
	query, exists := pers.q[name]

	if !exists {
		return nil, fmt.Errorf("Quers %s is not registered", name)
	}

	return pers.db.Query(query, args...)
}

func (pers sqlDataPersistence) queryResultNotEmpty(name string, args ...interface{}) (bool, error) {
	rows, err := pers.query(name, args...)

	if err != nil {
		return false, err
	}

	return !rows.Next(), nil
}

func (pers sqlDataPersistence) Init() error {
	tables := make([]table, 4)
	tables[0] = table{name:"Nodes", createQuery:"createNodes", }
	tables[1] = table{name:"Diagrams", createQuery:"createDiagrams", }
	tables[2] = table{name:"Graphs", createQuery:"createGraphs", }
	tables[3] = table{name:"DataPoints", createQuery:"createDataPoints", }

	for _, table := range tables {
		if exists, err := pers.tableExists(table.name); err != nil {
			return err
		} else if !exists {
			log.Printf("Create table %s ", table)
			_, err := pers.query(table.createQuery)

			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (pers sqlDataPersistence) tableExists(name string) (bool, error) {
	//todo be clever bout this
	rows, err := pers.db.Query(pers.q["tableExists"] + " '" + name + "'")
	defer rows.Close()

	if err != nil {
		return false, err
	}

	return rows.Next(), nil
}

func (pers sqlDataPersistence) Add(node string, t time.Time, info api.NodeInfo) {
	if empty, err := pers.queryResultNotEmpty("nodeByName", node); err != nil {
		log.Println(err)
	} else if empty {
		pers.query("insertNode", node, "")
	}
	/*
		if empty, err := pers.queryResultNotEmpty("diagramByName", node); err != nil {
			log.Println(err)
		} else if empty {
			pers.query("insertDiagram", node, "")
		}

		if empty, err := pers.queryResultNotEmpty("nodeByName", node); err != nil {
			log.Println(err)
		} else if empty {
			pers.query("insertNode", node, "")
		}*/
}

func (pers sqlDataPersistence) GetAll() (result, error) {
	res := make(result)
	rows, err := pers.query("all")
	defer rows.Close()

	if err != nil {
		return nil, err
	}

	for rows.Next() {
		var value string
		var timestamp []uint8
		var graph string
		var diagram string
		var node string
		var url string
		err := rows.Scan(&value, &timestamp, &graph, &diagram, &node, &url)

		if err != nil {
			return nil, err
		}

		log.Println(value, timestamp, graph, diagram, node, url)
	}

	return res, nil
}<commit_msg>update dbState internally<commit_after>package context

import (
	"github.com/WE-Development/mosel/commons"
	"time"
	"github.com/WE-Development/mosel/api"
	"database/sql"
	"fmt"
	"log"
)

type table struct {
	name        string
	createQuery string
}

type result map[string]map[string]map[string]string

type dbState map[string]map[string][]string

type dbResult struct {
	value     string
	timestamp []uint8
	graph     string
	diagram   string
	node      string
	url       string
}

type dataPersistence interface {
	Init() error
	Add(node string, t time.Time, info api.NodeInfo)
	GetAll() (result, error)
}

type sqlDataPersistence struct {
	db      *sql.DB
	q       commons.SqlQueries

	dbState dbState
}

func NewSqlDataPersistence(db *sql.DB, queries commons.SqlQueries) dataPersistence {
	return sqlDataPersistence{
		db:db,
		q:queries,
	}
}

func (pers sqlDataPersistence) query(name string, args ...interface{}) (*sql.Rows, error) {
	query, exists := pers.q[name]

	if !exists {
		return nil, fmt.Errorf("Quers %s is not registered", name)
	}

	return pers.db.Query(query, args...)
}

func (pers sqlDataPersistence) queryResultNotEmpty(name string, args ...interface{}) (bool, error) {
	rows, err := pers.query(name, args...)
	defer rows.Close()

	if err != nil {
		return false, err
	}

	return !rows.Next(), nil
}

func (pers sqlDataPersistence) tableExists(name string) (bool, error) {
	//todo be clever bout this
	rows, err := pers.db.Query(pers.q["tableExists"] + " '" + name + "'")
	defer rows.Close()
	if err != nil {
		return false, err
	}

	return rows.Next(), nil
}

func (pers sqlDataPersistence) Init() error {
	tables := make([]table, 4)
	tables[0] = table{name:"Nodes", createQuery:"createNodes", }
	tables[1] = table{name:"Diagrams", createQuery:"createDiagrams", }
	tables[2] = table{name:"Graphs", createQuery:"createGraphs", }
	tables[3] = table{name:"DataPoints", createQuery:"createDataPoints", }

	for _, table := range tables {
		if exists, err := pers.tableExists(table.name); err != nil {
			return err
		} else if !exists {
			log.Printf("Create table %s ", table)
			_, err := pers.query(table.createQuery)

			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (pers sqlDataPersistence) Add(node string, t time.Time, info api.NodeInfo) {

}

func (pers sqlDataPersistence) GetAll() (result, error) {
	res := make(result)
	rows, err := pers.query("all")
	defer rows.Close()

	if err != nil {
		return nil, err
	}

	for rows.Next() {
		var dbRes dbResult
		err := rows.Scan(&dbRes.value, &dbRes.timestamp, &dbRes.graph, &dbRes.diagram, &dbRes.node, &dbRes.url)

		if err != nil {
			return nil, err
		}
		pers.updateDbState(dbRes)
	}
	log.Println(pers.dbState)

	return res, nil
}

func (pers *sqlDataPersistence) updateDbState(dbRes dbResult) {
	//log.Println(value, timestamp, graph, diagram, node, url)
	if dbRes.node == "" {
		return
	}

	if pers.dbState == nil {
		pers.dbState = make(dbState)
	}

	diagrams, ok := pers.dbState[dbRes.node]
	if !ok {
		diagrams = make(map[string][]string)
		pers.dbState[dbRes.node] = diagrams
	}

	if dbRes.diagram == "" {
		return
	}

	graphs, ok := pers.dbState[dbRes.node][dbRes.diagram]
	if !ok {
		graphs = make([]string, 0)
		pers.dbState[dbRes.node][dbRes.diagram] = graphs
	}

	if dbRes.graph == "" {
		return
	}

	graphs = append(graphs, dbRes.graph)
}<|endoftext|>