<commit_before>// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2010 Google Inc.  All rights reserved.
// http://code.google.com/p/goprotobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package proto

/*
 * Types and routines for supporting protocol buffer extensions.
 */

import (
	"errors"
	"reflect"
	"strconv"
)

// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
var ErrMissingExtension = errors.New("proto: missing extension")

// ExtensionRange represents a range of message extensions for a protocol buffer.
// Used in code generated by the protocol compiler.
type ExtensionRange struct {
	Start, End int32 // both inclusive
}

// extendableProto is an interface implemented by any protocol buffer that may be extended.
type extendableProto interface {
	Message
	ExtensionRangeArray() []ExtensionRange
	ExtensionMap() map[int32]Extension
}

// ExtensionDesc represents an extension specification.
// Used in generated code from the protocol compiler.
type ExtensionDesc struct {
	ExtendedType  Message     // nil pointer to the type that is being extended
	ExtensionType interface{} // nil pointer to the extension type
	Field         int32       // field number
	Name          string      // fully-qualified name of extension, for text formatting
	Tag           string      // protobuf tag style
}

func (ed *ExtensionDesc) repeated() bool {
	t := reflect.TypeOf(ed.ExtensionType)
	return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
}

/*
Extension represents an extension in a message.

When an extension is stored in a message using SetExtension
only desc and value are set. When the message is marshaled
enc will be set to the encoded form of the message.

When a message is unmarshaled and contains extensions, each
extension will have only enc set. When such an extension is
accessed using GetExtension (or GetExtensions) desc and value
will be set.
*/
type Extension struct {
	desc  *ExtensionDesc
	value interface{}
	enc   []byte
}

// SetRawExtension is for testing only.
func SetRawExtension(base extendableProto, id int32, b []byte) {
	base.ExtensionMap()[id] = Extension{enc: b}
}

// isExtensionField returns true iff the given field number is in an extension range.
func isExtensionField(pb extendableProto, field int32) bool {
	for _, er := range pb.ExtensionRangeArray() {
		if er.Start <= field && field <= er.End {
			return true
		}
	}
	return false
}

// checkExtensionTypes checks that the given extension is valid for pb.
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
	// Check the extended type.
	if a, b := reflect.TypeOf(pb), reflect.TypeOf(extension.ExtendedType); a != b {
		return errors.New("bad extended type; " + b.String() + " does not extend " + a.String())
	}
	// Check the range.
	if !isExtensionField(pb, extension.Field) {
		return errors.New("bad extension number; not in declared ranges")
	}
	return nil
}

// encodeExtensionMap encodes any unmarshaled (unencoded) extensions in m.
func encodeExtensionMap(m map[int32]Extension) error {
	for k, e := range m {
		if e.value == nil || e.desc == nil {
			// Extension is only in its encoded form.
			continue
		}

		// We don't skip extensions that have an encoded form set,
		// because the extension value may have been mutated after
		// the last time this function was called.

		et := reflect.TypeOf(e.desc.ExtensionType)
		props := new(Properties)
		props.Init(et, "unknown_name", e.desc.Tag, 0)

		p := NewBuffer(nil)
		// The encoder must be passed a pointer to e.value.
		// Allocate a copy of value so that we can use its address.
		x := reflect.New(et)
		x.Elem().Set(reflect.ValueOf(e.value))
		if err := props.enc(p, props, x.Pointer()); err != nil {
			return err
		}
		e.enc = p.buf
		m[k] = e
	}
	return nil
}

// HasExtension returns whether the given extension is present in pb.
func HasExtension(pb extendableProto, extension *ExtensionDesc) bool {
	// TODO: Check types, field numbers, etc.?
	_, ok := pb.ExtensionMap()[extension.Field]
	return ok
}

// ClearExtension removes the given extension from pb.
func ClearExtension(pb extendableProto, extension *ExtensionDesc) {
	// TODO: Check types, field numbers, etc.?
	delete(pb.ExtensionMap(), extension.Field)
}

// GetExtension parses and returns the given extension of pb.
// If the extension is not present it returns ErrMissingExtension.
func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) {
	if err := checkExtensionTypes(pb, extension); err != nil {
		return nil, err
	}

	e, ok := pb.ExtensionMap()[extension.Field]
	if !ok {
		return nil, ErrMissingExtension
	}
	if e.value != nil {
		// Already decoded. Check the descriptor, though.
		if e.desc != extension {
			// This shouldn't happen. If it does, it means that
			// GetExtension was called twice with two different
			// descriptors with the same field number.
			return nil, errors.New("proto: descriptor conflict")
		}
		return e.value, nil
	}

	v, err := decodeExtension(e.enc, extension)
	if err != nil {
		return nil, err
	}

	// Remember the decoded version and drop the encoded version.
	// That way it is safe to mutate what we return.
	e.value = v
	e.desc = extension
	e.enc = nil
	return e.value, nil
}

// decodeExtension decodes an extension encoded in b.
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
	o := NewBuffer(b)

	t := reflect.TypeOf(extension.ExtensionType)
	rep := extension.repeated()

	props := &Properties{}
	props.Init(t, "irrelevant_name", extension.Tag, 0)

	// t is a pointer to a struct, pointer to basic type or a slice.
	// Allocate a "field" to store the pointer/slice itself; the
	// pointer/slice will be stored here. We pass
	// the address of this field to props.dec.
	value := reflect.New(t).Elem()

	for {
		// Discard wire type and field number varint. It isn't needed.
		if _, err := o.DecodeVarint(); err != nil {
			return nil, err
		}

		if err := props.dec(o, props, value.UnsafeAddr()); err != nil {
			return nil, err
		}

		if !rep || o.index >= len(o.buf) {
			break
		}
	}
	return value.Interface(), nil
}

// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
// The returned slice has the same length as es; missing extensions will appear as nil elements.
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
	epb, ok := pb.(extendableProto)
	if !ok {
		err = errors.New("not an extendable proto")
		return
	}
	extensions = make([]interface{}, len(es))
	for i, e := range es {
		extensions[i], err = GetExtension(epb, e)
		if err != nil {
			return
		}
	}
	return
}

// SetExtension sets the specified extension of pb to the specified value.
func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error {
	if err := checkExtensionTypes(pb, extension); err != nil {
		return err
	}
	typ := reflect.TypeOf(extension.ExtensionType)
	if typ != reflect.TypeOf(value) {
		return errors.New("bad extension value type")
	}

	pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value}
	return nil
}

// A global registry of extensions.
// The generated code will register the generated descriptors by calling RegisterExtension.

var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)

// RegisterExtension is called from the generated code.
func RegisterExtension(desc *ExtensionDesc) {
	st := reflect.TypeOf(desc.ExtendedType).Elem()
	m := extensionMaps[st]
	if m == nil {
		m = make(map[int32]*ExtensionDesc)
		extensionMaps[st] = m
	}
	if _, ok := m[desc.Field]; ok {
		panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
	}
	m[desc.Field] = desc
}

// RegisteredExtensions returns a map of the registered extensions of a
// protocol buffer struct, indexed by the extension number.
// The argument pb should be a nil pointer to the struct type.
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
	return extensionMaps[reflect.TypeOf(pb).Elem()]
}
<commit_msg>goprotobuf: Doc updates for extensions.<commit_after>// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2010 Google Inc.  All rights reserved.
// http://code.google.com/p/goprotobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package proto

/*
 * Types and routines for supporting protocol buffer extensions.
 */

import (
	"errors"
	"reflect"
	"strconv"
)

// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
var ErrMissingExtension = errors.New("proto: missing extension")

// ExtensionRange represents a range of message extensions for a protocol buffer.
// Used in code generated by the protocol compiler.
type ExtensionRange struct {
	Start, End int32 // both inclusive
}

// extendableProto is an interface implemented by any protocol buffer that may be extended.
type extendableProto interface {
	Message
	ExtensionRangeArray() []ExtensionRange
	ExtensionMap() map[int32]Extension
}

// ExtensionDesc represents an extension specification.
// Used in generated code from the protocol compiler.
type ExtensionDesc struct {
	ExtendedType  Message     // nil pointer to the type that is being extended
	ExtensionType interface{} // nil pointer to the extension type
	Field         int32       // field number
	Name          string      // fully-qualified name of extension, for text formatting
	Tag           string      // protobuf tag style
}

func (ed *ExtensionDesc) repeated() bool {
	t := reflect.TypeOf(ed.ExtensionType)
	return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
}

// Extension represents an extension in a message.
type Extension struct {
	// When an extension is stored in a message using SetExtension
	// only desc and value are set. When the message is marshaled
	// enc will be set to the encoded form of the message.
	//
	// When a message is unmarshaled and contains extensions, each
	// extension will have only enc set. When such an extension is
	// accessed using GetExtension (or GetExtensions) desc and value
	// will be set.
	desc  *ExtensionDesc
	value interface{}
	enc   []byte
}

// SetRawExtension is for testing only.
func SetRawExtension(base extendableProto, id int32, b []byte) {
	base.ExtensionMap()[id] = Extension{enc: b}
}

// isExtensionField returns true iff the given field number is in an extension range.
func isExtensionField(pb extendableProto, field int32) bool {
	for _, er := range pb.ExtensionRangeArray() {
		if er.Start <= field && field <= er.End {
			return true
		}
	}
	return false
}

// checkExtensionTypes checks that the given extension is valid for pb.
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
	// Check the extended type.
	if a, b := reflect.TypeOf(pb), reflect.TypeOf(extension.ExtendedType); a != b {
		return errors.New("bad extended type; " + b.String() + " does not extend " + a.String())
	}
	// Check the range.
	if !isExtensionField(pb, extension.Field) {
		return errors.New("bad extension number; not in declared ranges")
	}
	return nil
}

// encodeExtensionMap encodes any unmarshaled (unencoded) extensions in m.
func encodeExtensionMap(m map[int32]Extension) error {
	for k, e := range m {
		if e.value == nil || e.desc == nil {
			// Extension is only in its encoded form.
			continue
		}

		// We don't skip extensions that have an encoded form set,
		// because the extension value may have been mutated after
		// the last time this function was called.

		et := reflect.TypeOf(e.desc.ExtensionType)
		props := new(Properties)
		props.Init(et, "unknown_name", e.desc.Tag, 0)

		p := NewBuffer(nil)
		// The encoder must be passed a pointer to e.value.
		// Allocate a copy of value so that we can use its address.
		x := reflect.New(et)
		x.Elem().Set(reflect.ValueOf(e.value))
		if err := props.enc(p, props, x.Pointer()); err != nil {
			return err
		}
		e.enc = p.buf
		m[k] = e
	}
	return nil
}

// HasExtension returns whether the given extension is present in pb.
func HasExtension(pb extendableProto, extension *ExtensionDesc) bool {
	// TODO: Check types, field numbers, etc.?
	_, ok := pb.ExtensionMap()[extension.Field]
	return ok
}

// ClearExtension removes the given extension from pb.
func ClearExtension(pb extendableProto, extension *ExtensionDesc) {
	// TODO: Check types, field numbers, etc.?
	delete(pb.ExtensionMap(), extension.Field)
}

// GetExtension parses and returns the given extension of pb.
// If the extension is not present it returns ErrMissingExtension.
// If the returned extension is modified, SetExtension must be called
// for the modifications to be reflected in pb.
func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) {
	if err := checkExtensionTypes(pb, extension); err != nil {
		return nil, err
	}

	e, ok := pb.ExtensionMap()[extension.Field]
	if !ok {
		return nil, ErrMissingExtension
	}
	if e.value != nil {
		// Already decoded. Check the descriptor, though.
		if e.desc != extension {
			// This shouldn't happen. If it does, it means that
			// GetExtension was called twice with two different
			// descriptors with the same field number.
			return nil, errors.New("proto: descriptor conflict")
		}
		return e.value, nil
	}

	v, err := decodeExtension(e.enc, extension)
	if err != nil {
		return nil, err
	}

	// Remember the decoded version and drop the encoded version.
	// That way it is safe to mutate what we return.
	e.value = v
	e.desc = extension
	e.enc = nil
	return e.value, nil
}

// decodeExtension decodes an extension encoded in b.
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
	o := NewBuffer(b)

	t := reflect.TypeOf(extension.ExtensionType)
	rep := extension.repeated()

	props := &Properties{}
	props.Init(t, "irrelevant_name", extension.Tag, 0)

	// t is a pointer to a struct, pointer to basic type or a slice.
	// Allocate a "field" to store the pointer/slice itself; the
	// pointer/slice will be stored here. We pass
	// the address of this field to props.dec.
	value := reflect.New(t).Elem()

	for {
		// Discard wire type and field number varint. It isn't needed.
		if _, err := o.DecodeVarint(); err != nil {
			return nil, err
		}

		if err := props.dec(o, props, value.UnsafeAddr()); err != nil {
			return nil, err
		}

		if !rep || o.index >= len(o.buf) {
			break
		}
	}
	return value.Interface(), nil
}

// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
// The returned slice has the same length as es; missing extensions will appear as nil elements.
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
	epb, ok := pb.(extendableProto)
	if !ok {
		err = errors.New("not an extendable proto")
		return
	}
	extensions = make([]interface{}, len(es))
	for i, e := range es {
		extensions[i], err = GetExtension(epb, e)
		if err != nil {
			return
		}
	}
	return
}

// SetExtension sets the specified extension of pb to the specified value.
func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error {
	if err := checkExtensionTypes(pb, extension); err != nil {
		return err
	}
	typ := reflect.TypeOf(extension.ExtensionType)
	if typ != reflect.TypeOf(value) {
		return errors.New("bad extension value type")
	}

	pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value}
	return nil
}

// A global registry of extensions.
// The generated code will register the generated descriptors by calling RegisterExtension.

var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)

// RegisterExtension is called from the generated code.
func RegisterExtension(desc *ExtensionDesc) {
	st := reflect.TypeOf(desc.ExtendedType).Elem()
	m := extensionMaps[st]
	if m == nil {
		m = make(map[int32]*ExtensionDesc)
		extensionMaps[st] = m
	}
	if _, ok := m[desc.Field]; ok {
		panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
	}
	m[desc.Field] = desc
}

// RegisteredExtensions returns a map of the registered extensions of a
// protocol buffer struct, indexed by the extension number.
// The argument pb should be a nil pointer to the struct type.
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
	return extensionMaps[reflect.TypeOf(pb).Elem()]
}
<|endoftext|>


<commit_before>package libstorage

import (
	"errors"
	"fmt"

	"github.com/codedellemc/gocsi/csi"
	"github.com/codedellemc/gocsi/mount"

	apitypes "github.com/codedellemc/rexray/libstorage/api/types"
	apiutils "github.com/codedellemc/rexray/libstorage/api/utils"
)

var errMissingIDKeyPath = errors.New("missing id key path")

const resNotFound = "resource not found"

func isNotFoundErr(err error) bool {
	return err.Error() == resNotFound
}

// GetVolumeName should return the name of the volume specified
// by the provided volume ID. If the volume does not exist then
// an empty string should be returned.
func (d *driver) GetVolumeName(id *csi.VolumeID) (string, error) {
	idVal, ok := id.Values["id"]
	if !ok {
		return "", errMissingIDKeyPath
	}

	opts := &apitypes.VolumeInspectOpts{
		Opts: apiutils.NewStore(),
	}

	vol, err := d.client.Storage().VolumeInspect(d.ctx, idVal, opts)
	if err != nil {

		// If the volume is not found then return an empty string
		// for the name to indicate such.
		if isNotFoundErr(err) {
			return "", nil
		}

		return "", err
	}

	return vol.Name, nil
}

// GetVolumeInfo should return information about the volume
// specified by the provided volume name. If the volume does not
// exist then a nil value should be returned.
func (d *driver) GetVolumeInfo(name string) (*csi.VolumeInfo, error) {
	td, ok := d.client.Storage().(apitypes.StorageDriverVolInspectByName)
	if !ok {
		return nil, fmt.Errorf(
			"stor driver not by name: %T", d.client.Storage())
	}

	opts := &apitypes.VolumeInspectOpts{
		Opts: apiutils.NewStore(),
	}

	vol, err := td.VolumeInspectByName(d.ctx, name, opts)
	if err != nil {

		// If the volume is not found then return nil for the
		// volume info to indicate such.
		if isNotFoundErr(err) {
			return nil, nil
		}

		return nil, err
	}

	return toVolumeInfo(vol), nil
}

// IsControllerPublished should return publication info about
// the volume specified by the provided volume name or ID.
func (d *driver) IsControllerPublished(
	id *csi.VolumeID) (*csi.PublishVolumeInfo, error) {

	idVal, ok := id.Values["id"]
	if !ok {
		return nil, errMissingIDKeyPath
	}

	// Request only volumes that are attached.
	opts := &apitypes.VolumeInspectOpts{
		Attachments: apitypes.VolAttReqForInstance,
		Opts:        apiutils.NewStore(),
	}

	vol, err := d.client.Storage().VolumeInspect(d.ctx, idVal, opts)
	if err != nil {
		return nil, err
	}

	// If the volume is not attached to this node then do not
	// indicate an error; just return nil to indicate
	// the volume is not attached to this node.
	if vol.AttachmentState != apitypes.VolumeAttached {
		return nil, nil
	}

	d.attTokensRWL.RLock()
	defer d.attTokensRWL.RUnlock()

	return &csi.PublishVolumeInfo{
		Values: map[string]string{
			"token":     d.attTokens[idVal],
			"encrypted": fmt.Sprintf("%v", vol.Encrypted),
		},
	}, nil
}

// IsNodePublished should return a flag indicating whether or
// not the volume exists and is published on the current host.
func (d *driver) IsNodePublished(
	id *csi.VolumeID,
	pubInfo *csi.PublishVolumeInfo,
	targetPath string) (bool, error) {

	idVal, ok := id.Values["id"]
	if !ok {
		return false, errMissingIDKeyPath
	}

	// Request only volumes attached to this instance.
	opts := &apitypes.VolumeInspectOpts{
		Attachments: apitypes.VolAttReqWithDevMapForInstance,
		Opts:        apiutils.NewStore(),
	}

	vol, err := d.client.Storage().VolumeInspect(d.ctx, idVal, opts)
	if err != nil {
		return false, err
	}

	// If the volume is not attached to this node then do not
	// indicate an error; just return false to indicate
	// the volume is not attached to this node.
	if vol.AttachmentState != apitypes.VolumeAttached {
		return false, nil
	}

	// If the volume has no attachments then it's not possible to
	// determine the node publication status.
	if len(vol.Attachments) == 0 {
		return false, nil
	}

	// Get the local mount table.
	minfo, err := mount.GetMounts()
	if err != nil {
		return false, err
	}

	// Scan the mount table and get the path to which the device of
	// the attached volume is mounted.
	var (
		volMountPath string
		devPath      = vol.Attachments[0].DeviceName
	)
	for _, mi := range minfo {
		if mi.Device == devPath {
			volMountPath = mi.Path
			break
		}
	}

	if volMountPath == "" {
		// Device hasn't been mounted anywhere yet, but we know
		// it is already attached.
		return false, nil
	}

	// Scan the mount table info and if an entry's device matches
	// the volume attachment's device, then it's mounted.
	for _, mi := range minfo {
		if mi.Source == volMountPath && mi.Path == targetPath {
			return true, nil
		}
	}

	// If no mount was discovered then indicate the volume is not
	// published on this node.
	return false, nil
}
<commit_msg>Account for root path in libStorage volume<commit_after>package libstorage

import (
	"errors"
	"fmt"
	"path"

	"github.com/codedellemc/gocsi/csi"
	"github.com/codedellemc/gocsi/mount"

	apitypes "github.com/codedellemc/rexray/libstorage/api/types"
	apiutils "github.com/codedellemc/rexray/libstorage/api/utils"
)

var errMissingIDKeyPath = errors.New("missing id key path")
var errMissingTokenKey = errors.New("missing token key")
var errUnableToGetLocDevs = errors.New("unable to get local devices")

const resNotFound = "resource not found"

func isNotFoundErr(err error) bool {
	return err.Error() == resNotFound
}

// GetVolumeName should return the name of the volume specified
// by the provided volume ID. If the volume does not exist then
// an empty string should be returned.
func (d *driver) GetVolumeName(id *csi.VolumeID) (string, error) {
	idVal, ok := id.Values["id"]
	if !ok {
		return "", errMissingIDKeyPath
	}

	opts := &apitypes.VolumeInspectOpts{
		Opts: apiutils.NewStore(),
	}

	vol, err := d.client.Storage().VolumeInspect(d.ctx, idVal, opts)
	if err != nil {

		// If the volume is not found then return an empty string
		// for the name to indicate such.
		if isNotFoundErr(err) {
			return "", nil
		}

		return "", err
	}

	return vol.Name, nil
}

// GetVolumeInfo should return information about the volume
// specified by the provided volume name. If the volume does not
// exist then a nil value should be returned.
func (d *driver) GetVolumeInfo(name string) (*csi.VolumeInfo, error) {
	td, ok := d.client.Storage().(apitypes.StorageDriverVolInspectByName)
	if !ok {
		return nil, fmt.Errorf(
			"stor driver not by name: %T", d.client.Storage())
	}

	opts := &apitypes.VolumeInspectOpts{
		Opts: apiutils.NewStore(),
	}

	vol, err := td.VolumeInspectByName(d.ctx, name, opts)
	if err != nil {

		// If the volume is not found then return nil for the
		// volume info to indicate such.
		if isNotFoundErr(err) {
			return nil, nil
		}

		return nil, err
	}

	return toVolumeInfo(vol), nil
}

// IsControllerPublished should return publication info about
// the volume specified by the provided volume name or ID.
func (d *driver) IsControllerPublished(
	id *csi.VolumeID) (*csi.PublishVolumeInfo, error) {

	idVal, ok := id.Values["id"]
	if !ok {
		return nil, errMissingIDKeyPath
	}

	// Request only volumes that are attached.
	opts := &apitypes.VolumeInspectOpts{
		Attachments: apitypes.VolAttReqForInstance,
		Opts:        apiutils.NewStore(),
	}

	vol, err := d.client.Storage().VolumeInspect(d.ctx, idVal, opts)
	if err != nil {
		return nil, err
	}

	// If the volume is not attached to this node then do not
	// indicate an error; just return nil to indicate
	// the volume is not attached to this node.
	if vol.AttachmentState != apitypes.VolumeAttached {
		return nil, nil
	}

	d.attTokensRWL.RLock()
	defer d.attTokensRWL.RUnlock()

	return &csi.PublishVolumeInfo{
		Values: map[string]string{
			"token":     d.attTokens[idVal],
			"encrypted": fmt.Sprintf("%v", vol.Encrypted),
		},
	}, nil
}

// IsNodePublished should return a flag indicating whether or
// not the volume exists and is published on the current host.
func (d *driver) IsNodePublished(
	id *csi.VolumeID,
	pubInfo *csi.PublishVolumeInfo,
	targetPath string) (bool, error) {

	var (
		devPath      string
		volMountPath string
		rootPath     = d.config.GetString(apitypes.ConfigIgVolOpsMountRootPath)
	)

	if pubInfo != nil {
		token, ok := pubInfo.Values["token"]
		if !ok {
			return false, errMissingTokenKey
		}

		// Get device from local devices
		opts := &apitypes.LocalDevicesOpts{
			Opts:     apiutils.NewStore(),
			ScanType: apitypes.DeviceScanQuick,
		}
		devs, err := d.client.Executor().LocalDevices(d.ctx, opts)
		if err != nil {
			return false, errUnableToGetLocDevs
		}

		devPath, ok = devs.DeviceMap[token]
		if !ok {
			// device not in device map yet. That may not be an error, as
			// it may not have shown up yet. Defer to lower-level publish
			return false, nil
		}
	} else {
		// pubInfo is nil, so this is likely checking an Unpublish request
		// all we have is the ID, so we will have to make a libStorage
		// inspect call to get the underlying device

		idVal, ok := id.Values["id"]
		if !ok {
			return false, errMissingIDKeyPath
		}

		// Request only volumes attached to this instance.
		opts := &apitypes.VolumeInspectOpts{
			Attachments: apitypes.VolAttReqWithDevMapForInstance,
			Opts:        apiutils.NewStore(),
		}

		vol, err := d.client.Storage().VolumeInspect(d.ctx, idVal, opts)
		if err != nil {
			return false, err
		}

		// If the volume is not attached to this node then do not
		// indicate an error; just return false to indicate
		// the volume is not attached to this node.
		if vol.AttachmentState != apitypes.VolumeAttached {
			return false, nil
		}

		// If the volume has no attachments then it's not possible to
		// determine the node publication status.
		if len(vol.Attachments) == 0 {
			return false, nil
		}

		devPath = vol.Attachments[0].DeviceName
	}

	// Get the local mount table.
	minfo, err := mount.GetMounts()
	if err != nil {
		return false, err
	}

	// Scan the mount table and get the path to which the device of
	// the attached volume is mounted.
	for _, mi := range minfo {
		if mi.Device == devPath {
			volMountPath = mi.Path
			break
		}
	}

	if volMountPath == "" {
		// Device hasn't been mounted anywhere yet, but we know
		// it is already attached.
		return false, nil
	}
	// This adds the root path (/data by default)
	volMountPath = path.Join(volMountPath, rootPath)

	// Scan the mount table info and if an entry's device matches
	// the volume attachment's device, then it's mounted.
	for _, mi := range minfo {
		if mi.Source == volMountPath && mi.Path == targetPath {
			return true, nil
		}
	}

	// If no mount was discovered then indicate the volume is not
	// published on this node.
	return false, nil
}
<|endoftext|>


<commit_before>package thumbnailer

import (
	"crypto/sha1"
	"fmt"
	"github.com/abustany/gollery/utils"
	"github.com/gographics/imagick/imagick"
	"github.com/howeyc/fsnotify"
	"github.com/robfig/revel"
	"io"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	THUMBNAILER_SIZE_PX             = 200
	THUMBNAILER_COMPRESSION_QUALITY = 75
)

type Thumbnailer struct {
	RootDir           string
	CacheDir          string
	monitor           *fsnotify.Watcher
	thumbnailingQueue chan string
	queuedItems       map[string]bool
	queuedItemsMutex  sync.Mutex
}

func init() {
	imagick.Initialize()
}

func makeCacheKey(path string) string {
	h := sha1.New()
	io.WriteString(h, path)
	key := fmt.Sprintf("%.0x", h.Sum(nil))
	return key
}

// returns the list of thumb keys from this directory
func (t *Thumbnailer) checkCacheDir(dirPath string) ([]string, error) {
	dirFd, err := os.Open(dirPath)

	revel.INFO.Printf("Cleaning cache for directory '%s'", dirPath)

	if err != nil {
		return nil, utils.WrapError(err, "Cannot open directory '%s'", dirPath)
	}

	defer dirFd.Close()

	fis, err := dirFd.Readdir(-1)

	if err == io.EOF {
		return nil, nil
	}

	if err != nil {
		return nil, utils.WrapError(err, "Cannot read directory '%s'", dirPath)
	}

	thumbsToCreate := []string{}
	allThumbKeys := []string{}

	for _, f := range fis {
		fPath := path.Join(dirPath, f.Name())

		if f.IsDir() {
			childThumbKeys, err := t.checkCacheDir(fPath)

			if err != nil {
				revel.WARN.Printf("Cannot clean cache directory '%s': %s (skipping)", fPath, err)
			}

			allThumbKeys = append(allThumbKeys, childThumbKeys...)

			continue
		}

		fId := fPath[1+len(t.RootDir):]

		revel.TRACE.Printf("Checking thumbnail for %s", fId)

		cacheKey := makeCacheKey(fId)

		allThumbKeys = append(allThumbKeys, cacheKey)

		cacheFilePath := path.Join(t.CacheDir, cacheKey)

		_, err := os.Stat(cacheFilePath)

		if os.IsNotExist(err) {
			thumbsToCreate = append(thumbsToCreate, fPath)
			continue
		}

		if err != nil {
			revel.WARN.Printf("Error while checking thumbnail for '%s': %s (skipping)", fPath, err)
			continue
		}
	}

	for _, x := range thumbsToCreate {
		t.ScheduleThumbnail(x)
	}

	return allThumbKeys, nil
}

func (t *Thumbnailer) fileMonitorRoutine() {
	for {
		select {
		case ev := <-t.monitor.Event:
			revel.TRACE.Printf("Thumbnailer: file event: %s", ev)

			basename := path.Base(ev.Name)

			if len(basename) > 0 && basename[0] == '.' {
				revel.TRACE.Printf("Skipping event for hidden file %s", ev.Name)
				continue
			}

			if ev.IsDelete() || ev.IsRename() {
				// We can't know if it was a directory or a file... Try to remove anyway
				// I assume if the directory gets deleted, the monitor goes away?
				t.DeleteThumbnail(ev.Name)
				continue
			}

			info, err := os.Stat(ev.Name)

			if err != nil {
				revel.ERROR.Printf("Cannot get file information for %s: %s", ev.Name, err)
				continue
			}

			if info.Mode().IsRegular() {
				t.ScheduleThumbnail(ev.Name)
			} else if info.IsDir() {
				if !ev.IsCreate() {
					continue
				}

				err := t.setupDirMonitor(ev.Name, info, nil)

				if err != nil {
					revel.ERROR.Printf("Cannot setup a file monitor on %s: %s", ev.Name, err)
				}
			} else {
				revel.INFO.Printf("Skipping file event for file of unknown type %s", ev.Name)
			}
		case err := <-t.monitor.Error:
			revel.ERROR.Printf("Thumbnailer: file monitoring error: %s", err)
		}
	}
}

func (t *Thumbnailer) thumbnailQueueRoutine() {
	for filePath := range t.thumbnailingQueue {
		t.queuedItemsMutex.Lock()
		delete(t.queuedItems, filePath)
		t.queuedItemsMutex.Unlock()

		err := t.CreateThumbnail(filePath)

		if err != nil {
			revel.ERROR.Printf("Couldn't create thumbnail for file '%s': %s", filePath, err)
		}

		revel.INFO.Printf("The thumbnailing queue now has %d items", len(t.thumbnailingQueue))
	}
}

func NewThumbnailer(rootDir string, cacheDir string) (*Thumbnailer, error) {
	t := &Thumbnailer{
		RootDir:           rootDir,
		CacheDir:          cacheDir,
		thumbnailingQueue: make(chan string, 256),
		queuedItems:       make(map[string]bool, 256),
	}

	var err error

	t.monitor, err = fsnotify.NewWatcher()

	if err != nil {
		return nil, utils.WrapError(err, "Cannot initialize file monitoring")
	}

	go t.fileMonitorRoutine()

	revel.INFO.Printf("Starting %d thumbnailer routines", runtime.NumCPU())

	for i := 0; i < runtime.NumCPU(); i++ {
		go t.thumbnailQueueRoutine()
	}

	return t, nil
}

// Creates missing thumbnails
func (t *Thumbnailer) CheckCache() error {
	allThumbKeys, err := t.checkCacheDir(t.RootDir)

	if err != nil {
		return utils.WrapError(err, "Cannot check thumbnail cache")
	}

	keyHash := make(map[string]bool, len(allThumbKeys))

	for _, key := range allThumbKeys {
		keyHash[key] = true
	}

	fd, err := os.Open(t.CacheDir)

	if err != nil {
		return utils.WrapError(err, "Cannot check thumbnail cache for stall thumbnails")
	}

	defer fd.Close()

	for {
		// Don't read all files at all, it might be a lot
		fis, err := fd.Readdir(1024)

		if err == io.EOF {
			break
		}

		if err != nil {
			return utils.WrapError(err, "Cannot list thumbnails while cleaning cache")
		}

		for _, fi := range fis {
			// Thumbnail corresponds to a known picture, leave it alone
			if _, exists := keyHash[fi.Name()]; exists {
				continue
			}

			thumbPath := path.Join(t.CacheDir, fi.Name())

			revel.INFO.Printf("Removing stale thumbnail with key %s", fi.Name())
			err = os.Remove(thumbPath)

			if err != nil && !os.IsNotExist(err) {
				return utils.WrapError(err, "Cannot delete thumbnail '%s' while cleaning up cache", thumbPath)
			}
		}
	}

	return nil
}

func (t *Thumbnailer) setupDirMonitor(path string, info os.FileInfo, err error) error {
	if err != nil {
		return err
	}

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

	if !info.IsDir() {
		return nil
	}

	err = t.monitor.Watch(path)

	if err == nil {
		revel.INFO.Printf("Setup a directory monitor on '%s'", path)
	}

	return err
}

func (t *Thumbnailer) SetupMonitors() error {
	return utils.WrapError(filepath.Walk(t.RootDir, t.setupDirMonitor), "Cannot setup monitoring")
}

func (t *Thumbnailer) ScheduleThumbnail(filePath string) {
	revel.INFO.Printf("Scheduling thumbnailing of file %s", filePath)

	t.queuedItemsMutex.Lock()
	defer t.queuedItemsMutex.Unlock()

	if _, alreadyQueued := t.queuedItems[filePath]; alreadyQueued {
		revel.INFO.Printf("Thumbnailing already scheduled for file %s", filePath)
		return
	}

	t.thumbnailingQueue <- filePath
	t.queuedItems[filePath] = true
}

// For absolute paths, check that they are in the root dir
// For relative paths, prepend the root dir path
func (t *Thumbnailer) normalizePath(filePath string) (string, error) {
	if len(filePath) > 0 && filePath[0] == '/' {
		if !strings.HasPrefix(filePath, t.RootDir) {
			return "", fmt.Errorf("Not creating a thumbnail for a file outside the root directory: %s", filePath)
		}

		return filePath, nil
	}

	return path.Join(t.RootDir, filePath), nil
}

func (t *Thumbnailer) CreateThumbnail(filePath string) error {
	normalizedPath, err := t.normalizePath(filePath)

	if err != nil {
		return utils.WrapError(err, "Invalid path '%s'", filePath)
	}

	fileId := normalizedPath[1+len(t.RootDir):]

	startTime := time.Now()

	mw := imagick.NewMagickWand()
	defer mw.Destroy()

	err = mw.ReadImage(normalizedPath)

	if err != nil {
		return utils.WrapError(err, "Cannot read file '%s'", normalizedPath)
	}

	thumbKey := makeCacheKey(fileId)
	thumbPath := path.Join(t.CacheDir, thumbKey)

	width := mw.GetImageWidth()
	height := mw.GetImageHeight()
	var scale float32

	if width > height {
		scale = float32(THUMBNAILER_SIZE_PX) / float32(width)
		width = THUMBNAILER_SIZE_PX
		height = uint(float32(height) * scale)
	} else {
		scale = float32(THUMBNAILER_SIZE_PX) / float32(height)
		height = THUMBNAILER_SIZE_PX
		width = uint(float32(width) * scale)
	}

	// TRIANGLE is a simple linear interpolation, should be fast enough
	err = mw.ResizeImage(width, height, imagick.FILTER_TRIANGLE, 1)

	if err != nil {
		return utils.WrapError(err, "Cannot generate thumbnail for file '%s'", normalizedPath)
	}

	err = mw.SetCompressionQuality(THUMBNAILER_COMPRESSION_QUALITY)

	if err != nil {
		return utils.WrapError(err, "Cannot set compression quality for file '%s'", normalizedPath)
	}

	err = mw.WriteImage(thumbPath)

	if err != nil {
		return utils.WrapError(err, "Cannot write thumbnail '%s' for file '%s'", thumbPath, normalizedPath)
	}

	revel.INFO.Printf("Thumbnailed image '%s' as '%s' in %.2f seconds", normalizedPath, thumbPath, time.Now().Sub(startTime).Seconds())

	return nil
}

func (t *Thumbnailer) DeleteThumbnail(filePath string) error {
	normalizedPath, err := t.normalizePath(filePath)

	if err != nil {
		return utils.WrapError(err, "Invalid path '%s'", normalizedPath)
	}

	fileId := normalizedPath[1+len(t.RootDir):]
	thumbKey := makeCacheKey(fileId)
	thumbPath := path.Join(t.CacheDir, thumbKey)

	err = os.Remove(thumbPath)

	if os.IsNotExist(err) {
		return nil
	}

	if err != nil {
		return utils.WrapError(err, "Cannot remove thumbnail")
	}

	revel.INFO.Printf("Deleted thumbnail for image '%s'", normalizedPath)

	return nil
}

func (t *Thumbnailer) ThumbnailQueueSize() int {
	return len(t.queuedItems)
}
<commit_msg>thumbnailer: Handle file events for directories better<commit_after>package thumbnailer

import (
	"crypto/sha1"
	"fmt"
	"github.com/abustany/gollery/utils"
	"github.com/gographics/imagick/imagick"
	"github.com/howeyc/fsnotify"
	"github.com/robfig/revel"
	"io"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	THUMBNAILER_SIZE_PX             = 200
	THUMBNAILER_COMPRESSION_QUALITY = 75
	THUMBNAILER_CLEANUP_JOB_ID      = "@cleanup"
)

type Thumbnailer struct {
	RootDir           string
	CacheDir          string
	monitor           *fsnotify.Watcher
	thumbnailingQueue chan string
	queuedItems       map[string]bool
	queuedItemsMutex  sync.Mutex
	watchedDirs       map[string]bool
	watchedDirsMutex  sync.Mutex
}

func init() {
	imagick.Initialize()
}

func makeCacheKey(path string) string {
	h := sha1.New()
	io.WriteString(h, path)
	key := fmt.Sprintf("%.0x", h.Sum(nil))
	return key
}

// returns the list of thumb keys from this directory
func (t *Thumbnailer) checkCacheDir(dirPath string) ([]string, error) {
	dirFd, err := os.Open(dirPath)

	revel.INFO.Printf("Cleaning cache for directory '%s'", dirPath)

	if err != nil {
		return nil, utils.WrapError(err, "Cannot open directory '%s'", dirPath)
	}

	defer dirFd.Close()

	fis, err := dirFd.Readdir(-1)

	if err == io.EOF {
		return nil, nil
	}

	if err != nil {
		return nil, utils.WrapError(err, "Cannot read directory '%s'", dirPath)
	}

	thumbsToCreate := []string{}
	allThumbKeys := []string{}

	for _, f := range fis {
		fPath := path.Join(dirPath, f.Name())

		if f.IsDir() {
			childThumbKeys, err := t.checkCacheDir(fPath)

			if err != nil {
				revel.WARN.Printf("Cannot clean cache directory '%s': %s (skipping)", fPath, err)
			}

			allThumbKeys = append(allThumbKeys, childThumbKeys...)

			continue
		}

		fId := fPath[1+len(t.RootDir):]

		revel.TRACE.Printf("Checking thumbnail for %s", fId)

		cacheKey := makeCacheKey(fId)

		allThumbKeys = append(allThumbKeys, cacheKey)

		cacheFilePath := path.Join(t.CacheDir, cacheKey)

		_, err := os.Stat(cacheFilePath)

		if os.IsNotExist(err) {
			thumbsToCreate = append(thumbsToCreate, fPath)
			continue
		}

		if err != nil {
			revel.WARN.Printf("Error while checking thumbnail for '%s': %s (skipping)", fPath, err)
			continue
		}
	}

	for _, x := range thumbsToCreate {
		t.ScheduleThumbnail(x)
	}

	return allThumbKeys, nil
}

func (t *Thumbnailer) isWatchedDirectory(filePath string) bool {
	t.watchedDirsMutex.Lock()
	defer t.watchedDirsMutex.Unlock()

	_, watched := t.watchedDirs[filePath]

	return watched
}

func (t *Thumbnailer) fileMonitorRoutine() {
	for {
		select {
		case ev := <-t.monitor.Event:
			revel.TRACE.Printf("Thumbnailer: file event: %s", ev)

			basename := path.Base(ev.Name)

			if len(basename) > 0 && basename[0] == '.' {
				revel.TRACE.Printf("Skipping event for hidden file %s", ev.Name)
				continue
			}

			if ev.IsDelete() || ev.IsRename() {
				// We can't know if it was a directory or a file... Try to figure out

				if t.isWatchedDirectory(ev.Name) {
					// Crap, a directory was deleted, and we can't list it anymore... We need
					// queue a full cleanup
					t.ScheduleThumbnail(THUMBNAILER_CLEANUP_JOB_ID)
					continue
				}

				// It was not a watched directory... So it must have been a file
				t.DeleteThumbnail(ev.Name)
				continue
			}

			info, err := os.Stat(ev.Name)

			if err != nil {
				revel.ERROR.Printf("Cannot get file information for %s: %s", ev.Name, err)
				continue
			}

			if info.Mode().IsRegular() {
				t.ScheduleThumbnail(ev.Name)
			} else if info.IsDir() {
				if !ev.IsCreate() {
					continue
				}

				err := t.setupDirMonitor(ev.Name, info, nil)

				if err != nil {
					revel.ERROR.Printf("Cannot setup a file monitor on %s: %s", ev.Name, err)
					continue
				}

				_, err = t.checkCacheDir(ev.Name)

				if err != nil {
					revel.ERROR.Printf("Cannot create thumbnails for directory '%s': %s", ev.Name, err)
				}
			} else {
				revel.INFO.Printf("Skipping file event for file of unknown type %s", ev.Name)
			}
		case err := <-t.monitor.Error:
			revel.ERROR.Printf("Thumbnailer: file monitoring error: %s", err)
		}
	}
}

func (t *Thumbnailer) thumbnailQueueRoutine() {
	for filePath := range t.thumbnailingQueue {
		t.queuedItemsMutex.Lock()
		delete(t.queuedItems, filePath)
		t.queuedItemsMutex.Unlock()

		if filePath == THUMBNAILER_CLEANUP_JOB_ID {
			t.CheckCache()
			continue
		}

		err := t.CreateThumbnail(filePath)

		if err != nil {
			revel.ERROR.Printf("Couldn't create thumbnail for file '%s': %s", filePath, err)
		}

		revel.INFO.Printf("The thumbnailing queue now has %d items", len(t.thumbnailingQueue))
	}
}

func NewThumbnailer(rootDir string, cacheDir string) (*Thumbnailer, error) {
	t := &Thumbnailer{
		RootDir:           rootDir,
		CacheDir:          cacheDir,
		thumbnailingQueue: make(chan string, 256),
		queuedItems:       make(map[string]bool, 256),
		watchedDirs:       map[string]bool{},
	}

	var err error

	t.monitor, err = fsnotify.NewWatcher()

	if err != nil {
		return nil, utils.WrapError(err, "Cannot initialize file monitoring")
	}

	go t.fileMonitorRoutine()

	revel.INFO.Printf("Starting %d thumbnailer routines", runtime.NumCPU())

	for i := 0; i < runtime.NumCPU(); i++ {
		go t.thumbnailQueueRoutine()
	}

	return t, nil
}

// Creates missing thumbnails
func (t *Thumbnailer) CheckCache() error {
	revel.INFO.Printf("Starting cache cleanup")

	allThumbKeys, err := t.checkCacheDir(t.RootDir)

	if err != nil {
		return utils.WrapError(err, "Cannot check thumbnail cache")
	}

	keyHash := make(map[string]bool, len(allThumbKeys))

	for _, key := range allThumbKeys {
		keyHash[key] = true
	}

	fd, err := os.Open(t.CacheDir)

	if err != nil {
		return utils.WrapError(err, "Cannot check thumbnail cache for stall thumbnails")
	}

	defer fd.Close()

	for {
		// Don't read all files at all, it might be a lot
		fis, err := fd.Readdir(1024)

		if err == io.EOF {
			break
		}

		if err != nil {
			return utils.WrapError(err, "Cannot list thumbnails while cleaning cache")
		}

		for _, fi := range fis {
			// Thumbnail corresponds to a known picture, leave it alone
			if _, exists := keyHash[fi.Name()]; exists {
				continue
			}

			thumbPath := path.Join(t.CacheDir, fi.Name())

			revel.INFO.Printf("Removing stale thumbnail with key %s", fi.Name())
			err = os.Remove(thumbPath)

			if err != nil && !os.IsNotExist(err) {
				return utils.WrapError(err, "Cannot delete thumbnail '%s' while cleaning up cache", thumbPath)
			}
		}
	}

	return nil
}

func (t *Thumbnailer) setupDirMonitor(path string, info os.FileInfo, err error) error {
	if err != nil {
		return err
	}

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

	if !info.IsDir() {
		return nil
	}

	err = t.monitor.Watch(path)

	t.watchedDirsMutex.Lock()
	t.watchedDirs[path] = true
	t.watchedDirsMutex.Unlock()

	if err == nil {
		revel.INFO.Printf("Setup a directory monitor on '%s'", path)
	}

	return err
}

func (t *Thumbnailer) SetupMonitors() error {
	return utils.WrapError(filepath.Walk(t.RootDir, t.setupDirMonitor), "Cannot setup monitoring")
}

func (t *Thumbnailer) ScheduleThumbnail(filePath string) {
	revel.INFO.Printf("Scheduling thumbnailing of file %s", filePath)

	t.queuedItemsMutex.Lock()
	defer t.queuedItemsMutex.Unlock()

	if _, alreadyQueued := t.queuedItems[filePath]; alreadyQueued {
		revel.INFO.Printf("Thumbnailing already scheduled for file %s", filePath)
		return
	}

	t.thumbnailingQueue <- filePath
	t.queuedItems[filePath] = true
}

// For absolute paths, check that they are in the root dir
// For relative paths, prepend the root dir path
func (t *Thumbnailer) normalizePath(filePath string) (string, error) {
	if len(filePath) > 0 && filePath[0] == '/' {
		if !strings.HasPrefix(filePath, t.RootDir) {
			return "", fmt.Errorf("Not creating a thumbnail for a file outside the root directory: %s", filePath)
		}

		return filePath, nil
	}

	return path.Join(t.RootDir, filePath), nil
}

func (t *Thumbnailer) CreateThumbnail(filePath string) error {
	normalizedPath, err := t.normalizePath(filePath)

	if err != nil {
		return utils.WrapError(err, "Invalid path '%s'", filePath)
	}

	fileId := normalizedPath[1+len(t.RootDir):]

	startTime := time.Now()

	mw := imagick.NewMagickWand()
	defer mw.Destroy()

	err = mw.ReadImage(normalizedPath)

	if err != nil {
		return utils.WrapError(err, "Cannot read file '%s'", normalizedPath)
	}

	thumbKey := makeCacheKey(fileId)
	thumbPath := path.Join(t.CacheDir, thumbKey)

	width := mw.GetImageWidth()
	height := mw.GetImageHeight()
	var scale float32

	if width > height {
		scale = float32(THUMBNAILER_SIZE_PX) / float32(width)
		width = THUMBNAILER_SIZE_PX
		height = uint(float32(height) * scale)
	} else {
		scale = float32(THUMBNAILER_SIZE_PX) / float32(height)
		height = THUMBNAILER_SIZE_PX
		width = uint(float32(width) * scale)
	}

	// TRIANGLE is a simple linear interpolation, should be fast enough
	err = mw.ResizeImage(width, height, imagick.FILTER_TRIANGLE, 1)

	if err != nil {
		return utils.WrapError(err, "Cannot generate thumbnail for file '%s'", normalizedPath)
	}

	err = mw.SetCompressionQuality(THUMBNAILER_COMPRESSION_QUALITY)

	if err != nil {
		return utils.WrapError(err, "Cannot set compression quality for file '%s'", normalizedPath)
	}

	err = mw.WriteImage(thumbPath)

	if err != nil {
		return utils.WrapError(err, "Cannot write thumbnail '%s' for file '%s'", thumbPath, normalizedPath)
	}

	revel.INFO.Printf("Thumbnailed image '%s' as '%s' in %.2f seconds", normalizedPath, thumbPath, time.Now().Sub(startTime).Seconds())

	return nil
}

func (t *Thumbnailer) DeleteThumbnail(filePath string) error {
	normalizedPath, err := t.normalizePath(filePath)

	if err != nil {
		return utils.WrapError(err, "Invalid path '%s'", normalizedPath)
	}

	fileId := normalizedPath[1+len(t.RootDir):]
	thumbKey := makeCacheKey(fileId)
	thumbPath := path.Join(t.CacheDir, thumbKey)

	err = os.Remove(thumbPath)

	if os.IsNotExist(err) {
		return nil
	}

	if err != nil {
		return utils.WrapError(err, "Cannot remove thumbnail")
	}

	revel.INFO.Printf("Deleted thumbnail for image '%s'", normalizedPath)

	return nil
}

func (t *Thumbnailer) ThumbnailQueueSize() int {
	return len(t.queuedItems)
}
<|endoftext|>


<commit_before>// prints out user information
package main

import (
	"flag"
	"fmt"
	"github.com/DrItanium/fakku"
	"time"
)

var userName = flag.String("username", "", "The username to lookup")

func main() {
	flag.Parse()
	//TODO: add support for grabbing beyond the first page
	//TODO: add support for spitting out CLIPS style facts (template assertions
	//or instances)
	if *userName == "" {
		fmt.Printf("ERROR: no username specified")
	} else {
		user, err := fakku.GetUserProfile(*userName)
		if err != nil {
			fmt.Println(err)
			fmt.Println("Something bad happened! Perhaps fakku is down?")
		} else {
			fmt.Printf("Username: %s\nUrl: %s\nRank: %s\n", user.Username, user.Url, user.Rank)
			fmt.Printf("Avatar\n\tURL: %s\n\tWidth: %d\n\tHeight: %d\n", user.Avatar, user.AvatarWidth, user.AvatarHeight)
			regDate, err0 := time.Parse(time.UnixDate, string(user.RegistrationDate))
			if err0 != nil {
				fmt.Println(err0)
				return
			}
			lastVisit, err1 := time.Parse(time.UnixDate, string(user.LastVisit))
			if err1 != nil {
				fmt.Println(err1)
				return
			}
			fmt.Printf("Registration date: %t\nLastVisit: %t\n", regDate, lastVisit)
		}

	}
	/*
		posts, err := fakku.GetUserProfile(*userName)
		if err != nil {
			fmt.Println(err)
			fmt.Println("Something bad happened! Perhaps Fakku is down?")
		}
		for i := 0; i < int(posts.Total); i++ {
			tmp := posts.Index[i]
			switch tmp.(type) {
			case fakku.Content:
				content := tmp.(fakku.Content)
				tags := content.Tags
				fmt.Printf("%s - ", content.Name)
				// print out the tags one after another in a form that can be easily
				// grepped through
				if len(tags) == 0 {
					fmt.Printf("No tags!")
				} else {
					fmt.Printf("{ %s", tags[0].Attribute)
					for j := 1; j < len(tags); j++ {
						fmt.Printf(", %s", tags[j].Attribute)
					}
					fmt.Printf(" }")
				}
				fmt.Printf(" - %s", content.Url)
				fmt.Println()
			}
		}
	*/
}
<commit_msg>Finished implementing the fakku-user tool<commit_after>// prints out user information
package main

import (
	"flag"
	"fmt"
	"github.com/DrItanium/fakku"
	"time"
)

var userName = flag.String("username", "", "The username to lookup")

func main() {
	//TODO: file bug report for the fact that user has uid attached to the end of it :(
	flag.Parse()
	if *userName == "" {
		fmt.Println("ERROR: no username specified")
	} else {
		user, err := fakku.GetUserProfile(*userName)
		if err != nil {
			fmt.Println(err)
			fmt.Println("Something bad happened! Perhaps fakku is down?")
		} else {
			fmt.Printf("Username: %s\nUrl: %s\nRank: %s\n", user.Username, user.Url, user.Rank)
			//TODO: file bug report that avatar link isn't working :(
			fmt.Printf("Avatar\n\tURL: %s\n\tWidth: %d\n\tHeight: %d\n", user.Avatar, user.AvatarWidth, user.AvatarHeight)
			fmt.Printf("Registration date: %s\nLast visit: %s\n", time.Unix(int64(user.RegistrationDate), 0).String(), time.Unix(int64(user.LastVisit), 0).String())
			fmt.Printf("Subscription count: %d\nNumber of posts: %d\nNumber of topics: %d\n", user.Subscribed, user.Posts, user.Topics)
			//TODO: file bug report about user_timezone not existing
			fmt.Printf("Number of comments: %d\nSignature: \"%s\"\n", user.Comments, user.Signature)
			fmt.Printf("Reputation\n\tForum: %d\n\tComment: %d\n", user.ForumReputation, user.CommentReputation)
			var hasGold, isOnline string
			if user.Gold == 1 {
				hasGold = "yes"
			} else {
				hasGold = "no"
			}
			if user.Online == 1 {
				isOnline = "yes"
			} else {
				isOnline = "no"
			}
			fmt.Printf("Has Fakku Gold? %s\nIs online? %s\n", hasGold, isOnline)
		}
	}
}
<|endoftext|>


<commit_before>// +build linux freebsd

package bcd

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

type pipes struct {
	stdin  io.Reader
	stderr io.Writer
}

type uploader struct {
	endpoint string
	options  PutOptions
}

type BTTracer struct {
	// Path to the tracer to invoke.
	cmd string

	// Output directory for generated snapshots.
	outputDir string

	// Generic options to pass to the tracer.
	options []string

	// Prefix for key-value options.
	kvp string

	// Delimeter between key and value for key-value options.
	kvd string

	// Channel which receives signal notifications.
	sigs chan os.Signal

	// The set of signals the tracer will monitor.
	ss []os.Signal

	// The pipes to use for tracer I/O.
	p pipes

	// Protects tracer state modification.
	m sync.RWMutex

	// Logs tracer execution status messages.
	logger Log

	// Default trace options to use if none are specified to bcd.Trace().
	defaultTraceOptions TraceOptions

	// The connection information and options used during Put operations.
	put uploader
}

type defaultLogger struct {
	logger *log.Logger
	level  LogPriority
}

func (d *defaultLogger) Logf(level LogPriority, format string, v ...interface{}) {
	if (d.level & level) == 0 {
		return
	}

	d.logger.Printf(format, v...)
}

func (d *defaultLogger) SetLogLevel(level LogPriority) {
	d.level = level
}

type NewOptions struct {
	// If false, system goroutines (i.e. those started and used by the Go
	// runtime) are excluded.
	IncludeSystemGs bool
}

// Returns a new object implementing the bcd.Tracer and bcd.TracerSig interfaces
// using the Backtrace debugging platform. Currently, only Linux and FreeBSD
// are supported.
//
// Relevant default values:
//
// Tracer path: /opt/backtrace/bin/ptrace.
//
// Output directory: Current working directory of process.
//
// Signal set: ABRT, FPE, SEGV, ILL, BUS. Note: Go converts BUS, FPE, and
// SEGV arising from process execution into run-time panics, which cannot be
// handled by signal handlers. These signals are caught when sent from
// os.Process.Kill or similar.
//
// The default logger prints to stderr.
//
// DefaultTraceOptions:
//
// Faulted: true
//
// CallerOnly: false
//
// ErrClassification: true
//
// Timeout: 120s
func New(options NewOptions) *BTTracer {
	moduleOpt := "--module=go:enable,true"
	if !options.IncludeSystemGs {
		moduleOpt += ",filter,user"
	}

	return &BTTracer{
		cmd: "/opt/backtrace/bin/ptrace",
		kvp: "--kv",
		kvd: ":",
		options: []string{
			"--load=",
			moduleOpt,
			"--faulted",
			strconv.Itoa(os.Getpid())},
		ss: []os.Signal{
			syscall.SIGABRT,
			syscall.SIGFPE,
			syscall.SIGSEGV,
			syscall.SIGILL,
			syscall.SIGBUS},
		logger: &defaultLogger{
			logger: log.New(os.Stderr, "[bcd] ", log.LstdFlags),
			level:  LogError},
		defaultTraceOptions: TraceOptions{
			Faulted:           true,
			CallerOnly:        false,
			ErrClassification: true,
			Timeout:           time.Second * 120}}
}

const (
	defaultCoronerScheme = "https"
	defaultCoronerPort   = "6098"
)

type PutOptions struct {
	// If set to true, tracer remnants (i.e. generated snapshot files)
	// will be unlinked from the filesystem after successful puts.
	Unlink bool

	// The http.Client to use for uploading. The default will be used
	// if left unspecified.
	Client http.Client
}

// Enables uploading of the generated snapshot file to a remote Backtrace
// coronerd object store.
//
// endpoint: The URL of the server. It must be a valid HTTP endpoint as
// according to url.Parse() (which is based on RFC 3986). The default scheme
// and port are https and 6098, respectively, and are used if left unspecified.
//
// token: The hash associated with the coronerd project to which this
// application belongs; see
// https://documentation.backtrace.io/coronerd_setup/#authentication-tokens
// for more details.
//
// options: Modifies behavior of the Put action; see PutOptions documentation
// for more details.
func (t *BTTracer) EnablePut(endpoint, token string, options PutOptions) error {
	if endpoint == "" || token == "" {
		return errors.New("Endpoint must be non-empty")
	}

	url, err := url.Parse(endpoint)
	if err != nil {
		return err
	}

	// Endpoints without the scheme prefix (or at the very least a '//`
	// prefix) are interpreted as remote server paths. Handle the
	// (unlikely) case of an unspecified scheme. We won't allow other
	// cases, like a port specified without a scheme, though, as per
	// RFC 3986.
	if url.Host == "" {
		if url.Path == "" {
			return errors.New("invalid URL specification: host " +
				"or path must be non-empty")
		}

		url.Host = url.Path
	}

	if url.Scheme == "" {
		url.Scheme = defaultCoronerScheme
	}

	if strings.IndexAny(url.Host, ":") == -1 {
		url.Host += ":" + defaultCoronerPort
	}

	url.Path = "post"
	url.RawQuery = fmt.Sprintf("token=%s", token)

	t.put.endpoint = url.String()
	t.put.options = options

	t.Logf(LogDebug, "Put enabled (endpoint: %s, unlink: %v)\n",
		t.put.endpoint,
		t.put.options.Unlink)

	return nil
}

// See bcd.Tracer.PutEnabled().
func (t *BTTracer) PutEnabled() bool {
	return t.put.endpoint != ""
}

// See bcd.Tracer.Put().
func (t *BTTracer) Put(snapshot []byte) error {
	end := bytes.IndexByte(snapshot, 0)
	if end == -1 {
		end = len(snapshot)
	}
	path := strings.TrimSpace(string(snapshot[:end]))

	t.Logf(LogDebug, "Attempting to upload snapshot %s...\n", path)

	body, err := os.Open(path)
	if err != nil {
		return err
	}

	// The file is automatically closed by the Post request after
	// completion.

	resp, err := t.put.options.Client.Post(
		t.put.endpoint,
		"application/octet-stream",
		body)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("failed to upload: %s", resp.Status)
	}

	if t.put.options.Unlink {
		t.Logf(LogDebug, "Unlinking snapshot...\n")

		if err := os.Remove(path); err != nil {
			t.Logf(LogWarning,
				"Failed to unlink snapshot: %v\n",
				err)

			// This does not mean the put itself failed,
			// so we don't return this error here.
		} else {
			t.Logf(LogDebug, "Unlinked snapshot\n")
		}
	}

	t.Logf(LogDebug, "Uploaded snapshot\n")

	return nil
}

// Sets the executable path for the tracer.
func (t *BTTracer) SetTracerPath(path string) {
	t.m.Lock()
	defer t.m.Unlock()

	t.cmd = path
}

// Sets the output path for generated snapshots. The directory will be
// created with the specified permission bits if it does not already
// exist.
//
// If perm is 0, a default of 0755 will be used.
func (t *BTTracer) SetOutputPath(path string, perm os.FileMode) error {
	if perm == 0 {
		perm = 0755
	}

	if err := os.MkdirAll(path, perm); err != nil {
		t.Logf(LogError, "Failed to create output directory: %v\n", err)
		return err
	}

	t.m.Lock()
	defer t.m.Unlock()

	t.outputDir = path

	return nil
}

// Sets the input and output pipes for the tracer.
// Stdout is not redirected; it is instead passed to the
// tracer's Put command.
func (t *BTTracer) SetPipes(stdin io.Reader, stderr io.Writer) {
	t.m.Lock()
	defer t.m.Unlock()

	t.p.stdin = stdin
	t.p.stderr = stderr
}

// Sets the logger for the tracer.
func (t *BTTracer) SetLogger(logger Log) {
	t.logger = logger
}

// See bcd.Tracer.AddOptions().
func (t *BTTracer) AddOptions(options []string, v ...string) []string {
	if options != nil {
		return append(options, v...)
	}

	t.m.Lock()
	defer t.m.Unlock()

	t.options = append(t.options, v...)
	return nil
}

// See bcd.Tracer.AddKV().
func (t *BTTracer) AddKV(options []string, key, val string) []string {
	return t.AddOptions(options, t.kvp, key+t.kvd+val)
}

// See bcd.Tracer.AddThreadFilter().
func (t *BTTracer) AddThreadFilter(options []string, tid int) []string {
	return t.AddOptions(options, "--thread", strconv.Itoa(tid))
}

// See bcd.Tracer.AddFaultedThread().
func (t *BTTracer) AddFaultedThread(options []string, tid int) []string {
	return t.AddOptions(options, "--fault-thread", strconv.Itoa(tid))
}

// See bcd.Tracer.AddClassifier().
func (t *BTTracer) AddClassifier(options []string, classifier string) []string {
	return t.AddOptions(options, "--classifier", classifier)
}

// See bcd.Tracer.Options().
func (t *BTTracer) Options() []string {
	t.m.RLock()
	defer t.m.RUnlock()

	return append([]string(nil), t.options...)
}

// See bcd.Tracer.ClearOptions().
func (t *BTTracer) ClearOptions() {
	t.m.Lock()
	defer t.m.Unlock()

	t.options = nil
}

// See bcd.Tracer.DefaultTraceOptions().
func (t *BTTracer) DefaultTraceOptions() *TraceOptions {
	return &t.defaultTraceOptions
}

// See bcd.Tracer.Finalize().
func (t *BTTracer) Finalize(options []string) *exec.Cmd {
	t.m.RLock()
	defer t.m.RUnlock()

	tracer := exec.Command(t.cmd, options...)
	tracer.Dir = t.outputDir
	tracer.Stdin = t.p.stdin
	tracer.Stderr = t.p.stderr

	t.Logf(LogDebug, "Command: %v\n", tracer)

	return tracer
}

func (t *BTTracer) Logf(level LogPriority, format string, v ...interface{}) {
	t.m.RLock()
	defer t.m.RUnlock()

	t.logger.Logf(level, format, v...)
}

func (t *BTTracer) SetLogLevel(level LogPriority) {
	t.m.RLock()
	defer t.m.RUnlock()

	t.logger.SetLogLevel(level)
}

func (t *BTTracer) String() string {
	t.m.RLock()
	defer t.m.RUnlock()

	return fmt.Sprintf("Command: %s, Options: %v", t.cmd, t.options)
}

// See bcd.TracerSig.SetSigset().
func (t *BTTracer) SetSigset(sigs ...os.Signal) {
	t.ss = append([]os.Signal(nil), sigs...)
}

// See bcd.TracerSig.Sigset().
func (t *BTTracer) Sigset() []os.Signal {
	return append([]os.Signal(nil), t.ss...)
}

// See bcd.TracerSig.SetSigchan().
func (t *BTTracer) SetSigchan(sc chan os.Signal) {
	t.sigs = sc
}

// See bcd.TracerSig.Sigchan().
func (t *BTTracer) Sigchan() chan os.Signal {
	return t.sigs
}
<commit_msg>tracer: Add a missing deferred file.Close().<commit_after>// +build linux freebsd

package bcd

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

type pipes struct {
	stdin  io.Reader
	stderr io.Writer
}

type uploader struct {
	endpoint string
	options  PutOptions
}

type BTTracer struct {
	// Path to the tracer to invoke.
	cmd string

	// Output directory for generated snapshots.
	outputDir string

	// Generic options to pass to the tracer.
	options []string

	// Prefix for key-value options.
	kvp string

	// Delimeter between key and value for key-value options.
	kvd string

	// Channel which receives signal notifications.
	sigs chan os.Signal

	// The set of signals the tracer will monitor.
	ss []os.Signal

	// The pipes to use for tracer I/O.
	p pipes

	// Protects tracer state modification.
	m sync.RWMutex

	// Logs tracer execution status messages.
	logger Log

	// Default trace options to use if none are specified to bcd.Trace().
	defaultTraceOptions TraceOptions

	// The connection information and options used during Put operations.
	put uploader
}

type defaultLogger struct {
	logger *log.Logger
	level  LogPriority
}

func (d *defaultLogger) Logf(level LogPriority, format string, v ...interface{}) {
	if (d.level & level) == 0 {
		return
	}

	d.logger.Printf(format, v...)
}

func (d *defaultLogger) SetLogLevel(level LogPriority) {
	d.level = level
}

type NewOptions struct {
	// If false, system goroutines (i.e. those started and used by the Go
	// runtime) are excluded.
	IncludeSystemGs bool
}

// Returns a new object implementing the bcd.Tracer and bcd.TracerSig interfaces
// using the Backtrace debugging platform. Currently, only Linux and FreeBSD
// are supported.
//
// Relevant default values:
//
// Tracer path: /opt/backtrace/bin/ptrace.
//
// Output directory: Current working directory of process.
//
// Signal set: ABRT, FPE, SEGV, ILL, BUS. Note: Go converts BUS, FPE, and
// SEGV arising from process execution into run-time panics, which cannot be
// handled by signal handlers. These signals are caught when sent from
// os.Process.Kill or similar.
//
// The default logger prints to stderr.
//
// DefaultTraceOptions:
//
// Faulted: true
//
// CallerOnly: false
//
// ErrClassification: true
//
// Timeout: 120s
func New(options NewOptions) *BTTracer {
	moduleOpt := "--module=go:enable,true"
	if !options.IncludeSystemGs {
		moduleOpt += ",filter,user"
	}

	return &BTTracer{
		cmd: "/opt/backtrace/bin/ptrace",
		kvp: "--kv",
		kvd: ":",
		options: []string{
			"--load=",
			moduleOpt,
			"--faulted",
			strconv.Itoa(os.Getpid())},
		ss: []os.Signal{
			syscall.SIGABRT,
			syscall.SIGFPE,
			syscall.SIGSEGV,
			syscall.SIGILL,
			syscall.SIGBUS},
		logger: &defaultLogger{
			logger: log.New(os.Stderr, "[bcd] ", log.LstdFlags),
			level:  LogError},
		defaultTraceOptions: TraceOptions{
			Faulted:           true,
			CallerOnly:        false,
			ErrClassification: true,
			Timeout:           time.Second * 120}}
}

const (
	defaultCoronerScheme = "https"
	defaultCoronerPort   = "6098"
)

type PutOptions struct {
	// If set to true, tracer remnants (i.e. generated snapshot files)
	// will be unlinked from the filesystem after successful puts.
	Unlink bool

	// The http.Client to use for uploading. The default will be used
	// if left unspecified.
	Client http.Client
}

// Enables uploading of the generated snapshot file to a remote Backtrace
// coronerd object store.
//
// endpoint: The URL of the server. It must be a valid HTTP endpoint as
// according to url.Parse() (which is based on RFC 3986). The default scheme
// and port are https and 6098, respectively, and are used if left unspecified.
//
// token: The hash associated with the coronerd project to which this
// application belongs; see
// https://documentation.backtrace.io/coronerd_setup/#authentication-tokens
// for more details.
//
// options: Modifies behavior of the Put action; see PutOptions documentation
// for more details.
func (t *BTTracer) EnablePut(endpoint, token string, options PutOptions) error {
	if endpoint == "" || token == "" {
		return errors.New("Endpoint must be non-empty")
	}

	url, err := url.Parse(endpoint)
	if err != nil {
		return err
	}

	// Endpoints without the scheme prefix (or at the very least a '//`
	// prefix) are interpreted as remote server paths. Handle the
	// (unlikely) case of an unspecified scheme. We won't allow other
	// cases, like a port specified without a scheme, though, as per
	// RFC 3986.
	if url.Host == "" {
		if url.Path == "" {
			return errors.New("invalid URL specification: host " +
				"or path must be non-empty")
		}

		url.Host = url.Path
	}

	if url.Scheme == "" {
		url.Scheme = defaultCoronerScheme
	}

	if strings.IndexAny(url.Host, ":") == -1 {
		url.Host += ":" + defaultCoronerPort
	}

	url.Path = "post"
	url.RawQuery = fmt.Sprintf("token=%s", token)

	t.put.endpoint = url.String()
	t.put.options = options

	t.Logf(LogDebug, "Put enabled (endpoint: %s, unlink: %v)\n",
		t.put.endpoint,
		t.put.options.Unlink)

	return nil
}

// See bcd.Tracer.PutEnabled().
func (t *BTTracer) PutEnabled() bool {
	return t.put.endpoint != ""
}

// See bcd.Tracer.Put().
func (t *BTTracer) Put(snapshot []byte) error {
	end := bytes.IndexByte(snapshot, 0)
	if end == -1 {
		end = len(snapshot)
	}
	path := strings.TrimSpace(string(snapshot[:end]))

	t.Logf(LogDebug, "Attempting to upload snapshot %s...\n", path)

	body, err := os.Open(path)
	if err != nil {
		return err
	}
	defer body.Close()

	// The file is automatically closed by the Post request after
	// completion.

	resp, err := t.put.options.Client.Post(
		t.put.endpoint,
		"application/octet-stream",
		body)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("failed to upload: %s", resp.Status)
	}

	if t.put.options.Unlink {
		t.Logf(LogDebug, "Unlinking snapshot...\n")

		if err := os.Remove(path); err != nil {
			t.Logf(LogWarning,
				"Failed to unlink snapshot: %v\n",
				err)

			// This does not mean the put itself failed,
			// so we don't return this error here.
		} else {
			t.Logf(LogDebug, "Unlinked snapshot\n")
		}
	}

	t.Logf(LogDebug, "Uploaded snapshot\n")

	return nil
}

// Sets the executable path for the tracer.
func (t *BTTracer) SetTracerPath(path string) {
	t.m.Lock()
	defer t.m.Unlock()

	t.cmd = path
}

// Sets the output path for generated snapshots. The directory will be
// created with the specified permission bits if it does not already
// exist.
//
// If perm is 0, a default of 0755 will be used.
func (t *BTTracer) SetOutputPath(path string, perm os.FileMode) error {
	if perm == 0 {
		perm = 0755
	}

	if err := os.MkdirAll(path, perm); err != nil {
		t.Logf(LogError, "Failed to create output directory: %v\n", err)
		return err
	}

	t.m.Lock()
	defer t.m.Unlock()

	t.outputDir = path

	return nil
}

// Sets the input and output pipes for the tracer.
// Stdout is not redirected; it is instead passed to the
// tracer's Put command.
func (t *BTTracer) SetPipes(stdin io.Reader, stderr io.Writer) {
	t.m.Lock()
	defer t.m.Unlock()

	t.p.stdin = stdin
	t.p.stderr = stderr
}

// Sets the logger for the tracer.
func (t *BTTracer) SetLogger(logger Log) {
	t.logger = logger
}

// See bcd.Tracer.AddOptions().
func (t *BTTracer) AddOptions(options []string, v ...string) []string {
	if options != nil {
		return append(options, v...)
	}

	t.m.Lock()
	defer t.m.Unlock()

	t.options = append(t.options, v...)
	return nil
}

// See bcd.Tracer.AddKV().
func (t *BTTracer) AddKV(options []string, key, val string) []string {
	return t.AddOptions(options, t.kvp, key+t.kvd+val)
}

// See bcd.Tracer.AddThreadFilter().
func (t *BTTracer) AddThreadFilter(options []string, tid int) []string {
	return t.AddOptions(options, "--thread", strconv.Itoa(tid))
}

// See bcd.Tracer.AddFaultedThread().
func (t *BTTracer) AddFaultedThread(options []string, tid int) []string {
	return t.AddOptions(options, "--fault-thread", strconv.Itoa(tid))
}

// See bcd.Tracer.AddClassifier().
func (t *BTTracer) AddClassifier(options []string, classifier string) []string {
	return t.AddOptions(options, "--classifier", classifier)
}

// See bcd.Tracer.Options().
func (t *BTTracer) Options() []string {
	t.m.RLock()
	defer t.m.RUnlock()

	return append([]string(nil), t.options...)
}

// See bcd.Tracer.ClearOptions().
func (t *BTTracer) ClearOptions() {
	t.m.Lock()
	defer t.m.Unlock()

	t.options = nil
}

// See bcd.Tracer.DefaultTraceOptions().
func (t *BTTracer) DefaultTraceOptions() *TraceOptions {
	return &t.defaultTraceOptions
}

// See bcd.Tracer.Finalize().
func (t *BTTracer) Finalize(options []string) *exec.Cmd {
	t.m.RLock()
	defer t.m.RUnlock()

	tracer := exec.Command(t.cmd, options...)
	tracer.Dir = t.outputDir
	tracer.Stdin = t.p.stdin
	tracer.Stderr = t.p.stderr

	t.Logf(LogDebug, "Command: %v\n", tracer)

	return tracer
}

func (t *BTTracer) Logf(level LogPriority, format string, v ...interface{}) {
	t.m.RLock()
	defer t.m.RUnlock()

	t.logger.Logf(level, format, v...)
}

func (t *BTTracer) SetLogLevel(level LogPriority) {
	t.m.RLock()
	defer t.m.RUnlock()

	t.logger.SetLogLevel(level)
}

func (t *BTTracer) String() string {
	t.m.RLock()
	defer t.m.RUnlock()

	return fmt.Sprintf("Command: %s, Options: %v", t.cmd, t.options)
}

// See bcd.TracerSig.SetSigset().
func (t *BTTracer) SetSigset(sigs ...os.Signal) {
	t.ss = append([]os.Signal(nil), sigs...)
}

// See bcd.TracerSig.Sigset().
func (t *BTTracer) Sigset() []os.Signal {
	return append([]os.Signal(nil), t.ss...)
}

// See bcd.TracerSig.SetSigchan().
func (t *BTTracer) SetSigchan(sc chan os.Signal) {
	t.sigs = sc
}

// See bcd.TracerSig.Sigchan().
func (t *BTTracer) Sigchan() chan os.Signal {
	return t.sigs
}
<|endoftext|>


<commit_before>// Package fastentity provides text sequence identification in documents.
package fastentity

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"sync"
	"unicode"
)

var (
	// Maximum entity length.  NB: currently entities can be added which are longer
	// but they will be ignored in the search.
	// TODO: Maybe also error/warn on import?
	MaxEntityLen = 30
	// Number of entities to initially allocate when creating a Group.
	DefaultGroupSize = 1000
)

const (
	left  = 0
	right = 1
)

type pair [2]int

// Store is a collection of groups of entities.
type Store struct {
	sync.RWMutex // protects groups

	groups map[string]*group
}

type group struct {
	sync.RWMutex

	name     string
	entities map[string][][]rune
	maxLen   int
}

// Pops the last element and adds the new element to the front of stack.
func shift(n pair, s []pair) (pair, []pair) {
	if len(s) == 0 {
		return pair{}, append(s, n)
	}
	if len(s) == cap(s) {
		return s[0], append(s[1:], n)
	}
	return s[0], append(s, n)
}

// New creates a new Store of entity groups using the provided names.
func New(groups ...string) *Store {
	s := &Store{
		groups: make(map[string]*group, len(groups)),
	}
	for _, name := range groups {
		g := &group{
			name:     name,
			entities: make(map[string][][]rune, DefaultGroupSize),
		}
		s.groups[name] = g
	}
	return s
}

// Add ajoins the entities to the group identified by name.
func (s *Store) Add(name string, entities ...[]rune) {
	s.Lock()
	g, ok := s.groups[name]
	if !ok {
		g = &group{
			name:     name,
			entities: make(map[string][][]rune, DefaultGroupSize),
		}
		s.groups[name] = g
	}
	s.Unlock()

	g.Lock()
	for _, e := range entities {
		h := hash([]rune(e))
		g.entities[h] = append(g.entities[h], e)
		if len(e) > g.maxLen {
			g.maxLen = len(e)
		}
	}
	g.Unlock()
}

func hash(rs []rune) string {
	if len(rs) > 2 {
		return fmt.Sprintf("%s%s%s%03d", string(unicode.ToLower(rs[0])), string(unicode.ToLower(rs[1])), string(unicode.ToLower(rs[2])), len(rs))
	}
	if len(rs) > 1 {
		return fmt.Sprintf("%s%s%03d", string(unicode.ToLower(rs[0])), string(unicode.ToLower(rs[1])), len(rs))
	}
	return fmt.Sprintf("%s%03d", string(unicode.ToLower(rs[0])), len(rs))
}

// FindAll searches the input returning a maping group name -> found entities.
func (s *Store) FindAll(rs []rune) map[string][][]rune {
	result := make(map[string][][]rune, len(s.groups))
	for s, g := range s.groups {
		result[s] = g.Find(rs)
	}
	return result
}

// Find only the entities of a given type = "key"
func (g *group) Find(rs []rune) [][]rune {
	g.RLock()
	ents := find(rs, []*group{g})
	g.RUnlock()
	return ents[g.name]
}

// Lock free find for use internally
func find(rs []rune, groups []*group) map[string][][]rune {
	results := make(map[string][][]rune, len(groups))
	pairs := make([]pair, 0, 20)
	start := 0
	prevSpace := true // First char of sequence is legit
	space := false

	for off, r := range rs {
		// What are we looking at?
		space = unicode.IsPunct(r) || unicode.IsSpace(r)

		if prevSpace && !space {
			// Word is beginning at this rune
			start = off
		} else if space && !prevSpace {
			// Word is ending, shift the pairs stack
			_, pairs = shift(pair{start, off}, pairs)

			// Run the stack, check for entities working backwards from the current position
			if len(pairs) > 1 {
				p2 := pairs[len(pairs)-1]
				for i := len(pairs) - 1; i >= 0; i-- {
					p1 := pairs[i]
					if p2[right]-p1[left] > MaxEntityLen {
						break // Too long or short, can ignore it
					}
					for _, g := range groups {
						if p2[right]-p1[left] > g.maxLen {
							continue
						}
						if ents, ok := g.entities[hash(rs[p1[left]:p2[right]])]; ok {
							// We have at least one entity with this key
							for _, ent := range ents {
								if len(ent) != p2[right]-p1[left] {
									break
								}
								match := true
								for i, r := range ent {
									if unicode.ToLower(r) != unicode.ToLower(rs[p1[left]+i]) {
										match = false
										break
									}
								}
								if match {
									results[g.name] = append(results[g.name], rs[p1[left]:p2[right]])
								}
							}
						}
					}
				}
			}
		}

		// Mark prevSpace for the next loop
		if space {
			prevSpace = true
		} else {
			prevSpace = false
		}
	}
	return results
}

type incr struct {
	sync.Mutex
	n int
}

func (i *incr) incr() {
	i.Lock()
	i.n++
	i.Unlock()
}

var entityFileSuffix = ".entities.csv"

// FromDir creates a new Store by loading entity files from a given directory path. Any files
// contained in the directory with names matching <group>.entities.csv will be imported,
// and the entities added to the group <group>.
func FromDir(dir string) (*Store, error) {
	dir = strings.TrimRight(dir, "/")
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	s := New()
	var wg sync.WaitGroup
	count := &incr{}
	errCh := make(chan error, len(files))
	for _, stat := range files {
		if strings.HasSuffix(stat.Name(), entityFileSuffix) {
			wg.Add(1)
			go func(path string, group string) {
				defer wg.Done()
				f, err := os.Open(path)
				if err != nil {
					errCh <- fmt.Errorf("error opening %v: %v\n", path, err)
					return
				}
				defer f.Close()

				err = AddFromReader(f, s, group)
				if err != nil {
					errCh <- fmt.Errorf("error reading from %v: %v\n", path, err)
					return
				}
				count.incr()
			}(fmt.Sprintf("%s/%s", dir, stat.Name()), strings.TrimSuffix(stat.Name(), entityFileSuffix))
		}
	}
	wg.Wait()
	close(errCh)

	for e := range errCh {
		if err == nil {
			err = e
		}
	}
	if err != nil {
		return nil, err
	}

	if count.n == 0 {
		return nil, errors.New("no entity files found")
	}
	return s, nil
}

// AddFromReader adds entities to the store under the group name from the io.Reader.
func AddFromReader(r io.Reader, store *Store, name string) error {
	s := bufio.NewScanner(r)
	for s.Scan() {
		store.Add(name, []rune(s.Text()))
	}
	return s.Err()
}

// Save writes the existing entities to disk under the given directory path (assumed
// to already exist). Each entity group becomes a file <group>.entities.csv.
func (s *Store) Save(dir string) error {
	s.RLock()
	defer s.RUnlock()

	dir = strings.TrimRight(dir, "/")
	for name, g := range s.groups {
		path := fmt.Sprintf("%s/%s", dir, strings.Replace(name, "/", "_", -1)+entityFileSuffix)
		f, err := os.Create(path)
		if err != nil {
			return err
		}
		defer f.Close()

		for _, entities := range g.entities {
			for _, e := range entities {
				f.WriteString(string(e) + "\n")
			}
		}
		f.Close()
	}
	return nil
}
<commit_msg>Discard empty entities on import.<commit_after>// Package fastentity provides text sequence identification in documents.
package fastentity

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"sync"
	"unicode"
)

var (
	// Maximum entity length.  NB: currently entities can be added which are longer
	// but they will be ignored in the search.
	// TODO: Maybe also error/warn on import?
	MaxEntityLen = 30
	// Number of entities to initially allocate when creating a Group.
	DefaultGroupSize = 1000
)

const (
	left  = 0
	right = 1
)

type pair [2]int

// Store is a collection of groups of entities.
type Store struct {
	sync.RWMutex // protects groups

	groups map[string]*group
}

type group struct {
	sync.RWMutex

	name     string
	entities map[string][][]rune
	maxLen   int
}

// Pops the last element and adds the new element to the front of stack.
func shift(n pair, s []pair) (pair, []pair) {
	if len(s) == 0 {
		return pair{}, append(s, n)
	}
	if len(s) == cap(s) {
		return s[0], append(s[1:], n)
	}
	return s[0], append(s, n)
}

// New creates a new Store of entity groups using the provided names.
func New(groups ...string) *Store {
	s := &Store{
		groups: make(map[string]*group, len(groups)),
	}
	for _, name := range groups {
		g := &group{
			name:     name,
			entities: make(map[string][][]rune, DefaultGroupSize),
		}
		s.groups[name] = g
	}
	return s
}

// Add ajoins the entities to the group identified by name.
func (s *Store) Add(name string, entities ...[]rune) {
	s.Lock()
	g, ok := s.groups[name]
	if !ok {
		g = &group{
			name:     name,
			entities: make(map[string][][]rune, DefaultGroupSize),
		}
		s.groups[name] = g
	}
	s.Unlock()

	g.Lock()
	for _, e := range entities {
		h := hash([]rune(e))
		g.entities[h] = append(g.entities[h], e)
		if len(e) > g.maxLen {
			g.maxLen = len(e)
		}
	}
	g.Unlock()
}

func hash(rs []rune) string {
	if len(rs) > 2 {
		return fmt.Sprintf("%s%s%s%03d", string(unicode.ToLower(rs[0])), string(unicode.ToLower(rs[1])), string(unicode.ToLower(rs[2])), len(rs))
	}
	if len(rs) > 1 {
		return fmt.Sprintf("%s%s%03d", string(unicode.ToLower(rs[0])), string(unicode.ToLower(rs[1])), len(rs))
	}
	return fmt.Sprintf("%s%03d", string(unicode.ToLower(rs[0])), len(rs))
}

// FindAll searches the input returning a maping group name -> found entities.
func (s *Store) FindAll(rs []rune) map[string][][]rune {
	result := make(map[string][][]rune, len(s.groups))
	for s, g := range s.groups {
		result[s] = g.Find(rs)
	}
	return result
}

// Find only the entities of a given type = "key"
func (g *group) Find(rs []rune) [][]rune {
	g.RLock()
	ents := find(rs, []*group{g})
	g.RUnlock()
	return ents[g.name]
}

// Lock free find for use internally
func find(rs []rune, groups []*group) map[string][][]rune {
	results := make(map[string][][]rune, len(groups))
	pairs := make([]pair, 0, 20)
	start := 0
	prevSpace := true // First char of sequence is legit
	space := false

	for off, r := range rs {
		// What are we looking at?
		space = unicode.IsPunct(r) || unicode.IsSpace(r)

		if prevSpace && !space {
			// Word is beginning at this rune
			start = off
		} else if space && !prevSpace {
			// Word is ending, shift the pairs stack
			_, pairs = shift(pair{start, off}, pairs)

			// Run the stack, check for entities working backwards from the current position
			if len(pairs) > 1 {
				p2 := pairs[len(pairs)-1]
				for i := len(pairs) - 1; i >= 0; i-- {
					p1 := pairs[i]
					if p2[right]-p1[left] > MaxEntityLen {
						break // Too long or short, can ignore it
					}
					for _, g := range groups {
						if p2[right]-p1[left] > g.maxLen {
							continue
						}
						if ents, ok := g.entities[hash(rs[p1[left]:p2[right]])]; ok {
							// We have at least one entity with this key
							for _, ent := range ents {
								if len(ent) != p2[right]-p1[left] {
									break
								}
								match := true
								for i, r := range ent {
									if unicode.ToLower(r) != unicode.ToLower(rs[p1[left]+i]) {
										match = false
										break
									}
								}
								if match {
									results[g.name] = append(results[g.name], rs[p1[left]:p2[right]])
								}
							}
						}
					}
				}
			}
		}

		// Mark prevSpace for the next loop
		if space {
			prevSpace = true
		} else {
			prevSpace = false
		}
	}
	return results
}

type incr struct {
	sync.Mutex
	n int
}

func (i *incr) incr() {
	i.Lock()
	i.n++
	i.Unlock()
}

var entityFileSuffix = ".entities.csv"

// FromDir creates a new Store by loading entity files from a given directory path. Any files
// contained in the directory with names matching <group>.entities.csv will be imported,
// and the entities added to the group <group>.
func FromDir(dir string) (*Store, error) {
	dir = strings.TrimRight(dir, "/")
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	s := New()
	var wg sync.WaitGroup
	count := &incr{}
	errCh := make(chan error, len(files))
	for _, stat := range files {
		if strings.HasSuffix(stat.Name(), entityFileSuffix) {
			wg.Add(1)
			go func(path string, group string) {
				defer wg.Done()
				f, err := os.Open(path)
				if err != nil {
					errCh <- fmt.Errorf("error opening %v: %v\n", path, err)
					return
				}
				defer f.Close()

				err = AddFromReader(f, s, group)
				if err != nil {
					errCh <- fmt.Errorf("error reading from %v: %v\n", path, err)
					return
				}
				count.incr()
			}(fmt.Sprintf("%s/%s", dir, stat.Name()), strings.TrimSuffix(stat.Name(), entityFileSuffix))
		}
	}
	wg.Wait()
	close(errCh)

	for e := range errCh {
		if err == nil {
			err = e
		}
	}
	if err != nil {
		return nil, err
	}

	if count.n == 0 {
		return nil, errors.New("no entity files found")
	}
	return s, nil
}

// AddFromReader adds entities to the store under the group name from the io.Reader.
func AddFromReader(r io.Reader, store *Store, name string) error {
	s := bufio.NewScanner(r)
	for s.Scan() {
		rt := []rune(s.Text())
		if len(rt) > 0 {
			store.Add(name, rt)
		}
	}
	return s.Err()
}

// Save writes the existing entities to disk under the given directory path (assumed
// to already exist). Each entity group becomes a file <group>.entities.csv.
func (s *Store) Save(dir string) error {
	s.RLock()
	defer s.RUnlock()

	dir = strings.TrimRight(dir, "/")
	for name, g := range s.groups {
		path := fmt.Sprintf("%s/%s", dir, strings.Replace(name, "/", "_", -1)+entityFileSuffix)
		f, err := os.Create(path)
		if err != nil {
			return err
		}
		defer f.Close()

		for _, entities := range g.entities {
			for _, e := range entities {
				f.WriteString(string(e) + "\n")
			}
		}
		f.Close()
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Alan Kang. All rights reserved.
// Chain of middlewares support for httpmux

package httpmux

import (
	"net/http"
)

type Chain struct {
	mws     []Handler // slice of middlewares
	h       Handler   // final handler of request
	nextIdx int
}

func NewChain() *Chain {
	return &Chain{}
}

func (c *Chain) AppendMiddlewares(mws ...Handler) *Chain {
	c.mws = append(c.mws, mws...)
	return c
}

func (c *Chain) Use(h Handler) *Chain {
	c.h = h
	return c
}

func (c *Chain) Next(w http.ResponseWriter, req *http.Request, ctx *Context) {
	var h Handler
	if c.nextIdx >= 0 && c.nextIdx < len(c.mws) {
		h = c.mws[c.nextIdx]
	} else if c.nextIdx == len(c.mws) {
		h = c.h
	} else {
		return
	}

	c.nextIdx++

	if h != nil {
		h.ServeHTTP(w, req, ctx)
	}
}

func (c *Chain) ServeHTTP(w http.ResponseWriter, req *http.Request, ctx *Context) {
	c.nextIdx = 0
	c.Next(w, req, ctx)
}
<commit_msg>prepend middlewares<commit_after>// Copyright (c) 2016 Alan Kang. All rights reserved.
// Chain of middlewares support for httpmux

package httpmux

import (
	"net/http"
)

type Chain struct {
	mws     []Handler // slice of middlewares
	h       Handler   // final handler of request
	nextIdx int
}

func NewChain() *Chain {
	return &Chain{}
}

func (c *Chain) PrependMiddlewares(mws ...Handler) *Chain {
	var m []Handler
	m = append(m, mws...)
	m = append(m, c.mws...)
	c.mws = m
	return c
}

func (c *Chain) AppendMiddlewares(mws ...Handler) *Chain {
	c.mws = append(c.mws, mws...)
	return c
}

func (c *Chain) Use(h Handler) *Chain {
	c.h = h
	return c
}

func (c *Chain) Next(w http.ResponseWriter, req *http.Request, ctx *Context) {
	var h Handler
	if c.nextIdx >= 0 && c.nextIdx < len(c.mws) {
		h = c.mws[c.nextIdx]
	} else if c.nextIdx == len(c.mws) {
		h = c.h
	} else {
		return
	}

	c.nextIdx++

	if h != nil {
		h.ServeHTTP(w, req, ctx)
	}
}

func (c *Chain) ServeHTTP(w http.ResponseWriter, req *http.Request, ctx *Context) {
	c.nextIdx = 0
	c.Next(w, req, ctx)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url   string
	text  string
	color string
}

type connection struct {
	protocol string
	host     string
	port     int
	address  string
}

func checkError(err error) bool {
	if err == nil {
		return false
	}
	log.Printf("error: %s", err)
	return true
}

func (c *connection) conn() (net.Conn, bool) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	errBool := checkError(err)
	return conn, errBool
}

func (a *alertMessage) sentSlack() {
	err := slackhookgo.Send(
		a.url,
		slackhookgo.NewSlackMessage(
			"username",
			"backup",
		).AddAttachment(
			slackhookgo.MessageAttachment{
				Color: a.color,
				Text:  a.text,
				Title: "<!channel>",
			},
		),
	)
	checkError(err)
}

func main() {
	var (
		k        string
		t        string
		lastE    bool
		protocol = flag.String("protocol", "tcp", "protocol tcp/udp")
		host     = flag.String("host", "ya.ru", "destination host")
		port     = flag.Uint("port", 80, "destination port")
		interval = flag.Uint("interval", 5, "interval check seconds")
		url      = flag.String("url", "", "hook url")
	)

	flag.Parse()

	for {
		c := connection{
			protocol: *protocol,
			address:  fmt.Sprintf("%s:%v", *host, *port),
		}
		conn, err := c.conn()
		if err != lastE {
			if err == false { // normal
				k = "good"
				t = "reachable"
				conn.Close()
			} else { // not normal
				k = "danger"
				t = "unreachable"
			}
			lastE = err // key of success
			am := alertMessage{
				color: k,
				text:  fmt.Sprintf("Destination host %s:%v %s\n", *host, *port, t),
				url:   *url,
			}
			am.sentSlack()
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<commit_msg>add flag, add check flag<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url     string
	text    string
	color   string
	channel string
}

type connection struct {
	protocol string
	address  string
}

func checkError(err error) bool {
	if err == nil {
		return false
	}
	log.Printf("error: %s", err)
	return true
}

func (c *connection) conn() (net.Conn, bool) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	errBool := checkError(err)
	return conn, errBool
}

func (a *alertMessage) sentSlack() {
	msg := slackhookgo.NewSlackMessage(
		"Alert",
		a.channel,
	).AddAttachment(
		slackhookgo.MessageAttachment{
			Color: a.color,
			Text:  a.text,
			Title: "<!channel>",
		},
	)
	msg.IconEmoji = ":exclamation:"
	err := slackhookgo.Send(a.url, msg)
	checkError(err)
}

func main() {
	var (
		color     string
		status    string
		lastState bool
		protocol  = flag.String("protocol", "tcp", "protocol tcp/udp")
		host      = flag.String("host", "", "destination host")
		port      = flag.Uint("port", 80, "destination port")
		interval  = flag.Uint("interval", 15, "interval check seconds")
		url       = flag.String("url", "", "hook url")
		slack     = flag.Bool("slack", false, "use -slack for send alert in slack")
		channel   = flag.String("channel", "general", "slack channel")
	)

	flag.Parse()

	if len(os.Args) == 1 {
		flag.PrintDefaults()
		os.Exit(1)
	} else if *host == "" {
		log.Fatal("param '-host' is empty")
	} else if *slack == true {
		if *url == "" {
			log.Fatal("param '-url' is empty")
		}
	}

	c := connection{
		protocol: *protocol,
		address:  fmt.Sprintf("%s:%v", *host, *port),
	}

	for {
		conn, err := c.conn()
		if err != lastState {
			if err == false { // normal
				conn.Close()
				color = "good"
				status = "reachable"
			} else { // not normal
				color = "danger"
				status = "unreachable"
			}
			lastState = err // key of success
			if *slack == true {
				am := alertMessage{
					channel: *channel,
					color:   color,
					text:    fmt.Sprintf("Destination host %s:%v %s\n", *host, *port, status),
					url:     *url,
				}
				am.sentSlack()
			}
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<|endoftext|>