<commit_before>// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
// Giorgis (habtom@giorgis.io)

//Package avcodec contains the codecs (decoders and encoders) provided by the libavcodec library
//Provides some generic global options, which can be set on all the encoders and decoders.
package avcodec

//#cgo pkg-config: libavformat libavcodec libavutil libswresample
//#include <stdio.h>
//#include <stdlib.h>
//#include <inttypes.h>
//#include <stdint.h>
//#include <string.h>
//#include <libavformat/avformat.h>
//#include <libavcodec/avcodec.h>
//#include <libavutil/avutil.h>
import "C"
import (
	"unsafe"
	"github.com/giorgisio/goav/avutil"
)

type (
	Codec                         C.struct_AVCodec
	CodecContext                  C.struct_AVCodecContext
	Descriptor                    C.struct_AVCodecDescriptor
	Parser                        C.struct_AVCodecParser
	ParserContext                 C.struct_AVCodecParserContext
	Dictionary                    C.struct_AVDictionary
	Frame                         C.struct_AVFrame
	Packet                        C.struct_AVPacket
	BitStreamFilter               C.struct_AVBitStreamFilter
	BitStreamFilterContext        C.struct_AVBitStreamFilterContext
	Rational                      C.struct_AVRational
	Class                         C.struct_AVClass
	AvHWAccel                     C.struct_AVHWAccel
	AvPacketSideData              C.struct_AVPacketSideData
	AvPanScan                     C.struct_AVPanScan
	Picture                       C.struct_AVPicture
	AvProfile                     C.struct_AVProfile
	AvSubtitle                    C.struct_AVSubtitle
	AvSubtitleRect                C.struct_AVSubtitleRect
	RcOverride                    C.struct_RcOverride
	AvBufferRef                   C.struct_AVBufferRef
	AvAudioServiceType            C.enum_AVAudioServiceType
	AvChromaLocation              C.enum_AVChromaLocation
	CodecId                       C.enum_AVCodecID
	AvColorPrimaries              C.enum_AVColorPrimaries
	AvColorRange                  C.enum_AVColorRange
	AvColorSpace                  C.enum_AVColorSpace
	AvColorTransferCharacteristic C.enum_AVColorTransferCharacteristic
	AvDiscard                     C.enum_AVDiscard
	AvFieldOrder                  C.enum_AVFieldOrder
	AvPacketSideDataType          C.enum_AVPacketSideDataType
	PixelFormat                   C.enum_AVPixelFormat
	AvSampleFormat                C.enum_AVSampleFormat
)

func (c *Codec) AvCodecGetMaxLowres() int {
	return int(C.av_codec_get_max_lowres((*C.struct_AVCodec)(c)))
}

//If c is NULL, returns the first registered codec, if c is non-NULL,
func (c *Codec) AvCodecNext() *Codec {
	return (*Codec)(C.av_codec_next((*C.struct_AVCodec)(c)))
}

//Register the codec codec and initialize libavcodec.
func (c *Codec) AvcodecRegister() {
	C.avcodec_register((*C.struct_AVCodec)(c))
}

//Return a name for the specified profile, if available.
func (c *Codec) AvGetProfileName(p int) string {
	return C.GoString(C.av_get_profile_name((*C.struct_AVCodec)(c), C.int(p)))
}

//Allocate an Context and set its fields to default values.
func (c *Codec) AvcodecAllocContext3() *CodecContext {
	return (*CodecContext)(C.avcodec_alloc_context3((*C.struct_AVCodec)(c)))
}

func (c *Codec) AvCodecIsEncoder() int {
	return int(C.av_codec_is_encoder((*C.struct_AVCodec)(c)))
}

func (c *Codec) AvCodecIsDecoder() int {
	return int(C.av_codec_is_decoder((*C.struct_AVCodec)(c)))
}

//Same behaviour av_fast_malloc but the buffer has additional FF_INPUT_BUFFER_PADDING_SIZE at the end which will always be 0.
func AvFastPaddedMalloc(p unsafe.Pointer, s *uint, t uintptr) {
	C.av_fast_padded_malloc(p, (*C.uint)(unsafe.Pointer(s)), (C.size_t)(t))
}

//Return the LIBAvCODEC_VERSION_INT constant.
func AvcodecVersion() uint {
	return uint(C.avcodec_version())
}

//Return the libavcodec build-time configuration.
func AvcodecConfiguration() string {
	return C.GoString(C.avcodec_configuration())

}

//Return the libavcodec license.
func AvcodecLicense() string {
	return C.GoString(C.avcodec_license())
}

//Register all the codecs, parsers and bitstream filters which were enabled at configuration time.
func AvcodecRegisterAll() {
	C.avcodec_register_all()
}

//Get the Class for Context.
func AvcodecGetClass() *Class {
	return (*Class)(C.avcodec_get_class())
}

//Get the Class for Frame.
func AvcodecGetFrameClass() *Class {
	return (*Class)(C.avcodec_get_frame_class())
}

//Get the Class for AvSubtitleRect.
func AvcodecGetSubtitleRectClass() *Class {
	return (*Class)(C.avcodec_get_subtitle_rect_class())
}

//Free all allocated data in the given subtitle struct.
func AvsubtitleFree(s *AvSubtitle) {
	C.avsubtitle_free((*C.struct_AVSubtitle)(s))
}

//Pack a dictionary for use in side_data.
func AvPacketPackDictionary(d *Dictionary, s *int) *uint8 {
	return (*uint8)(C.av_packet_pack_dictionary((*C.struct_AVDictionary)(d), (*C.int)(unsafe.Pointer(s))))
}

//Unpack a dictionary from side_data.
func AvPacketUnpackDictionary(d *uint8, s int, dt **Dictionary) int {
	return int(C.av_packet_unpack_dictionary((*C.uint8_t)(d), C.int(s), (**C.struct_AVDictionary)(unsafe.Pointer(dt))))
}

//Find a registered decoder with a matching codec ID.
func AvcodecFindDecoder(id CodecId) *Codec {
	return (*Codec)(C.avcodec_find_decoder((C.enum_AVCodecID)(id)))
}

//Find a registered decoder with the specified name.
func AvcodecFindDecoderByName(n string) *Codec {
	return (*Codec)(C.avcodec_find_decoder_by_name(C.CString(n)))
}

//Converts AvChromaLocation to swscale x/y chroma position.
func AvcodecEnumToChromaPos(x, y *int, l AvChromaLocation) int {
	return int(C.avcodec_enum_to_chroma_pos((*C.int)(unsafe.Pointer(x)), (*C.int)(unsafe.Pointer(y)), (C.enum_AVChromaLocation)(l)))
}

//Converts swscale x/y chroma position to AvChromaLocation.
func AvcodecChromaPosToEnum(x, y int) AvChromaLocation {
	return (AvChromaLocation)(C.avcodec_chroma_pos_to_enum(C.int(x), C.int(y)))
}

//Find a registered encoder with a matching codec ID.
func AvcodecFindEncoder(id CodecId) *Codec {
	return (*Codec)(C.avcodec_find_encoder((C.enum_AVCodecID)(id)))
}

//Find a registered encoder with the specified name.
func AvcodecFindEncoderByName(c string) *Codec {
	return (*Codec)(C.avcodec_find_encoder_by_name(C.CString(c)))
}

//Put a string representing the codec tag codec_tag in buf.
func AvGetCodecTagString(b string, bf uintptr, c uint) uintptr {
	return uintptr(C.av_get_codec_tag_string(C.CString(b), C.size_t(bf), C.uint(c)))
}

func AvcodecString(b string, bs int, ctxt *CodecContext, e int) {
	C.avcodec_string(C.CString(b), C.int(bs), (*C.struct_AVCodecContext)(ctxt), C.int(e))
}

//Fill Frame audio data and linesize pointers.
func AvcodecFillAudioFrame(f *Frame, c int, s AvSampleFormat, b *uint8, bs, a int) int {
	return int(C.avcodec_fill_audio_frame((*C.struct_AVFrame)(f), C.int(c), (C.enum_AVSampleFormat)(s), (*C.uint8_t)(b), C.int(bs), C.int(a)))
}

//Return codec bits per sample.
func AvGetBitsPerSample(c CodecId) int {
	return int(C.av_get_bits_per_sample((C.enum_AVCodecID)(c)))
}

//Return the PCM codec associated with a sample format.
func AvGetPcmCodec(f AvSampleFormat, b int) CodecId {
	return (CodecId)(C.av_get_pcm_codec((C.enum_AVSampleFormat)(f), C.int(b)))
}

//Return codec bits per sample.
func AvGetExactBitsPerSample(c CodecId) int {
	return int(C.av_get_exact_bits_per_sample((C.enum_AVCodecID)(c)))
}

//Same behaviour av_fast_padded_malloc except that buffer will always be 0-initialized after call.
func AvFastPaddedMallocz(p unsafe.Pointer, s *uint, t uintptr) {
	C.av_fast_padded_mallocz(p, (*C.uint)(unsafe.Pointer(s)), (C.size_t)(t))
}

//Encode extradata length to a buffer.
func AvXiphlacing(s *string, v uint) uint {
	return uint(C.av_xiphlacing((*C.uchar)(unsafe.Pointer(s)), (C.uint)(v)))
}

//If hwaccel is NULL, returns the first registered hardware accelerator, if hwaccel is non-NULL,
//returns the next registered hardware accelerator after hwaccel, or NULL if hwaccel is the last one.
func (a *AvHWAccel) AvHwaccelNext() *AvHWAccel {
	return (*AvHWAccel)(C.av_hwaccel_next((*C.struct_AVHWAccel)(a)))
}

//Get the type of the given codec.
func AvcodecGetType(c CodecId) avutil.MediaType {
	return (avutil.MediaType)(C.avcodec_get_type((C.enum_AVCodecID)(c)))
}

//Get the name of a codec.
func AvcodecGetName(d CodecId) string {
	return C.GoString(C.avcodec_get_name((C.enum_AVCodecID)(d)))
}

//const Descriptor *avcodec_descriptor_get (enum CodecId id)
func AvcodecDescriptorGet(id CodecId) *Descriptor {
	return (*Descriptor)(C.avcodec_descriptor_get((C.enum_AVCodecID)(id)))
}

//Iterate over all codec descriptors known to libavcodec.
func (d *Descriptor) AvcodecDescriptorNext() *Descriptor {
	return (*Descriptor)(C.avcodec_descriptor_next((*C.struct_AVCodecDescriptor)(d)))
}

func AvcodecDescriptorGetByName(n string) *Descriptor {
	return (*Descriptor)(C.avcodec_descriptor_get_by_name(C.CString(n)))
}
<commit_msg>[AvCodec] Implement getName, getLongName for Codec<commit_after>// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
// Giorgis (habtom@giorgis.io)

//Package avcodec contains the codecs (decoders and encoders) provided by the libavcodec library
//Provides some generic global options, which can be set on all the encoders and decoders.
package avcodec

//#cgo pkg-config: libavformat libavcodec libavutil libswresample
//#include <stdio.h>
//#include <stdlib.h>
//#include <inttypes.h>
//#include <stdint.h>
//#include <string.h>
//#include <libavformat/avformat.h>
//#include <libavcodec/avcodec.h>
//#include <libavutil/avutil.h>
import "C"
import (
	"unsafe"
	"github.com/giorgisio/goav/avutil"
)

type (
	Codec                         C.struct_AVCodec
	CodecContext                  C.struct_AVCodecContext
	Descriptor                    C.struct_AVCodecDescriptor
	Parser                        C.struct_AVCodecParser
	ParserContext                 C.struct_AVCodecParserContext
	Dictionary                    C.struct_AVDictionary
	Frame                         C.struct_AVFrame
	Packet                        C.struct_AVPacket
	BitStreamFilter               C.struct_AVBitStreamFilter
	BitStreamFilterContext        C.struct_AVBitStreamFilterContext
	Rational                      C.struct_AVRational
	Class                         C.struct_AVClass
	AvHWAccel                     C.struct_AVHWAccel
	AvPacketSideData              C.struct_AVPacketSideData
	AvPanScan                     C.struct_AVPanScan
	Picture                       C.struct_AVPicture
	AvProfile                     C.struct_AVProfile
	AvSubtitle                    C.struct_AVSubtitle
	AvSubtitleRect                C.struct_AVSubtitleRect
	RcOverride                    C.struct_RcOverride
	AvBufferRef                   C.struct_AVBufferRef
	AvAudioServiceType            C.enum_AVAudioServiceType
	AvChromaLocation              C.enum_AVChromaLocation
	CodecId                       C.enum_AVCodecID
	AvColorPrimaries              C.enum_AVColorPrimaries
	AvColorRange                  C.enum_AVColorRange
	AvColorSpace                  C.enum_AVColorSpace
	AvColorTransferCharacteristic C.enum_AVColorTransferCharacteristic
	AvDiscard                     C.enum_AVDiscard
	AvFieldOrder                  C.enum_AVFieldOrder
	AvPacketSideDataType          C.enum_AVPacketSideDataType
	PixelFormat                   C.enum_AVPixelFormat
	AvSampleFormat                C.enum_AVSampleFormat
)

func (c *Codec) AvCodecGetMaxLowres() int {
	return int(C.av_codec_get_max_lowres((*C.struct_AVCodec)(c)))
}

//If c is NULL, returns the first registered codec, if c is non-NULL,
func (c *Codec) AvCodecNext() *Codec {
	return (*Codec)(C.av_codec_next((*C.struct_AVCodec)(c)))
}

//Register the codec codec and initialize libavcodec.
func (c *Codec) AvcodecRegister() {
	C.avcodec_register((*C.struct_AVCodec)(c))
}

//Return a name for the specified profile, if available.
func (c *Codec) AvGetProfileName(p int) string {
	return C.GoString(C.av_get_profile_name((*C.struct_AVCodec)(c), C.int(p)))
}

//Allocate an Context and set its fields to default values.
func (c *Codec) AvcodecAllocContext3() *CodecContext {
	return (*CodecContext)(C.avcodec_alloc_context3((*C.struct_AVCodec)(c)))
}

func (c *Codec) AvCodecIsEncoder() int {
	return int(C.av_codec_is_encoder((*C.struct_AVCodec)(c)))
}

func (c *Codec) AvCodecIsDecoder() int {
	return int(C.av_codec_is_decoder((*C.struct_AVCodec)(c)))
}

func (c *Codec) Name() string {
	return C.GoString(c.name)
}

func (c *Codec) LongName() string {
	return C.GoString(c.long_name)
}

//Same behaviour av_fast_malloc but the buffer has additional FF_INPUT_BUFFER_PADDING_SIZE at the end which will always be 0.
func AvFastPaddedMalloc(p unsafe.Pointer, s *uint, t uintptr) {
	C.av_fast_padded_malloc(p, (*C.uint)(unsafe.Pointer(s)), (C.size_t)(t))
}

//Return the LIBAvCODEC_VERSION_INT constant.
func AvcodecVersion() uint {
	return uint(C.avcodec_version())
}

//Return the libavcodec build-time configuration.
func AvcodecConfiguration() string {
	return C.GoString(C.avcodec_configuration())

}

//Return the libavcodec license.
func AvcodecLicense() string {
	return C.GoString(C.avcodec_license())
}

//Register all the codecs, parsers and bitstream filters which were enabled at configuration time.
func AvcodecRegisterAll() {
	C.avcodec_register_all()
}

//Get the Class for Context.
func AvcodecGetClass() *Class {
	return (*Class)(C.avcodec_get_class())
}

//Get the Class for Frame.
func AvcodecGetFrameClass() *Class {
	return (*Class)(C.avcodec_get_frame_class())
}

//Get the Class for AvSubtitleRect.
func AvcodecGetSubtitleRectClass() *Class {
	return (*Class)(C.avcodec_get_subtitle_rect_class())
}

//Free all allocated data in the given subtitle struct.
func AvsubtitleFree(s *AvSubtitle) {
	C.avsubtitle_free((*C.struct_AVSubtitle)(s))
}

//Pack a dictionary for use in side_data.
func AvPacketPackDictionary(d *Dictionary, s *int) *uint8 {
	return (*uint8)(C.av_packet_pack_dictionary((*C.struct_AVDictionary)(d), (*C.int)(unsafe.Pointer(s))))
}

//Unpack a dictionary from side_data.
func AvPacketUnpackDictionary(d *uint8, s int, dt **Dictionary) int {
	return int(C.av_packet_unpack_dictionary((*C.uint8_t)(d), C.int(s), (**C.struct_AVDictionary)(unsafe.Pointer(dt))))
}

//Find a registered decoder with a matching codec ID.
func AvcodecFindDecoder(id CodecId) *Codec {
	return (*Codec)(C.avcodec_find_decoder((C.enum_AVCodecID)(id)))
}

//Find a registered decoder with the specified name.
func AvcodecFindDecoderByName(n string) *Codec {
	return (*Codec)(C.avcodec_find_decoder_by_name(C.CString(n)))
}

//Converts AvChromaLocation to swscale x/y chroma position.
func AvcodecEnumToChromaPos(x, y *int, l AvChromaLocation) int {
	return int(C.avcodec_enum_to_chroma_pos((*C.int)(unsafe.Pointer(x)), (*C.int)(unsafe.Pointer(y)), (C.enum_AVChromaLocation)(l)))
}

//Converts swscale x/y chroma position to AvChromaLocation.
func AvcodecChromaPosToEnum(x, y int) AvChromaLocation {
	return (AvChromaLocation)(C.avcodec_chroma_pos_to_enum(C.int(x), C.int(y)))
}

//Find a registered encoder with a matching codec ID.
func AvcodecFindEncoder(id CodecId) *Codec {
	return (*Codec)(C.avcodec_find_encoder((C.enum_AVCodecID)(id)))
}

//Find a registered encoder with the specified name.
func AvcodecFindEncoderByName(c string) *Codec {
	return (*Codec)(C.avcodec_find_encoder_by_name(C.CString(c)))
}

//Put a string representing the codec tag codec_tag in buf.
func AvGetCodecTagString(b string, bf uintptr, c uint) uintptr {
	return uintptr(C.av_get_codec_tag_string(C.CString(b), C.size_t(bf), C.uint(c)))
}

func AvcodecString(b string, bs int, ctxt *CodecContext, e int) {
	C.avcodec_string(C.CString(b), C.int(bs), (*C.struct_AVCodecContext)(ctxt), C.int(e))
}

//Fill Frame audio data and linesize pointers.
func AvcodecFillAudioFrame(f *Frame, c int, s AvSampleFormat, b *uint8, bs, a int) int {
	return int(C.avcodec_fill_audio_frame((*C.struct_AVFrame)(f), C.int(c), (C.enum_AVSampleFormat)(s), (*C.uint8_t)(b), C.int(bs), C.int(a)))
}

//Return codec bits per sample.
func AvGetBitsPerSample(c CodecId) int {
	return int(C.av_get_bits_per_sample((C.enum_AVCodecID)(c)))
}

//Return the PCM codec associated with a sample format.
func AvGetPcmCodec(f AvSampleFormat, b int) CodecId {
	return (CodecId)(C.av_get_pcm_codec((C.enum_AVSampleFormat)(f), C.int(b)))
}

//Return codec bits per sample.
func AvGetExactBitsPerSample(c CodecId) int {
	return int(C.av_get_exact_bits_per_sample((C.enum_AVCodecID)(c)))
}

//Same behaviour av_fast_padded_malloc except that buffer will always be 0-initialized after call.
func AvFastPaddedMallocz(p unsafe.Pointer, s *uint, t uintptr) {
	C.av_fast_padded_mallocz(p, (*C.uint)(unsafe.Pointer(s)), (C.size_t)(t))
}

//Encode extradata length to a buffer.
func AvXiphlacing(s *string, v uint) uint {
	return uint(C.av_xiphlacing((*C.uchar)(unsafe.Pointer(s)), (C.uint)(v)))
}

//If hwaccel is NULL, returns the first registered hardware accelerator, if hwaccel is non-NULL,
//returns the next registered hardware accelerator after hwaccel, or NULL if hwaccel is the last one.
func (a *AvHWAccel) AvHwaccelNext() *AvHWAccel {
	return (*AvHWAccel)(C.av_hwaccel_next((*C.struct_AVHWAccel)(a)))
}

//Get the type of the given codec.
func AvcodecGetType(c CodecId) avutil.MediaType {
	return (avutil.MediaType)(C.avcodec_get_type((C.enum_AVCodecID)(c)))
}

//Get the name of a codec.
func AvcodecGetName(d CodecId) string {
	return C.GoString(C.avcodec_get_name((C.enum_AVCodecID)(d)))
}

//const Descriptor *avcodec_descriptor_get (enum CodecId id)
func AvcodecDescriptorGet(id CodecId) *Descriptor {
	return (*Descriptor)(C.avcodec_descriptor_get((C.enum_AVCodecID)(id)))
}

//Iterate over all codec descriptors known to libavcodec.
func (d *Descriptor) AvcodecDescriptorNext() *Descriptor {
	return (*Descriptor)(C.avcodec_descriptor_next((*C.struct_AVCodecDescriptor)(d)))
}

func AvcodecDescriptorGetByName(n string) *Descriptor {
	return (*Descriptor)(C.avcodec_descriptor_get_by_name(C.CString(n)))
}
<|endoftext|>


<commit_before>package storage

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"mime"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/images"
	"github.com/chrislusf/seaweedfs/weed/operation"
)

const (
	NeedleHeaderSize      = 16 //should never change this
	NeedlePaddingSize     = 8
	NeedleChecksumSize    = 4
	MaxPossibleVolumeSize = 4 * 1024 * 1024 * 1024 * 8
	TombstoneFileSize     = math.MaxUint32
	PairNamePrefix        = "Seaweed-"
)

/*
* A Needle means a uploaded and stored file.
* Needle file size is limited to 4GB for now.
 */
type Needle struct {
	Cookie uint32 `comment:"random number to mitigate brute force lookups"`
	Id     uint64 `comment:"needle id"`
	Size   uint32 `comment:"sum of DataSize,Data,NameSize,Name,MimeSize,Mime"`

	DataSize     uint32 `comment:"Data size"` //version2
	Data         []byte `comment:"The actual file data"`
	Flags        byte   `comment:"boolean flags"` //version2
	NameSize     uint8  //version2
	Name         []byte `comment:"maximum 256 characters"` //version2
	MimeSize     uint8  //version2
	Mime         []byte `comment:"maximum 256 characters"` //version2
	PairsSize    uint16 //version2
	Pairs        []byte `comment:"additional name value pairs, json format, maximum 64kB"`
	LastModified uint64 //only store LastModifiedBytesLength bytes, which is 5 bytes to disk
	Ttl          *TTL

	Checksum CRC    `comment:"CRC32 to check integrity"`
	Padding  []byte `comment:"Aligned to 8 bytes"`

	rawBlock *Block // underlying supporing []byte, fetched and released into a pool
}

func (n *Needle) String() (str string) {
	str = fmt.Sprintf("Cookie:%d, Id:%d, Size:%d, DataSize:%d, Name: %s, Mime: %s", n.Cookie, n.Id, n.Size, n.DataSize, n.Name, n.Mime)
	return
}

func ParseUpload(r *http.Request) (
	fileName string, data []byte, mimeType string, pairMap map[string]string, isGzipped bool,
	modifiedTime uint64, ttl *TTL, isChunkedFile bool, e error) {
	pairMap = make(map[string]string)
	for k, v := range r.Header {
		if len(v) > 0 && strings.HasPrefix(k, PairNamePrefix) {
			pairMap[k] = v[0]
		}
	}

	form, fe := r.MultipartReader()
	if fe != nil {
		glog.V(0).Infoln("MultipartReader [ERROR]", fe)
		e = fe
		return
	}

	//first multi-part item
	part, fe := form.NextPart()
	if fe != nil {
		glog.V(0).Infoln("Reading Multi part [ERROR]", fe)
		e = fe
		return
	}

	fileName = part.FileName()
	if fileName != "" {
		fileName = path.Base(fileName)
	}

	data, e = ioutil.ReadAll(part)
	if e != nil {
		glog.V(0).Infoln("Reading Content [ERROR]", e)
		return
	}

	//if the filename is empty string, do a search on the other multi-part items
	for fileName == "" {
		part2, fe := form.NextPart()
		if fe != nil {
			break // no more or on error, just safely break
		}

		fName := part2.FileName()

		//found the first <file type> multi-part has filename
		if fName != "" {
			data2, fe2 := ioutil.ReadAll(part2)
			if fe2 != nil {
				glog.V(0).Infoln("Reading Content [ERROR]", fe2)
				e = fe2
				return
			}

			//update
			data = data2
			fileName = path.Base(fName)
			break
		}
	}

	isChunkedFile, _ = strconv.ParseBool(r.FormValue("cm"))

	if !isChunkedFile {

		dotIndex := strings.LastIndex(fileName, ".")
		ext, mtype := "", ""
		if dotIndex > 0 {
			ext = strings.ToLower(fileName[dotIndex:])
			mtype = mime.TypeByExtension(ext)
		}
		contentType := part.Header.Get("Content-Type")
		if contentType != "" && mtype != contentType {
			mimeType = contentType //only return mime type if not deductable
			mtype = contentType
		}

		if part.Header.Get("Content-Encoding") == "gzip" {
			isGzipped = true
		} else if operation.IsGzippable(ext, mtype) {
			if data, e = operation.GzipData(data); e != nil {
				return
			}
			isGzipped = true
		}
		if ext == ".gz" {
			isGzipped = true
		}
		if strings.HasSuffix(fileName, ".gz") &&
			!strings.HasSuffix(fileName, ".tar.gz") {
			fileName = fileName[:len(fileName)-3]
		}
	}
	modifiedTime, _ = strconv.ParseUint(r.FormValue("ts"), 10, 64)
	ttl, _ = ReadTTL(r.FormValue("ttl"))

	return
}
func NewNeedle(r *http.Request, fixJpgOrientation bool) (n *Needle, e error) {
	var pairMap map[string]string
	fname, mimeType, isGzipped, isChunkedFile := "", "", false, false
	n = new(Needle)
	fname, n.Data, mimeType, pairMap, isGzipped, n.LastModified, n.Ttl, isChunkedFile, e = ParseUpload(r)
	if e != nil {
		return
	}
	if len(fname) < 256 {
		n.Name = []byte(fname)
		n.SetHasName()
	}
	if len(mimeType) < 256 {
		n.Mime = []byte(mimeType)
		n.SetHasMime()
	}
	if len(pairMap) != 0 {
		trimmedPairMap := make(map[string]string)
		for k, v := range pairMap {
			trimmedPairMap[k[len(PairNamePrefix):]] = v
		}

		pairs, _ := json.Marshal(trimmedPairMap)
		if len(pairs) < 65536 {
			n.Pairs = pairs
			n.PairsSize = uint16(len(pairs))
			n.SetHasPairs()
		}
	}
	if isGzipped {
		n.SetGzipped()
	}
	if n.LastModified == 0 {
		n.LastModified = uint64(time.Now().Unix())
	}
	n.SetHasLastModifiedDate()
	if n.Ttl != EMPTY_TTL {
		n.SetHasTtl()
	}

	if isChunkedFile {
		n.SetIsChunkManifest()
	}

	if fixJpgOrientation {
		loweredName := strings.ToLower(fname)
		if mimeType == "image/jpeg" || strings.HasSuffix(loweredName, ".jpg") || strings.HasSuffix(loweredName, ".jpeg") {
			n.Data = images.FixJpgOrientation(n.Data)
		}
	}

	n.Checksum = NewCRC(n.Data)

	commaSep := strings.LastIndex(r.URL.Path, ",")
	dotSep := strings.LastIndex(r.URL.Path, ".")
	fid := r.URL.Path[commaSep+1:]
	if dotSep > 0 {
		fid = r.URL.Path[commaSep+1 : dotSep]
	}

	e = n.ParsePath(fid)

	return
}
func (n *Needle) ParsePath(fid string) (err error) {
	length := len(fid)
	if length <= 8 {
		return fmt.Errorf("Invalid fid: %s", fid)
	}
	delta := ""
	deltaIndex := strings.LastIndex(fid, "_")
	if deltaIndex > 0 {
		fid, delta = fid[0:deltaIndex], fid[deltaIndex+1:]
	}
	n.Id, n.Cookie, err = ParseKeyHash(fid)
	if err != nil {
		return err
	}
	if delta != "" {
		if d, e := strconv.ParseUint(delta, 10, 64); e == nil {
			n.Id += d
		} else {
			return e
		}
	}
	return err
}

func ParseKeyHash(key_hash_string string) (uint64, uint32, error) {
	if len(key_hash_string) <= 8 {
		return 0, 0, fmt.Errorf("KeyHash is too short.")
	}
	if len(key_hash_string) > 24 {
		return 0, 0, fmt.Errorf("KeyHash is too long.")
	}
	split := len(key_hash_string) - 8
	key, err := strconv.ParseUint(key_hash_string[:split], 16, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("Parse key error: %v", err)
	}
	hash, err := strconv.ParseUint(key_hash_string[split:], 16, 32)
	if err != nil {
		return 0, 0, fmt.Errorf("Parse hash error: %v", err)
	}
	return key, uint32(hash), nil
}
<commit_msg>simplify the gzip optimization logic<commit_after>package storage

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"mime"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/images"
	"github.com/chrislusf/seaweedfs/weed/operation"
)

const (
	NeedleHeaderSize      = 16 //should never change this
	NeedlePaddingSize     = 8
	NeedleChecksumSize    = 4
	MaxPossibleVolumeSize = 4 * 1024 * 1024 * 1024 * 8
	TombstoneFileSize     = math.MaxUint32
	PairNamePrefix        = "Seaweed-"
)

/*
* A Needle means a uploaded and stored file.
* Needle file size is limited to 4GB for now.
 */
type Needle struct {
	Cookie uint32 `comment:"random number to mitigate brute force lookups"`
	Id     uint64 `comment:"needle id"`
	Size   uint32 `comment:"sum of DataSize,Data,NameSize,Name,MimeSize,Mime"`

	DataSize     uint32 `comment:"Data size"` //version2
	Data         []byte `comment:"The actual file data"`
	Flags        byte   `comment:"boolean flags"` //version2
	NameSize     uint8  //version2
	Name         []byte `comment:"maximum 256 characters"` //version2
	MimeSize     uint8  //version2
	Mime         []byte `comment:"maximum 256 characters"` //version2
	PairsSize    uint16 //version2
	Pairs        []byte `comment:"additional name value pairs, json format, maximum 64kB"`
	LastModified uint64 //only store LastModifiedBytesLength bytes, which is 5 bytes to disk
	Ttl          *TTL

	Checksum CRC    `comment:"CRC32 to check integrity"`
	Padding  []byte `comment:"Aligned to 8 bytes"`

	rawBlock *Block // underlying supporing []byte, fetched and released into a pool
}

func (n *Needle) String() (str string) {
	str = fmt.Sprintf("Cookie:%d, Id:%d, Size:%d, DataSize:%d, Name: %s, Mime: %s", n.Cookie, n.Id, n.Size, n.DataSize, n.Name, n.Mime)
	return
}

func ParseUpload(r *http.Request) (
	fileName string, data []byte, mimeType string, pairMap map[string]string, isGzipped bool,
	modifiedTime uint64, ttl *TTL, isChunkedFile bool, e error) {
	pairMap = make(map[string]string)
	for k, v := range r.Header {
		if len(v) > 0 && strings.HasPrefix(k, PairNamePrefix) {
			pairMap[k] = v[0]
		}
	}

	form, fe := r.MultipartReader()
	if fe != nil {
		glog.V(0).Infoln("MultipartReader [ERROR]", fe)
		e = fe
		return
	}

	//first multi-part item
	part, fe := form.NextPart()
	if fe != nil {
		glog.V(0).Infoln("Reading Multi part [ERROR]", fe)
		e = fe
		return
	}

	fileName = part.FileName()
	if fileName != "" {
		fileName = path.Base(fileName)
	}

	data, e = ioutil.ReadAll(part)
	if e != nil {
		glog.V(0).Infoln("Reading Content [ERROR]", e)
		return
	}

	//if the filename is empty string, do a search on the other multi-part items
	for fileName == "" {
		part2, fe := form.NextPart()
		if fe != nil {
			break // no more or on error, just safely break
		}

		fName := part2.FileName()

		//found the first <file type> multi-part has filename
		if fName != "" {
			data2, fe2 := ioutil.ReadAll(part2)
			if fe2 != nil {
				glog.V(0).Infoln("Reading Content [ERROR]", fe2)
				e = fe2
				return
			}

			//update
			data = data2
			fileName = path.Base(fName)
			break
		}
	}

	isChunkedFile, _ = strconv.ParseBool(r.FormValue("cm"))

	if !isChunkedFile {

		dotIndex := strings.LastIndex(fileName, ".")
		ext, mtype := "", ""
		if dotIndex > 0 {
			ext = strings.ToLower(fileName[dotIndex:])
			mtype = mime.TypeByExtension(ext)
		}
		contentType := part.Header.Get("Content-Type")
		if contentType != "" && mtype != contentType {
			mimeType = contentType //only return mime type if not deductable
			mtype = contentType
		}

		if part.Header.Get("Content-Encoding") == "gzip" {
			isGzipped = true
		} else if operation.IsGzippable(ext, mtype) {
			if data, e = operation.GzipData(data); e != nil {
				return
			}
			isGzipped = true
		}
		if ext == ".gz" {
			if strings.HasSuffix(fileName, ".css.gz") ||
				strings.HasSuffix(fileName, ".html.gz") ||
				strings.HasSuffix(fileName, ".txt.gz") ||
				strings.HasSuffix(fileName, ".js.gz") {
				fileName = fileName[:len(fileName)-3]
				isGzipped = true
			}
		}
	}
	modifiedTime, _ = strconv.ParseUint(r.FormValue("ts"), 10, 64)
	ttl, _ = ReadTTL(r.FormValue("ttl"))

	return
}
func NewNeedle(r *http.Request, fixJpgOrientation bool) (n *Needle, e error) {
	var pairMap map[string]string
	fname, mimeType, isGzipped, isChunkedFile := "", "", false, false
	n = new(Needle)
	fname, n.Data, mimeType, pairMap, isGzipped, n.LastModified, n.Ttl, isChunkedFile, e = ParseUpload(r)
	if e != nil {
		return
	}
	if len(fname) < 256 {
		n.Name = []byte(fname)
		n.SetHasName()
	}
	if len(mimeType) < 256 {
		n.Mime = []byte(mimeType)
		n.SetHasMime()
	}
	if len(pairMap) != 0 {
		trimmedPairMap := make(map[string]string)
		for k, v := range pairMap {
			trimmedPairMap[k[len(PairNamePrefix):]] = v
		}

		pairs, _ := json.Marshal(trimmedPairMap)
		if len(pairs) < 65536 {
			n.Pairs = pairs
			n.PairsSize = uint16(len(pairs))
			n.SetHasPairs()
		}
	}
	if isGzipped {
		n.SetGzipped()
	}
	if n.LastModified == 0 {
		n.LastModified = uint64(time.Now().Unix())
	}
	n.SetHasLastModifiedDate()
	if n.Ttl != EMPTY_TTL {
		n.SetHasTtl()
	}

	if isChunkedFile {
		n.SetIsChunkManifest()
	}

	if fixJpgOrientation {
		loweredName := strings.ToLower(fname)
		if mimeType == "image/jpeg" || strings.HasSuffix(loweredName, ".jpg") || strings.HasSuffix(loweredName, ".jpeg") {
			n.Data = images.FixJpgOrientation(n.Data)
		}
	}

	n.Checksum = NewCRC(n.Data)

	commaSep := strings.LastIndex(r.URL.Path, ",")
	dotSep := strings.LastIndex(r.URL.Path, ".")
	fid := r.URL.Path[commaSep+1:]
	if dotSep > 0 {
		fid = r.URL.Path[commaSep+1 : dotSep]
	}

	e = n.ParsePath(fid)

	return
}
func (n *Needle) ParsePath(fid string) (err error) {
	length := len(fid)
	if length <= 8 {
		return fmt.Errorf("Invalid fid: %s", fid)
	}
	delta := ""
	deltaIndex := strings.LastIndex(fid, "_")
	if deltaIndex > 0 {
		fid, delta = fid[0:deltaIndex], fid[deltaIndex+1:]
	}
	n.Id, n.Cookie, err = ParseKeyHash(fid)
	if err != nil {
		return err
	}
	if delta != "" {
		if d, e := strconv.ParseUint(delta, 10, 64); e == nil {
			n.Id += d
		} else {
			return e
		}
	}
	return err
}

func ParseKeyHash(key_hash_string string) (uint64, uint32, error) {
	if len(key_hash_string) <= 8 {
		return 0, 0, fmt.Errorf("KeyHash is too short.")
	}
	if len(key_hash_string) > 24 {
		return 0, 0, fmt.Errorf("KeyHash is too long.")
	}
	split := len(key_hash_string) - 8
	key, err := strconv.ParseUint(key_hash_string[:split], 16, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("Parse key error: %v", err)
	}
	hash, err := strconv.ParseUint(key_hash_string[split:], 16, 32)
	if err != nil {
		return 0, 0, fmt.Errorf("Parse hash error: %v", err)
	}
	return key, uint32(hash), nil
}
<|endoftext|>


<commit_before>package ari

import (
	"fmt"
	"sync"
	"time"

	"golang.org/x/net/context"
)

// RecordingStartTimeout is the amount of time to wait for a recording to start
// before declaring the recording to have failed.
var RecordingStartTimeout = 1 * time.Second

// LiveRecording describes a recording which is in progress
type LiveRecording struct {
	Cause           string `json:"cause,omitempty"`            // If failed, the cause of the failure
	Duration        int    `json:"duration,omitempty"`         // Length of recording in seconds
	Format          string `json:"format"`                     // Format of recording (wav, gsm, etc)
	Name            string `json:"name"`                       // (base) name for the recording
	SilenceDuration int    `json:"silence_duration,omitempty"` // If silence was detected in the recording, the duration in seconds of that silence (requires that maxSilenceSeconds be non-zero)
	State           string `json:"state"`                      // Current state of the recording
	TalkingDuration int    `json:"talking_duration,omitempty"` // Duration of talking, in seconds, that has been detected in the recording (requires that maxSilenceSeconds be non-zero)
	TargetURI       string `json:"target_uri"`                 // URI for the channel or bridge which is being recorded (TODO: figure out format for this)

	client *Client // Reference to the client which created or returned this LiveRecording

	doneChan chan struct{} // channel for indicating the the recording is stopped.
	mu       sync.Mutex

	status int // The status of the live recording
}

var (
	// ExistsFail indicates that a recording should fail if the
	// given name already exists.
	ExistsFail = "fail"

	// ExistsOverwrite indicates that if a recording exists of
	// the same name, it should be overwritten.
	ExistsOverwrite = "overwrite"

	// ExistsAppend indicates that if a recording exists of the
	// same name, it should be appended to.
	ExistsAppend = "append"
)

var (
	// TerminateNever indicates that a recording should not be
	// ended on any DTMF tone.
	TerminateNever = "none"

	// TerminateAny indicates that a recording should be terminated
	// if any DTMF digit is received
	TerminateAny = "any"

	// TerminateStar indicates that a recording should be terminated
	// if a * DTMF character is received.
	TerminateStar = "*"

	// TerminateHash indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminateHash = "#"

	// TerminatePound indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminatePound = "#"
)

const (
	// RecordInProgress indicates that a recording is still in progress
	RecordInProgress = iota

	// RecordCanceled indicates that a recording was canceled (by request)
	RecordCanceled

	// RecordFailed indicates that a recording failed
	RecordFailed

	// RecordFinished indicates that a recording finished normally
	RecordFinished

	// RecordHangup indicates that a recording was ended due to hangup
	RecordHangup
)

// RecordingOptions describes the set of options available when making a recording.
type RecordingOptions struct {
	// Format is the file format/encoding to which the recording should be stored.
	// This will usually be one of: slin, ulaw, alaw, wav, gsm.
	// If not specified, this will default to slin.
	Format string

	// MaxDuration is the maximum duration of the recording, after which the recording will
	// automatically stop.  If not set, there is no maximum.
	MaxDuration time.Duration

	// MaxSilence is the maximum duration of detected to be found before terminating the recording.
	MaxSilence time.Duration

	// Exists determines what should happen if the given recording already exists.
	// Valid values are: "fail", "overwrite", or "append".
	// If not specified, it will default to "fail"
	Exists string

	// Beep indicates whether a beep should be played to the recorded
	// party at the beginning of the recording.
	Beep bool

	// Terminate indicates whether the recording should be terminated on
	// receipt of a DTMF digit.
	// valid options are: "none", "any", "*", and "#"
	// If not specified, it will default to "none" (never terminate on DTMF).
	Terminate string
}

// ToRequest converts a set of recording options to a
// record request.
func (o *RecordingOptions) ToRequest(name string) *RecordRequest {
	if o.Format == "" {
		o.Format = "slin"
	}
	return &RecordRequest{
		Name:               name,
		Format:             o.Format,
		MaxDurationSeconds: int(o.MaxDuration.Seconds()),
		MaxSilenceSeconds:  int(o.MaxSilence.Seconds()),
		IfExists:           o.Exists,
		Beep:               o.Beep,
		TerminateOn:        o.Terminate,
	}
}

// StoredRecording describes a past recording which may be played back (via GetStoredRecording)
type StoredRecording struct {
	Format string `json:"format"`
	Name   string `json:"name"`

	client *Client // Reference to the client which created or returned this StoredRecording
}

// A Recorder is anything which can "Record"
type Recorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	GetClient() *Client
}

// A PlayerRecorder is anywhich which can Record and Play
type PlayerRecorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	Play(string) (string, error)
	GetClient() *Client
}

//ListStoredRecordings lists all completed recordings
//Equivalent to GET /recordings/stored
func (c *Client) ListStoredRecordings() ([]StoredRecording, error) {
	var m []StoredRecording
	err := c.Get("/recordings/stored", &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetStoredRecording returns a stored recording's details
//Equivalent to GET /recordings/stored/{recordingName}
func (c *Client) GetStoredRecording(recordingName string) (StoredRecording, error) {
	var m StoredRecording
	err := c.Get("/recordings/stored/"+recordingName, &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetLiveRecording returns a specific live recording
//Equivalent to GET /recordings/live/{recordingName}
func (c *Client) GetLiveRecording(recordingName string) (*LiveRecording, error) {
	var m LiveRecording
	err := c.Get("/recordings/live/"+recordingName, &m)
	return &m, err
}

// Record starts a recording on the given Recorder.
func Record(ctx context.Context, r Recorder, name string, opts *RecordingOptions) (*LiveRecording, error) {
	var rec *LiveRecording

	// Extract the ari client from the recorder
	c := r.GetClient()
	if c == nil {
		return nil, fmt.Errorf("Failed to find *ari.Client in Recorder")
	}

	// Listen for start, stop, and failed events
	startSub := c.Bus.Subscribe("RecordingStarted")
	defer startSub.Cancel()

	finishedSub := c.Bus.Subscribe("RecordingFinished")
	defer finishedSub.Cancel()

	failedSub := c.Bus.Subscribe("RecordingFailed")
	defer failedSub.Cancel()

	// Start recording
	r.Record(name, opts)

	// Wait for the recording to start
	startTimer := time.After(RecordingStartTimeout)
	for rec == nil {
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("Recording canceled.")
		case e := <-startSub.C:
			Logger.Debug("Recording started.")
			r := &(e.(*RecordingStarted).Recording)
			if r.Name == name {
				rec = r
				rec.client = c
			}
		case e := <-finishedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				return nil, fmt.Errorf("Recording stopped before starting")
			}
			if e.GetType() == "RecordingFailed" {
			}
			return nil, fmt.Errorf("Recording stopped before starting.")
		case e := <-failedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				return nil, fmt.Errorf("Recording failed to start.")
			}
		case <-startTimer:
			return nil, fmt.Errorf("Timed out waiting for recording to start.")
		}
	}

	// Make the doneChan on the recording to service Done() requests
	rec.doneChan = make(chan struct{})

	// Listen for the recording to finish
	go func() {
		defer func() {
			rec.Stop()
		}()

		select {
		case <-ctx.Done():
			rec.setStatus(RecordCanceled)
			return
		case <-c.Bus.Once(ctx, "ChannelHangup"):
			rec.setStatus(RecordHangup)
			return
		case <-c.Bus.Once(ctx, "RecordingFinished"):
			rec.setStatus(RecordFinished)
			return
		case <-c.Bus.Once(ctx, "RecordingFailed"):
			rec.setStatus(RecordFailed)
			return
		}
	}()

	return rec, nil
}

//Copy current StoredRecording to a new name (retaining the existing copy)
func (s *StoredRecording) Copy(destination string) (StoredRecording, error) {
	var sRet StoredRecording
	if s.client == nil {
		return sRet, fmt.Errorf("No client found in StoredRecording")
	}
	return s.client.CopyStoredRecording(s.Name, destination)
}

// Done returns a channel which is closed when the LiveRecording
// stops.
func (l *LiveRecording) Done() <-chan struct{} {
	return l.doneChan
}

func (l *LiveRecording) setStatus(status int) {
	l.status = status
}

// Status returns the status of the recording.
func (l *LiveRecording) Status() int {
	return l.status
}

//Stop and store current LiveRecording
func (l *LiveRecording) Stop() error {
	l.mu.Lock()
	if l.doneChan != nil {
		close(l.doneChan)
		l.doneChan = nil
	}
	l.mu.Unlock()
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.StopLiveRecording(l.Name)
}

//Pause current LiveRecording
func (l *LiveRecording) Pause() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.PauseLiveRecording(l.Name)
}

//Mute current LiveRecording
func (l *LiveRecording) Mute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.MuteLiveRecording(l.Name)
}

//Delete current LiveRecording
func (l *LiveRecording) Delete() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.DeleteStoredRecording(l.Name)
}

// Scrap Stops and deletes the current LiveRecording
//TODO: reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (l *LiveRecording) Scrap() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ScrapLiveRecording(l.Name)
}

// Resume unpauses the current LiveRecording
func (l *LiveRecording) Resume() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ResumeLiveRecording(l.Name)
}

// Unmute current LiveRecording
func (l *LiveRecording) Unmute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.UnmuteLiveRecording(l.Name)
}

// CopyStoredRecording copies a stored recording to a new destination
// within the stored recordings tree.
//Equivalent to Post /recordings/stored/{recordingName}/copy
func (c *Client) CopyStoredRecording(recordingName string, destination string) (StoredRecording, error) {
	var m StoredRecording

	//Request structure to copy a stored recording. DestinationRecordingName is required.
	type request struct {
		DestinationRecordingName string `json:"destinationRecordingName"`
	}

	req := request{destination}

	//Make the request
	err := c.Post("/recordings/stored/"+recordingName+"/copy", &m, &req)
	//TODO add individual error handling

	if err != nil {
		return m, err
	}
	return m, nil
}

// StopLiveRecording stops and stores a live recording
//Equivalent to Post /recordings/live/{recordingName}/stop
func (c *Client) StopLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/stop", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//PauseLiveRecording pauses a live recording
//Equivalent to Post /recordings/live/{recordingName}/pause
func (c *Client) PauseLiveRecording(recordingName string) error {

	//Since no request body is required nor return object
	//we just pass two nils.

	err := c.Post("/recordings/live/"+recordingName+"/pause", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//MuteLiveRecording mutes a live recording
//Equivalent to Post /recordings/live/{recordingName}/mute
func (c *Client) MuteLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/mute", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//DeleteStoredRecording deletes a stored recording
//Equivalent to DELETE /recordings/stored/{recordingName}
func (c *Client) DeleteStoredRecording(recordingName string) error {
	err := c.Delete("/recordings/stored/"+recordingName, nil, "")
	return err
}

// ScrapLiveRecording stops a live recording and discard it
//Equivalent to DELETE /recordings/live/{recordingName}
//TODO reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (c *Client) ScrapLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName, nil, "")
	return err
}

// ResumeLiveRecording resumes (unpauses) a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/pause
func (c *Client) ResumeLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/pause", nil, "")
	return err
}

// UnmuteLiveRecording unmutes a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/mute
func (c *Client) UnmuteLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/mute", nil, "")
	return err
}
<commit_msg>Provide Duration functions for recordings<commit_after>package ari

import (
	"fmt"
	"sync"
	"time"

	"golang.org/x/net/context"
)

// RecordingStartTimeout is the amount of time to wait for a recording to start
// before declaring the recording to have failed.
var RecordingStartTimeout = 1 * time.Second

// LiveRecording describes a recording which is in progress
type LiveRecording struct {
	Cause       string `json:"cause,omitempty"`            // If failed, the cause of the failure
	DurationSec int    `json:"duration,omitempty"`         // Length of recording in seconds
	Format      string `json:"format"`                     // Format of recording (wav, gsm, etc)
	Name        string `json:"name"`                       // (base) name for the recording
	SilenceSec  int    `json:"silence_duration,omitempty"` // If silence was detected in the recording, the duration in seconds of that silence (requires that maxSilenceSeconds be non-zero)
	State       string `json:"state"`                      // Current state of the recording
	TalkingSec  int    `json:"talking_duration,omitempty"` // Duration of talking, in seconds, that has been detected in the recording (requires that maxSilenceSeconds be non-zero)
	TargetURI   string `json:"target_uri"`                 // URI for the channel or bridge which is being recorded (TODO: figure out format for this)

	client *Client // Reference to the client which created or returned this LiveRecording

	doneChan chan struct{} // channel for indicating the the recording is stopped.
	mu       sync.Mutex

	status int // The status of the live recording
}

// Duration returns the duration of the live recording, if known
func (l *LiveRecording) Duration() (dur time.Duration) {
	if l.DurationSec > 0 {
		dur = time.Duration(l.DurationSec) * time.Second
	}
	return
}

// SilenceDuration returns the duration of the detected silence
// during the recording, if known
func (l *LiveRecording) SilenceDuration() (dur time.Duration) {
	if l.SilenceSec > 0 {
		dur = time.Duration(l.SilenceSec) * time.Second
	}
	return
}

// TalkingDuration returns the duration of the detected talking
// during the recording, if known
func (l *LiveRecording) TalkingDuration() (dur time.Duration) {
	if l.TalkingSec > 0 {
		dur = time.Duration(l.TalkingSec) * time.Second
	}
	return
}

var (
	// ExistsFail indicates that a recording should fail if the
	// given name already exists.
	ExistsFail = "fail"

	// ExistsOverwrite indicates that if a recording exists of
	// the same name, it should be overwritten.
	ExistsOverwrite = "overwrite"

	// ExistsAppend indicates that if a recording exists of the
	// same name, it should be appended to.
	ExistsAppend = "append"
)

var (
	// TerminateNever indicates that a recording should not be
	// ended on any DTMF tone.
	TerminateNever = "none"

	// TerminateAny indicates that a recording should be terminated
	// if any DTMF digit is received
	TerminateAny = "any"

	// TerminateStar indicates that a recording should be terminated
	// if a * DTMF character is received.
	TerminateStar = "*"

	// TerminateHash indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminateHash = "#"

	// TerminatePound indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminatePound = "#"
)

const (
	// RecordInProgress indicates that a recording is still in progress
	RecordInProgress = iota

	// RecordCanceled indicates that a recording was canceled (by request)
	RecordCanceled

	// RecordFailed indicates that a recording failed
	RecordFailed

	// RecordFinished indicates that a recording finished normally
	RecordFinished

	// RecordHangup indicates that a recording was ended due to hangup
	RecordHangup
)

// RecordingOptions describes the set of options available when making a recording.
type RecordingOptions struct {
	// Format is the file format/encoding to which the recording should be stored.
	// This will usually be one of: slin, ulaw, alaw, wav, gsm.
	// If not specified, this will default to slin.
	Format string

	// MaxDuration is the maximum duration of the recording, after which the recording will
	// automatically stop.  If not set, there is no maximum.
	MaxDuration time.Duration

	// MaxSilence is the maximum duration of detected to be found before terminating the recording.
	MaxSilence time.Duration

	// Exists determines what should happen if the given recording already exists.
	// Valid values are: "fail", "overwrite", or "append".
	// If not specified, it will default to "fail"
	Exists string

	// Beep indicates whether a beep should be played to the recorded
	// party at the beginning of the recording.
	Beep bool

	// Terminate indicates whether the recording should be terminated on
	// receipt of a DTMF digit.
	// valid options are: "none", "any", "*", and "#"
	// If not specified, it will default to "none" (never terminate on DTMF).
	Terminate string
}

// ToRequest converts a set of recording options to a
// record request.
func (o *RecordingOptions) ToRequest(name string) *RecordRequest {
	if o.Format == "" {
		o.Format = "slin"
	}
	return &RecordRequest{
		Name:               name,
		Format:             o.Format,
		MaxDurationSeconds: int(o.MaxDuration.Seconds()),
		MaxSilenceSeconds:  int(o.MaxSilence.Seconds()),
		IfExists:           o.Exists,
		Beep:               o.Beep,
		TerminateOn:        o.Terminate,
	}
}

// StoredRecording describes a past recording which may be played back (via GetStoredRecording)
type StoredRecording struct {
	Format string `json:"format"`
	Name   string `json:"name"`

	client *Client // Reference to the client which created or returned this StoredRecording
}

// A Recorder is anything which can "Record"
type Recorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	GetClient() *Client
}

// A PlayerRecorder is anywhich which can Record and Play
type PlayerRecorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	Play(string) (string, error)
	GetClient() *Client
}

//ListStoredRecordings lists all completed recordings
//Equivalent to GET /recordings/stored
func (c *Client) ListStoredRecordings() ([]StoredRecording, error) {
	var m []StoredRecording
	err := c.Get("/recordings/stored", &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetStoredRecording returns a stored recording's details
//Equivalent to GET /recordings/stored/{recordingName}
func (c *Client) GetStoredRecording(recordingName string) (StoredRecording, error) {
	var m StoredRecording
	err := c.Get("/recordings/stored/"+recordingName, &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetLiveRecording returns a specific live recording
//Equivalent to GET /recordings/live/{recordingName}
func (c *Client) GetLiveRecording(recordingName string) (*LiveRecording, error) {
	var m LiveRecording
	err := c.Get("/recordings/live/"+recordingName, &m)
	return &m, err
}

// Record starts a recording on the given Recorder.
func Record(ctx context.Context, r Recorder, name string, opts *RecordingOptions) (*LiveRecording, error) {
	var rec *LiveRecording

	// Extract the ari client from the recorder
	c := r.GetClient()
	if c == nil {
		return nil, fmt.Errorf("Failed to find *ari.Client in Recorder")
	}

	// Listen for start, stop, and failed events
	startSub := c.Bus.Subscribe("RecordingStarted")
	defer startSub.Cancel()

	finishedSub := c.Bus.Subscribe("RecordingFinished")
	defer finishedSub.Cancel()

	failedSub := c.Bus.Subscribe("RecordingFailed")
	defer failedSub.Cancel()

	// Start recording
	r.Record(name, opts)

	// Wait for the recording to start
	startTimer := time.After(RecordingStartTimeout)
	for rec == nil {
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("Recording canceled.")
		case e := <-startSub.C:
			Logger.Debug("Recording started.")
			r := &(e.(*RecordingStarted).Recording)
			if r.Name == name {
				rec = r
				rec.client = c
			}
		case e := <-finishedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				return nil, fmt.Errorf("Recording stopped before starting")
			}
			if e.GetType() == "RecordingFailed" {
			}
			return nil, fmt.Errorf("Recording stopped before starting.")
		case e := <-failedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				return nil, fmt.Errorf("Recording failed to start.")
			}
		case <-startTimer:
			return nil, fmt.Errorf("Timed out waiting for recording to start.")
		}
	}

	// Make the doneChan on the recording to service Done() requests
	rec.doneChan = make(chan struct{})

	// Listen for the recording to finish
	go func() {
		defer func() {
			rec.Stop()
		}()

		select {
		case <-ctx.Done():
			rec.setStatus(RecordCanceled)
			return
		case <-c.Bus.Once(ctx, "ChannelHangup"):
			rec.setStatus(RecordHangup)
			return
		case <-c.Bus.Once(ctx, "RecordingFinished"):
			rec.setStatus(RecordFinished)
			return
		case <-c.Bus.Once(ctx, "RecordingFailed"):
			rec.setStatus(RecordFailed)
			return
		}
	}()

	return rec, nil
}

//Copy current StoredRecording to a new name (retaining the existing copy)
func (s *StoredRecording) Copy(destination string) (StoredRecording, error) {
	var sRet StoredRecording
	if s.client == nil {
		return sRet, fmt.Errorf("No client found in StoredRecording")
	}
	return s.client.CopyStoredRecording(s.Name, destination)
}

// Done returns a channel which is closed when the LiveRecording
// stops.
func (l *LiveRecording) Done() <-chan struct{} {
	return l.doneChan
}

func (l *LiveRecording) setStatus(status int) {
	l.status = status
}

// Status returns the status of the recording.
func (l *LiveRecording) Status() int {
	return l.status
}

//Stop and store current LiveRecording
func (l *LiveRecording) Stop() error {
	l.mu.Lock()
	if l.doneChan != nil {
		close(l.doneChan)
		l.doneChan = nil
	}
	l.mu.Unlock()
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.StopLiveRecording(l.Name)
}

//Pause current LiveRecording
func (l *LiveRecording) Pause() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.PauseLiveRecording(l.Name)
}

//Mute current LiveRecording
func (l *LiveRecording) Mute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.MuteLiveRecording(l.Name)
}

//Delete current LiveRecording
func (l *LiveRecording) Delete() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.DeleteStoredRecording(l.Name)
}

// Scrap Stops and deletes the current LiveRecording
//TODO: reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (l *LiveRecording) Scrap() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ScrapLiveRecording(l.Name)
}

// Resume unpauses the current LiveRecording
func (l *LiveRecording) Resume() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ResumeLiveRecording(l.Name)
}

// Unmute current LiveRecording
func (l *LiveRecording) Unmute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.UnmuteLiveRecording(l.Name)
}

// CopyStoredRecording copies a stored recording to a new destination
// within the stored recordings tree.
//Equivalent to Post /recordings/stored/{recordingName}/copy
func (c *Client) CopyStoredRecording(recordingName string, destination string) (StoredRecording, error) {
	var m StoredRecording

	//Request structure to copy a stored recording. DestinationRecordingName is required.
	type request struct {
		DestinationRecordingName string `json:"destinationRecordingName"`
	}

	req := request{destination}

	//Make the request
	err := c.Post("/recordings/stored/"+recordingName+"/copy", &m, &req)
	//TODO add individual error handling

	if err != nil {
		return m, err
	}
	return m, nil
}

// StopLiveRecording stops and stores a live recording
//Equivalent to Post /recordings/live/{recordingName}/stop
func (c *Client) StopLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/stop", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//PauseLiveRecording pauses a live recording
//Equivalent to Post /recordings/live/{recordingName}/pause
func (c *Client) PauseLiveRecording(recordingName string) error {

	//Since no request body is required nor return object
	//we just pass two nils.

	err := c.Post("/recordings/live/"+recordingName+"/pause", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//MuteLiveRecording mutes a live recording
//Equivalent to Post /recordings/live/{recordingName}/mute
func (c *Client) MuteLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/mute", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//DeleteStoredRecording deletes a stored recording
//Equivalent to DELETE /recordings/stored/{recordingName}
func (c *Client) DeleteStoredRecording(recordingName string) error {
	err := c.Delete("/recordings/stored/"+recordingName, nil, "")
	return err
}

// ScrapLiveRecording stops a live recording and discard it
//Equivalent to DELETE /recordings/live/{recordingName}
//TODO reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (c *Client) ScrapLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName, nil, "")
	return err
}

// ResumeLiveRecording resumes (unpauses) a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/pause
func (c *Client) ResumeLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/pause", nil, "")
	return err
}

// UnmuteLiveRecording unmutes a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/mute
func (c *Client) UnmuteLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/mute", nil, "")
	return err
}
<|endoftext|>


<commit_before>/*
  REVISION HISTORY
  ----------------
  20 Mar 20 -- Made comparisons case insensitive.  And decided to make this cgrepi.go.
                 And then I figured I could not improve performance by using more packages.
                 But I can change the side effect of displaying altered case.
  22 Mar 20 -- Will add timing code that I wrote for anack.
  27 Mar 21 -- Changed commandLineFiles in platform specific code, and added the -g flag to force globbing.
  14 Dec 21 -- I'm porting the changed I wrote to multack here.  Also, I noticed that this is mure complex than it
                 needs to be.  I'm going to take a crack at writing a simpler version myself.
                 It takes a list of files from the command line (or on windows, a globbing pattern) and iterates
                 thru all of the files in the list.  Then it exits.  But this is using 2 channels.  I have to understand
                 this better.  It seems much too complex.  I'm going to simplify it.
  16 Dec 21 -- Adding a waitgroup, as the sleep at the end is a kludge.  And will only start number of worker go routines to match number of files.
  19 Dec 21 -- Will add the more selective use of atomic instructions as I learned about from Bill Kennedy and is in cgrepi2.go.  But I will
                 keep reading the file line by line.  Can now time difference when number of atomic operations is reduced.
                 Cgrepi2 is still faster, so most of the slowness here is the line by line file reading.
*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

const LastAltered = "19 Dec 2021"
const maxSecondsToTimeout = 300
const workerPoolMultiplier = 20

var workers = runtime.NumCPU() * workerPoolMultiplier

type grepType struct {
	regex    *regexp.Regexp
	filename string
	goRtnNum int
}

var grepChan chan grepType
var totFilesScanned, totMatchesFound int64
var t0, tfinal time.Time
var wg sync.WaitGroup

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
	log.SetFlags(0)

	// flag definitions and processing
	globflag := flag.Bool("g", false, "force use of globbing, only makes sense on Windows.") // Ptr
	var timeoutOpt = flag.Int64("timeout", 0, "seconds (0 means no timeout)")
	flag.Parse()

	if *timeoutOpt < 1 || *timeoutOpt > maxSecondsToTimeout {
		fmt.Fprintln(os.Stderr, "timeout is", *timeoutOpt, ", and is out of range of [0,300] seconds.  Set to", maxSecondsToTimeout)
		*timeoutOpt = maxSecondsToTimeout
	}
	args := flag.Args()
	if len(args) < 1 {
		log.Fatalln("a regexp to match must be specified")
	}
	pattern := args[0]
	pattern = strings.ToLower(pattern) // this is the change for the pattern.
	files := args[1:]
	if len(files) < 1 { // no files or globbing pattern on command line.
		if runtime.GOOS == "windows" {
			files = []string{"*.txt"}
		} else {
			files = txtFiles() // intended only for use on linux.
		}
	}

	t0 = time.Now()
	tfinal = t0.Add(time.Duration(*timeoutOpt) * time.Second)
	lineRegex, err := regexp.Compile(pattern)
	if err != nil {
		log.Fatalf("invalid regexp: %s\n", err)
	}

	fmt.Println()
	fmt.Printf(" Concurrent grep insensitive case last altered %s, using pattern of %q, %d worker rtns, compiled with %s. \n",
		LastAltered, pattern, workers, runtime.Version())
	fmt.Println()

	if *globflag && runtime.GOOS == "windows" { // glob function only makes sense on Windows.
		files = globCommandLineFiles(files) // this fails vet because it's in the platform specific code file.
	} else {
		files = commandLineFiles(files)
	}

	minGoRtns := min(len(files), workers)
	// start the worker pool
	grepChan = make(chan grepType, workers) // buffered channel
	for w := 0; w < minGoRtns; w++ {
		go func() {
			for g := range grepChan { // These are channel reads that are only stopped when the channel is closed.
				grepFile(g.regex, g.filename)
			}
		}()
	}

	for _, file := range files {
		wg.Add(1)
		grepChan <- grepType{regex: lineRegex, filename: file}
	}

	goRtns := runtime.NumGoroutine() // must capture this before we sleep for a second.
	wg.Wait()
	close(grepChan) // must close the channel so the worker go routines know to stop.

	elapsed := time.Since(t0)
	//time.Sleep(time.Second) // I've noticed that sometimes main exits before everything can be displayed.  This sleep line fixes that.

	fmt.Println()
	fmt.Println()

	fmt.Printf(" Elapsed time is %s and there are %d go routines that found %d matches in %d files\n", elapsed.String(), goRtns, totMatchesFound, totFilesScanned)
	fmt.Println()
}

func grepFile(lineRegex *regexp.Regexp, fpath string) {
	var localMatches int64
	file, err := os.Open(fpath)
	if err != nil {
		log.Printf("grepFile os.Open error : %s\n", err)
		return
	}
	defer func() {
		file.Close()
		atomic.AddInt64(&totFilesScanned, 1)
		atomic.AddInt64(&totMatchesFound, localMatches)
		wg.Done()
	}()

	reader := bufio.NewReader(file)
	for lino := 1; ; lino++ {
		lineStr, er := reader.ReadString('\n')
		lineStrLower := strings.ToLower(lineStr) // this is the change I made to make every comparison case insensitive.
		if lineRegex.MatchString(lineStrLower) {
			fmt.Printf("%s:%d:%s", fpath, lino, lineStr)
			localMatches++
		}
		if er != nil { // when can't read any more bytes, break.  The test for er is here so line fragments are processed, too.
			break // just exit when hit EOF condition.
		}
		now := time.Now()
		if now.After(tfinal) {
			log.Fatalln(" Time up.  Elapsed is", time.Since(t0))
		}
	}
} // end grepFile

func txtFiles() []string { // intended to be needed on linux.
	workingDirname, err := os.Getwd()
	if err != nil {
		fmt.Fprintln(os.Stderr, "from commandlinefiles:", err)
		return nil
	}
	direntries, err := os.ReadDir(workingDirname) // became available as of Go 1.16
	if err != nil {
		fmt.Fprintln(os.Stderr, "While using os.ReadDir got:", err)
		os.Exit(1)
	}

	pattern := "*.txt"
	matchingNames := make([]string, 0, len(direntries))
	for _, d := range direntries {
		if d.IsDir() {
			continue // skip it
		}
		bool, er := filepath.Match(pattern, strings.ToLower(d.Name()))
		if er != nil {
			fmt.Fprintln(os.Stderr, err)
			continue
		}
		if bool {
			matchingNames = append(matchingNames, d.Name())
		}
	}
	return matchingNames
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}
<commit_msg>09/30/2022 14:05:18 cgrepi/cgrepi.go -- added smart case<commit_after>/*
  REVISION HISTORY
  ----------------
  20 Mar 20 -- Made comparisons case insensitive.  And decided to make this cgrepi.go.
                 And then I figured I could not improve performance by using more packages.
                 But I can change the side effect of displaying altered case.
  22 Mar 20 -- Will add timing code that I wrote for anack.
  27 Mar 21 -- Changed commandLineFiles in platform specific code, and added the -g flag to force globbing.
  14 Dec 21 -- I'm porting the changed I wrote to multack here.  Also, I noticed that this is mure complex than it
                 needs to be.  I'm going to take a crack at writing a simpler version myself.
                 It takes a list of files from the command line (or on windows, a globbing pattern) and iterates
                 thru all of the files in the list.  Then it exits.  But this is using 2 channels.  I have to understand
                 this better.  It seems much too complex.  I'm going to simplify it.
  16 Dec 21 -- Adding a waitgroup, as the sleep at the end is a kludge.  And will only start number of worker go routines to match number of files.
  19 Dec 21 -- Will add the more selective use of atomic instructions as I learned about from Bill Kennedy and is in cgrepi2.go.  But I will
                 keep reading the file line by line.  Can now time difference when number of atomic operations is reduced.
                 Cgrepi2 is still faster, so most of the slowness here is the line by line file reading.
  30 Sep 22 -- Got idea from ripgrep about smart case, where if input string is all lower case, then the search is  ase insensitive.
                 But if input string has an upper case character, then the search is case sensitive.
*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

const LastAltered = "30 Sept 2022"
const maxSecondsToTimeout = 300
const workerPoolMultiplier = 20

var workers = runtime.NumCPU() * workerPoolMultiplier

type grepType struct {
	regex    *regexp.Regexp
	filename string
	goRtnNum int
}

var caseSensitiveFlag bool // default is false.
var grepChan chan grepType
var totFilesScanned, totMatchesFound int64
var t0, tfinal time.Time
var wg sync.WaitGroup

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
	log.SetFlags(0)

	// flag definitions and processing
	globflag := flag.Bool("g", false, "force use of globbing, only makes sense on Windows.") // Ptr
	verboseFlag := flag.Bool("v", false, "Verbose flag")
	var timeoutOpt = flag.Int64("timeout", 0, "seconds (0 means no timeout)")
	flag.Parse()

	if *timeoutOpt < 1 || *timeoutOpt > maxSecondsToTimeout {
		fmt.Fprintln(os.Stderr, "timeout is", *timeoutOpt, ", and is out of range of [0,300] seconds.  Set to", maxSecondsToTimeout)
		*timeoutOpt = maxSecondsToTimeout
	}
	args := flag.Args()
	if len(args) < 1 {
		log.Fatalln("a regexp to match must be specified")
	}
	pattern := args[0]
	testCaseSensitivity, _ := regexp.Compile("[A-Z]") // If this matches then there is an upper case character in the input pattern.  And I'm ignoring errors, of course.
	caseSensitiveFlag = testCaseSensitivity.MatchString(pattern)
	if *verboseFlag {
		fmt.Printf(" grep pattern is %s and caseSensitive flag is %t\n", pattern, caseSensitiveFlag)
	}
	if !caseSensitiveFlag {
		pattern = strings.ToLower(pattern) // this is the change for the pattern.
	}
	if *verboseFlag {
		fmt.Printf(" after possible force to lower case, pattern is %s\n", pattern)
	}
	files := args[1:]
	if len(files) < 1 { // no files or globbing pattern on command line.
		if runtime.GOOS == "windows" {
			files = []string{"*.txt"}
		} else {
			files = txtFiles() // intended only for use on linux.
		}
	}

	t0 = time.Now()
	tfinal = t0.Add(time.Duration(*timeoutOpt) * time.Second)
	lineRegex, err := regexp.Compile(pattern)
	if err != nil {
		log.Fatalf("invalid regexp: %s\n", err)
	}

	fmt.Println()
	fmt.Printf(" Concurrent grep insensitive case last altered %s, using pattern of %q, %d worker rtns, compiled with %s. \n",
		LastAltered, pattern, workers, runtime.Version())
	fmt.Println()

	if *globflag && runtime.GOOS == "windows" { // glob function only makes sense on Windows.
		files = globCommandLineFiles(files) // this fails vet because it's in the platform specific code file.
	} else {
		files = commandLineFiles(files)
	}

	minGoRtns := min(len(files), workers)
	// start the worker pool
	grepChan = make(chan grepType, workers) // buffered channel
	for w := 0; w < minGoRtns; w++ {
		go func() {
			for g := range grepChan { // These are channel reads that are only stopped when the channel is closed.
				grepFile(g.regex, g.filename)
			}
		}()
	}

	for _, file := range files {
		wg.Add(1)
		grepChan <- grepType{regex: lineRegex, filename: file}
	}

	goRtns := runtime.NumGoroutine() // must capture this before we sleep for a second.
	wg.Wait()
	close(grepChan) // must close the channel so the worker go routines know to stop.

	elapsed := time.Since(t0)
	//time.Sleep(time.Second) // I've noticed that sometimes main exits before everything can be displayed.  This sleep line fixes that.

	fmt.Println()
	fmt.Println()

	fmt.Printf(" Elapsed time is %s and there are %d go routines that found %d matches in %d files\n", elapsed.String(), goRtns, totMatchesFound, totFilesScanned)
	fmt.Println()
}

func grepFile(lineRegex *regexp.Regexp, fpath string) {
	var localMatches int64
	var lineStrng string // either case sensitive or case insensitive string, depending on value of caseSensitiveFlag, which itself depends on case sensitivity of input pattern.
	file, err := os.Open(fpath)
	if err != nil {
		log.Printf("grepFile os.Open error : %s\n", err)
		return
	}
	defer func() {
		file.Close()
		atomic.AddInt64(&totFilesScanned, 1)
		atomic.AddInt64(&totMatchesFound, localMatches)
		wg.Done()
	}()

	reader := bufio.NewReader(file)
	for lino := 1; ; lino++ {
		lineStr, er := reader.ReadString('\n')
		if caseSensitiveFlag {
			lineStrng = lineStr
		} else {
			lineStrng = strings.ToLower(lineStr) // this is the change I made to make every comparison case insensitive.
		}

		if lineRegex.MatchString(lineStrng) { // this is now either case sensitive or not, depending on whether the input pattern has upper case letters.
			fmt.Printf("%s:%d:%s", fpath, lino, lineStr)
			localMatches++
		}
		if er != nil { // when can't read any more bytes, break.  The test for er is here so line fragments are processed, too.
			break // just exit when hit EOF condition.
		}
		now := time.Now()
		if now.After(tfinal) {
			log.Fatalln(" Time up.  Elapsed is", time.Since(t0))
		}
	}
} // end grepFile

func txtFiles() []string { // intended to be needed on linux.
	workingDirname, err := os.Getwd()
	if err != nil {
		fmt.Fprintln(os.Stderr, "from commandlinefiles:", err)
		return nil
	}
	direntries, err := os.ReadDir(workingDirname) // became available as of Go 1.16
	if err != nil {
		fmt.Fprintln(os.Stderr, "While using os.ReadDir got:", err)
		os.Exit(1)
	}

	pattern := "*.txt"
	matchingNames := make([]string, 0, len(direntries))
	for _, d := range direntries {
		if d.IsDir() {
			continue // skip it
		}
		bool, er := filepath.Match(pattern, strings.ToLower(d.Name()))
		if er != nil {
			fmt.Fprintln(os.Stderr, err)
			continue
		}
		if bool {
			matchingNames = append(matchingNames, d.Name())
		}
	}
	return matchingNames
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}
<|endoftext|>


<commit_before>// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// Run with "web" command-line argument for web server.
// See page 13.
//!+main

// Lissajous generates GIF animations of random Lissajous figures.
package main

import (
	"image"
	"image/color"
	"image/gif"
	"io"
	"math"
	"math/rand"
	"os"
)

//!-main
// Packages not needed by version in book.
import (
	"log"
	"net/http"
	"time"
)

//!+main

var palette = []color.Color{color.White, color.Black}

const (
	whiteIndex = 0 // first color in palette
	blackIndex = 1 // next color in palette
)

func main() {
	//!-main
	// The sequence of images is deterministic unless we seed
	// the pseudo-random number generator using the current time.
	// Thanks to Randall McPherson for pointing out the omission.
	rand.Seed(time.Now().UTC().UnixNano())

	if len(os.Args) > 1 && os.Args[1] == "web" {
		//!+http
		handler := func(w http.ResponseWriter, r *http.Request) {
			lissajous(w)
		}
		http.HandleFunc("/", handler)
		//!-http
		log.Fatal(http.ListenAndServe("localhost:8000", nil))
		return
	}
	//!+main
	lissajous(os.Stdout)
}

func lissajous(out io.Writer) {
	const (
		cycles  = 5     // number of complete x oscillator revolutions
		res     = 0.001 // angular resolution
		size    = 100   // image canvas covers [-size..+size]
		nframes = 64    // number of animation frames
		delay   = 8     // delay between frames in 10ms units
	)
	freq := rand.Float64() * 3.0 // relative frequency of y oscillator
	anim := gif.GIF{LoopCount: nframes}
	phase := 0.0 // phase difference
	for i := 0; i < nframes; i++ {
		rect := image.Rect(0, 0, 2*size+1, 2*size+1)
		img := image.NewPaletted(rect, palette)
		for t := 0.0; t < cycles*2*math.Pi; t += res {
			x := math.Sin(t)
			y := math.Sin(t*freq + phase)
			img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
				blackIndex)
		}
		phase += 0.1
		anim.Delay = append(anim.Delay, delay)
		anim.Image = append(anim.Image, img)
	}
	gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}

//!-main
<commit_msg>Modified main.go.<commit_after>// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// Run with "web" command-line argument for web server.
// See page 13.
//!+main

// Lissajous generates GIF animations of random Lissajous figures.
package main

import (
	"image"
	"image/color"
	"image/gif"
	"io"
	"math"
	"math/rand"
	"os"
)

//!-main
// Packages not needed by version in book.
import (
	"log"
	"net/http"
	"time"
)

//!+main

var palette = []color.Color{color.Black, color.RGBA{0x00, 0xFF, 0x00, 0xff}}

const (
	bgIndex = 0 // first color in palette
	fgIndex = 1 // next color in palette
)

func main() {
	//!-main
	// The sequence of images is deterministic unless we seed
	// the pseudo-random number generator using the current time.
	// Thanks to Randall McPherson for pointing out the omission.
	rand.Seed(time.Now().UTC().UnixNano())

	if len(os.Args) > 1 && os.Args[1] == "web" {
		//!+http
		handler := func(w http.ResponseWriter, r *http.Request) {
			lissajous(w)
		}
		http.HandleFunc("/", handler)
		//!-http
		log.Fatal(http.ListenAndServe("localhost:8000", nil))
		return
	}
	//!+main
	lissajous(os.Stdout)
}

func lissajous(out io.Writer) {
	const (
		cycles  = 5     // number of complete x oscillator revolutions
		res     = 0.001 // angular resolution
		size    = 100   // image canvas covers [-size..+size]
		nframes = 64    // number of animation frames
		delay   = 8     // delay between frames in 10ms units
	)
	freq := rand.Float64() * 3.0 // relative frequency of y oscillator
	anim := gif.GIF{LoopCount: nframes}
	phase := 0.0 // phase difference
	for i := 0; i < nframes; i++ {
		rect := image.Rect(0, 0, 2*size+1, 2*size+1)
		img := image.NewPaletted(rect, palette)
		for t := 0.0; t < cycles*2*math.Pi; t += res {
			x := math.Sin(t)
			y := math.Sin(t*freq + phase)
			img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
				fgIndex)
		}
		phase += 0.1
		anim.Delay = append(anim.Delay, delay)
		anim.Image = append(anim.Image, img)
	}
	gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}

//!-main
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
)

func main() {
	http.HandleFunc("/", http_handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func http_handler(w http.ResponseWriter, r *http.Request) {
	document_root := os.Args[1]
	target_file := fmt.Sprint(document_root, r.URL.Path)
	if file_stat, err := os.Stat(target_file); err == nil {
		if file_stat.IsDir() {
			files, _ := filepath.Glob(fmt.Sprint(target_file, "/*"))
			fmt.Fprintln(w, fmt.Sprint("Content of directory : ", r.URL.Path))
			for _, element := range files {
				fmt.Fprintln(w, element)
			}
		} else {
			file, _ := os.Open(target_file)
			scanner := bufio.NewScanner(file)
			for scanner.Scan() {
				fmt.Fprintln(w, scanner.Text())
			}
		}
	} else {
		http.Error(w, fmt.Sprint("No such file or directory : ", r.URL.Path), http.StatusNotFound)
	}
}
<commit_msg>add document_root setting<commit_after>package main

import (
	"bufio"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
)

func main() {
	http.HandleFunc("/", http_handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func http_handler(w http.ResponseWriter, r *http.Request) {
	document_root := filepath.Dir(os.Args[0])
	if len(os.Args) > 1 {
		document_root = os.Args[1]
	}
	target_file := fmt.Sprint(document_root, r.URL.Path)
	if file_stat, err := os.Stat(target_file); err == nil {
		if file_stat.IsDir() {
			files, _ := filepath.Glob(fmt.Sprint(target_file, "/*"))
			fmt.Fprintln(w, fmt.Sprint("Content of directory : ", r.URL.Path))
			for _, element := range files {
				fmt.Fprintln(w, element)
			}
		} else {
			file, _ := os.Open(target_file)
			scanner := bufio.NewScanner(file)
			for scanner.Scan() {
				fmt.Fprintln(w, scanner.Text())
			}
		}
	} else {
		http.Error(w, fmt.Sprint("No such file or directory : ", r.URL.Path), http.StatusNotFound)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/musl/libgofr"
	"github.com/nfnt/resize"
	"image"
	"image/color"
	"image/png"
	"log"
	"net/http"
	"runtime"
	"strconv"
	"time"
)

func finish(w http.ResponseWriter, status int, message string) {
	w.WriteHeader(status)
	fmt.Fprintf(w, message)
}

func logDuration(message string, start time.Time) {
	end := time.Now()
	log.Printf("%s: %v\n", message, end.Sub(start))
}

func route_png(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	defer logDuration(fmt.Sprintf("%s", r.URL.Path), start)

	if r.Method != "GET" {
		finish(w, http.StatusMethodNotAllowed, "Method not allowed.")
		return
	}

	q := r.URL.Query()

	s, err := strconv.Atoi(q.Get("s"))
	if err != nil {
		s = 1
	}

	iw, err := strconv.Atoi(q.Get("w"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid w")
		return
	}

	ih, err := strconv.Atoi(q.Get("h"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid h")
		return
	}

	i, err := strconv.Atoi(q.Get("i"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid i")
		return
	}

	er, err := strconv.ParseFloat(q.Get("e"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid e")
		return
	}

	rmin, err := strconv.ParseFloat(q.Get("rmin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	imin, err := strconv.ParseFloat(q.Get("imin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	rmax, err := strconv.ParseFloat(q.Get("rmax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmax")
		return
	}

	imax, err := strconv.ParseFloat(q.Get("imax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	c := q.Get("c")
	color_func := gofr.ColorMono
	switch c {
	case "mono":
		color_func = gofr.ColorMono
	case "stripe":
		color_func = gofr.ColorMonoStripe
	case "bands":
		color_func = gofr.ColorBands
	case "smooth":
		color_func = gofr.ColorSmooth
	default:
		finish(w, http.StatusUnprocessableEntity, "Invalid c")
		return
	}

	hex := q.Get("m")
	member_color := color.NRGBA64{0, 0, 0, 0xffff}
	if len(hex) > 2 {
		m, err := strconv.ParseInt(hex[1:len(hex)], 16, 32)
		if err != nil {
			finish(w, http.StatusUnprocessableEntity, "Invalid m")
			return
		}
		member_color = color.NRGBA64{
			uint16(((m >> 16) & 0xff) * 0x101),
			uint16(((m >> 8) & 0xff) * 0x101),
			uint16((m & 0xff) * 0x101),
			0xffff,
		}
	}

	p := gofr.Parameters{
		ImageWidth:   iw * s,
		ImageHeight:  ih * s,
		MaxI:         i,
		EscapeRadius: er,
		Min:          complex(rmin, imin),
		Max:          complex(rmax, imax),
		ColorFunc:    color_func,
		MemberColor:  member_color,
	}

	// TODO: Check parameters and set reasonable bounds on what we can
	// quickly calculate.

	img := image.NewNRGBA64(image.Rect(0, 0, p.ImageWidth, p.ImageHeight))
	n := runtime.NumCPU()
	contexts := gofr.MakeContexts(img, n, &p)
	gofr.Render(n, contexts, gofr.Mandelbrot)

	scaled_img := resize.Resize(uint(iw), uint(ih), image.Image(img), resize.Lanczos3)

	w.Header().Set("Content-Type", "image/png")
	png.Encode(w, scaled_img)
}

func main() {
	fs := http.FileServer(http.Dir("static"))
	bind_addr := "0.0.0.0:8000"

	http.Handle("/", fs)
	http.HandleFunc("/png", route_png)
	log.Printf("Listening on: %s\n", bind_addr)
	log.Fatal(http.ListenAndServe(bind_addr, nil))
}
<commit_msg>Add a request id to log messages<commit_after>package main

import (
	"fmt"
	"github.com/musl/libgofr"
	"github.com/nfnt/resize"
	"image"
	"image/color"
	"image/png"
	"log"
	"net/http"
	"runtime"
	"strconv"
	"time"
)

var id_chan = make(chan int, 1)

func finish(w http.ResponseWriter, status int, message string) {
	w.WriteHeader(status)
	fmt.Fprintf(w, message)
}

func logDuration(message string, start time.Time) {
	end := time.Now()
	log.Printf("%s: %v\n", message, end.Sub(start))
}

func route_png(w http.ResponseWriter, r *http.Request) {
	id := <-id_chan
	start := time.Now()
	defer logDuration(fmt.Sprintf("%08d %s", id, r.URL.Path), start)

	if r.Method != "GET" {
		finish(w, http.StatusMethodNotAllowed, "Method not allowed.")
		return
	}

	q := r.URL.Query()

	s, err := strconv.Atoi(q.Get("s"))
	if err != nil {
		s = 1
	}

	iw, err := strconv.Atoi(q.Get("w"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid w")
		return
	}

	ih, err := strconv.Atoi(q.Get("h"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid h")
		return
	}

	i, err := strconv.Atoi(q.Get("i"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid i")
		return
	}

	er, err := strconv.ParseFloat(q.Get("e"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid e")
		return
	}

	rmin, err := strconv.ParseFloat(q.Get("rmin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	imin, err := strconv.ParseFloat(q.Get("imin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	rmax, err := strconv.ParseFloat(q.Get("rmax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmax")
		return
	}

	imax, err := strconv.ParseFloat(q.Get("imax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	c := q.Get("c")
	color_func := gofr.ColorMono
	switch c {
	case "mono":
		color_func = gofr.ColorMono
	case "stripe":
		color_func = gofr.ColorMonoStripe
	case "bands":
		color_func = gofr.ColorBands
	case "smooth":
		color_func = gofr.ColorSmooth
	default:
		finish(w, http.StatusUnprocessableEntity, "Invalid c")
		return
	}

	hex := q.Get("m")
	member_color := color.NRGBA64{0, 0, 0, 0xffff}
	if len(hex) > 2 {
		m, err := strconv.ParseInt(hex[1:len(hex)], 16, 32)
		if err != nil {
			finish(w, http.StatusUnprocessableEntity, "Invalid m")
			return
		}
		member_color = color.NRGBA64{
			uint16(((m >> 16) & 0xff) * 0x101),
			uint16(((m >> 8) & 0xff) * 0x101),
			uint16((m & 0xff) * 0x101),
			0xffff,
		}
	}

	p := gofr.Parameters{
		ImageWidth:   iw * s,
		ImageHeight:  ih * s,
		MaxI:         i,
		EscapeRadius: er,
		Min:          complex(rmin, imin),
		Max:          complex(rmax, imax),
		ColorFunc:    color_func,
		MemberColor:  member_color,
	}

	log.Printf("%08d rendering\n", id)

	// TODO: Check parameters and set reasonable bounds on what we can
	// quickly calculate.

	img := image.NewNRGBA64(image.Rect(0, 0, p.ImageWidth, p.ImageHeight))
	n := runtime.NumCPU()
	contexts := gofr.MakeContexts(img, n, &p)
	gofr.Render(n, contexts, gofr.Mandelbrot)

	scaled_img := resize.Resize(uint(iw), uint(ih), image.Image(img), resize.Lanczos3)

	w.Header().Set("Content-Type", "image/png")
	png.Encode(w, scaled_img)
}

func main() {
	fs := http.FileServer(http.Dir("static"))
	bind_addr := "0.0.0.0:8000"

	go func() {
		for i := 0; ; i++ {
			id_chan <- i
		}
	}()

	http.Handle("/", fs)
	http.HandleFunc("/png", route_png)
	log.Printf("Listening on: %s\n", bind_addr)
	log.Fatal(http.ListenAndServe(bind_addr, nil))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"regexp"
	"time"

	"github.com/codegangsta/cli"
)

const alphanumericRegex = "^[a-zA-Z0-9_-]*$"

var repository = TaskCsvRepository{Path: "./db.csv"}
var transformer = Transformer{}
var commands = []cli.Command{
	{
		Name:   "start",
		Usage:  "Start tracking a given task",
		Action: Start,
	},
	{
		Name:   "stop",
		Usage:  "Stop tracking a given task",
		Action: Stop,
	},
	{
		Name:   "status",
		Usage:  "Give status of all tasks",
		Action: Status,
	},
	{
		Name:   "list",
		Usage:  "List all tasks",
		Action: List,
	},
}

// Start a given task
func Start(context *cli.Context) {
	identifier := context.Args().First()
	if !IsValidIdentifier(identifier) {
		cli.ShowCommandHelp(context, context.Command.FullName())
	}

	repository.save(Task{Identifier: identifier, Action: "start", At: time.Now().Format(time.RFC3339)})

	fmt.Println("Started tracking ", identifier)
}

// Stop a given task
func Stop(context *cli.Context) {
	identifier := context.Args().First()
	if !IsValidIdentifier(identifier) {
		cli.ShowCommandHelp(context, context.Command.FullName())
	}

	repository.save(Task{Identifier: identifier, Action: "stop", At: time.Now().Format(time.RFC3339)})

	fmt.Println("Stopped tracking ", identifier)
}

// Status display tasks being tracked
func Status(context *cli.Context) {
	identifier := context.Args().First()
	if !IsValidIdentifier(identifier) {
		cli.ShowCommandHelp(context, context.Command.FullName())
	}

	transformer.LoadedTasks = repository.load().getByIdentifier(identifier)
	fmt.Println(transformer.Transform()[identifier])
}

// List lists all tasks
func List(context *cli.Context) {
	transformer.LoadedTasks = repository.load()
	for _, task := range transformer.Transform() {
		fmt.Println(task)
	}
}

// IsValidIdentifier checks if the string passed is a valid task identifier
func IsValidIdentifier(identifier string) bool {
	re := regexp.MustCompile(alphanumericRegex)
	return len(identifier) > 0 && re.MatchString(identifier)
}

func main() {
	app := cli.NewApp()
	app.Name = "Golog"
	app.Usage = "Easy CLI time tracker for your tasks"
	app.Version = "0.1"
	app.Commands = commands
	app.Run(os.Args)
}
<commit_msg>create initial db file<commit_after>package main

import (
	"fmt"
	"os"
	"regexp"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mitchellh/go-homedir"
)

const alphanumericRegex = "^[a-zA-Z0-9_-]*$"
const dbFile = "~/.golog"

var dbPath, _ = homedir.Expand(dbFile)
var repository = TaskCsvRepository{Path: dbPath}
var transformer = Transformer{}
var commands = []cli.Command{
	{
		Name:   "start",
		Usage:  "Start tracking a given task",
		Action: Start,
	},
	{
		Name:   "stop",
		Usage:  "Stop tracking a given task",
		Action: Stop,
	},
	{
		Name:   "status",
		Usage:  "Give status of all tasks",
		Action: Status,
	},
	{
		Name:   "list",
		Usage:  "List all tasks",
		Action: List,
	},
}

// Start a given task
func Start(context *cli.Context) {
	identifier := context.Args().First()
	if !IsValidIdentifier(identifier) {
		cli.ShowCommandHelp(context, context.Command.FullName())
	}

	repository.save(Task{Identifier: identifier, Action: "start", At: time.Now().Format(time.RFC3339)})

	fmt.Println("Started tracking ", identifier)
}

// Stop a given task
func Stop(context *cli.Context) {
	identifier := context.Args().First()
	if !IsValidIdentifier(identifier) {
		cli.ShowCommandHelp(context, context.Command.FullName())
	}

	repository.save(Task{Identifier: identifier, Action: "stop", At: time.Now().Format(time.RFC3339)})

	fmt.Println("Stopped tracking ", identifier)
}

// Status display tasks being tracked
func Status(context *cli.Context) {
	identifier := context.Args().First()
	if !IsValidIdentifier(identifier) {
		cli.ShowCommandHelp(context, context.Command.FullName())
	}

	transformer.LoadedTasks = repository.load().getByIdentifier(identifier)
	fmt.Println(transformer.Transform()[identifier])
}

// List lists all tasks
func List(context *cli.Context) {
	transformer.LoadedTasks = repository.load()
	for _, task := range transformer.Transform() {
		fmt.Println(task)
	}
}

// IsValidIdentifier checks if the string passed is a valid task identifier
func IsValidIdentifier(identifier string) bool {
	re := regexp.MustCompile(alphanumericRegex)
	return len(identifier) > 0 && re.MatchString(identifier)
}

func checkInitialDbFile() {
	if _, err := os.Stat(dbPath); os.IsNotExist(err) {
		os.Create(dbPath)
	}
}

func main() {
	// @todo remove this from here, should be in file repo implementation
	checkInitialDbFile()
	app := cli.NewApp()
	app.Name = "Golog"
	app.Usage = "Easy CLI time tracker for your tasks"
	app.Version = "0.1"
	app.Commands = commands
	app.Run(os.Args)
}
<|endoftext|>