<commit_before>package exif44

import (
	"bytes"
	"encoding/binary"
	"errors"
	tiff "github.com/garyhouston/tiff66"
)

// Tags in the Exif IFD.
const (
	ExposureTime              = 0x829A
	FNumber                   = 0x829D
	ExposureProgram           = 0x8822
	SpectralSensitivity       = 0x8824
	PhotographicSensitivity   = 0x8827
	OECF                      = 0x8828
	SensitivityType           = 0x8830
	StandardOutputSensitivity = 0x8831
	RecommendedExposureIndex  = 0x8832
	ISOSpeed                  = 0x8833
	ISOSpeedLatitudeyyy       = 0x8834
	ISOSpeedLatitudezzz       = 0x8835
	ExifVersion               = 0x9000
	DateTimeOriginal          = 0x9003
	DateTimeDigitized         = 0x9004
	ComponentsConfiguration   = 0x9101
	CompressedBitsPerPixel    = 0x9102
	ShutterSpeedValue         = 0x9201
	ApertureValue             = 0x9202
	BrightnessValue           = 0x9203
	ExposureBiasValue         = 0x9204
	MaxApertureValue          = 0x9205
	SubjectDistance           = 0x9206
	MeteringMode              = 0x9207
	LightSource               = 0x9208
	Flash                     = 0x9209
	FocalLength               = 0x920A
	SubjectArea               = 0x9214
	MakerNote                 = 0x927C
	UserComment               = 0x9286
	SubSecTime                = 0x9290
	SubSecTimeOriginal        = 0x9291
	SubSecTimeDigitized       = 0x9292
	FlashpixVersion           = 0xA000
	ColorSpace                = 0xA001
	PixelXDimension           = 0xA002
	PixelYDimension           = 0xA003
	RelatedSoundFile          = 0xA004
	InteroperabilityIFD       = 0xA005
	FlashEnergy               = 0xA20B
	SpatialFrequencyResponse  = 0xA20C
	FocalPlaneXResolution     = 0xA20E
	FocalPlaneYResolution     = 0xA20F
	FocalPlaneResolutionUnit  = 0xA210
	SubjectLocation           = 0xA214
	ExposureIndex             = 0xA215
	SensingMethod             = 0xA217
	FileSource                = 0xA300
	SceneType                 = 0xA301
	CFAPattern                = 0xA302
	CustomRendered            = 0xA401
	ExposureMode              = 0xA402
	WhiteBalance              = 0xA403
	DigitalZoomRatio          = 0xA404
	FocalLengthIn35mmFilm     = 0xA405
	SceneCaptureType          = 0xA406
	GainControl               = 0xA407
	Contrast                  = 0xA408
	Saturation                = 0xA409
	Sharpness                 = 0xA40A
	DeviceSettingDescription  = 0xA40B
	SubjectDistanceRange      = 0xA40C
	ImageUniqueID             = 0xA420
	CameraOwnerName           = 0xA430
	BodySerialNumber          = 0xA431
	LensSpecification         = 0xA432
	LensMake                  = 0xA433
	LensModel                 = 0xA434
	LensSerialNumber          = 0xA435
	Gamma                     = 0xA500
)

// Mapping from Exif tags to strings.
var ExifTagNames = map[tiff.Tag]string{
	ExposureTime:            "ExposureTime",
	FNumber:                 "FNumber",
	ExposureProgram:         "ExposureProgram",
	SpectralSensitivity:     "SpectralSensitivity",
	PhotographicSensitivity: "PhotographicSensitivity",
	OECF:                      "OECF",
	SensitivityType:           "SensitivityType",
	StandardOutputSensitivity: "StandardOutputSensitivity",
	RecommendedExposureIndex:  "RecommendedExposureIndex",
	ISOSpeed:                  "ISOSpeed",
	ISOSpeedLatitudeyyy:       "ISOSpeedLatitudeyyy",
	ISOSpeedLatitudezzz:       "ISOSpeedLatitudezzz",
	ExifVersion:               "ExifVersion",
	DateTimeOriginal:          "DateTimeOriginal",
	DateTimeDigitized:         "DateTimeDigitized",
	ComponentsConfiguration:   "ComponentsConfiguration",
	CompressedBitsPerPixel:    "CompressedBitsPerPixel",
	ShutterSpeedValue:         "ShutterSpeedValue",
	ApertureValue:             "ApertureValue",
	BrightnessValue:           "BrightnessValue",
	ExposureBiasValue:         "ExposureBiasValue",
	MaxApertureValue:          "MaxApertureValue",
	SubjectDistance:           "SubjectDistance",
	MeteringMode:              "MeteringMode",
	LightSource:               "LightSource",
	Flash:                     "Flash",
	FocalLength:               "FocalLength",
	SubjectArea:               "SubjectArea",
	MakerNote:                 "MakerNote",
	UserComment:               "UserComment",
	SubSecTime:                "SubSecTime",
	SubSecTimeOriginal:        "SubSecTimeOriginal",
	SubSecTimeDigitized:       "SubSecTimeDigitized",
	FlashpixVersion:           "FlashpixVersion",
	ColorSpace:                "ColorSpace",
	PixelXDimension:           "PixelXDimension",
	PixelYDimension:           "PixelYDimension",
	RelatedSoundFile:          "RelatedSoundFile",
	InteroperabilityIFD:       "InteroperabilityIFD",
	FlashEnergy:               "FlashEnergy",
	SpatialFrequencyResponse:  "SpatialFrequencyResponse",
	FocalPlaneXResolution:     "FocalPlaneXResolution",
	FocalPlaneYResolution:     "FocalPlaneYResolution",
	FocalPlaneResolutionUnit:  "FocalPlaneResolutionUnit",
	SubjectLocation:           "SubjectLocation",
	ExposureIndex:             "ExposureIndex",
	SensingMethod:             "SensingMethod",
	FileSource:                "FileSource",
	SceneType:                 "SceneType",
	CFAPattern:                "CFAPattern",
	CustomRendered:            "CustomRendered",
	ExposureMode:              "ExposureMode",
	WhiteBalance:              "WhiteBalance",
	DigitalZoomRatio:          "DigitalZoomRatio",
	FocalLengthIn35mmFilm:     "FocalLengthIn35mmFilm",
	SceneCaptureType:          "SceneCaptureType",
	GainControl:               "GainControl",
	Contrast:                  "Contrast",
	Saturation:                "Saturation",
	Sharpness:                 "Sharpness",
	DeviceSettingDescription:  "DeviceSettingDescription",
	SubjectDistanceRange:      "SubjectDistanceRange",
	ImageUniqueID:             "ImageUniqueID",
	CameraOwnerName:           "CameraOwnerName",
	BodySerialNumber:          "BodySerialNumber",
	LensSpecification:         "LensSpecification",
	LensMake:                  "LensMake",
	LensModel:                 "LensModel",
	LensSerialNumber:          "LensSerialNumber",
	Gamma:                     "Gamma",
}

// Tags in the Interoperability IFD.
const (
	InteroperabilityIndex   = 0x1
	InteroperabilityVersion = 0x2
)

// Mapping from Interoperability tags to strings.
var InteropTagNames = map[tiff.Tag]string{
	InteroperabilityIndex:   "InteroperabilityIndex",
	InteroperabilityVersion: "InteroperabilityVersion",
}

// Tags in the GPS IFD.
const (
	GPSVersionID         = 0x00
	GPSLatitudeRef       = 0x01
	GPSLatitude          = 0x02
	GPSLongitudeRef      = 0x03
	GPSLongitude         = 0x04
	GPSAltitudeRef       = 0x05
	GPSAltitude          = 0x06
	GPSTimeStamp         = 0x07
	GPSSatellites        = 0x08
	GPSStatus            = 0x09
	GPSMeasureMode       = 0x0A
	GPSDOP               = 0x0B
	GPSSpeedRef          = 0x0C
	GPSSpeed             = 0x0D
	GPSTrackRef          = 0x0E
	GPSTrack             = 0x0F
	GPSImgDirectionRef   = 0x10
	GPSImgDirection      = 0x11
	GPSMapDatum          = 0x12
	GPSDestLatitudeRef   = 0x13
	GPSDestLatitude      = 0x14
	GPSDestLongitudeRef  = 0x15
	GPSDestLongitude     = 0x16
	GPSDestBearingRef    = 0x17
	GPSDestBearing       = 0x18
	GPSDestDistanceRef   = 0x19
	GPSDestDistance      = 0x1A
	GPSProcessingMethod  = 0x1B
	GPSAreaInformation   = 0x1C
	GPSDateStamp         = 0x1D
	GPSDifferential      = 0x1E
	GPSHPositioningError = 0x1F
)

// Mapping from GPS tags to strings.
var GPSTagNames = map[tiff.Tag]string{
	GPSVersionID:         "GPSVersionID",
	GPSLatitudeRef:       "GPSLatitudeRef",
	GPSLatitude:          "GPSLatitude",
	GPSLongitudeRef:      "GPSLongitudeRef",
	GPSLongitude:         "GPSLongitude",
	GPSAltitudeRef:       "GPSAltitudeRef",
	GPSAltitude:          "GPSAltitude",
	GPSTimeStamp:         "GPSTimeStamp",
	GPSSatellites:        "GPSSatellites",
	GPSStatus:            "GPSStatus",
	GPSMeasureMode:       "GPSMeasureMode",
	GPSDOP:               "GPSDOP",
	GPSSpeedRef:          "GPSSpeedRef",
	GPSSpeed:             "GPSSpeed",
	GPSTrackRef:          "GPSTrackRef",
	GPSTrack:             "GPSTrack",
	GPSImgDirectionRef:   "GPSImgDirectionRef",
	GPSImgDirection:      "GPSImgDirection",
	GPSMapDatum:          "GPSMapDatum",
	GPSDestLatitudeRef:   "GPSDestLatitudeRef",
	GPSDestLatitude:      "GPSDestLatitude",
	GPSDestLongitudeRef:  "GPSDestLongitudeRef",
	GPSDestLongitude:     "GPSDestLongitude",
	GPSDestBearingRef:    "GPSDestBearingRef",
	GPSDestBearing:       "GPSDestBearing",
	GPSDestDistanceRef:   "GPSDestDistanceRef",
	GPSDestDistance:      "GPSDestDistance",
	GPSProcessingMethod:  "GPSProcessingMethod",
	GPSAreaInformation:   "GPSAreaInformation",
	GPSDateStamp:         "GPSDateStamp",
	GPSDifferential:      "GPSDifferential",
	GPSHPositioningError: "GPSHPositioningError",
}

// Return the tag->name map for given namespace, or a nil map if the space
// is unknown.
func TagNameMap(space tiff.TagSpace) map[tiff.Tag]string {
	var names map[tiff.Tag]string
	switch space {
	case tiff.TIFFSpace:
		names = tiff.TagNames
	case tiff.ExifSpace:
		names = ExifTagNames
	case tiff.InteropSpace:
		names = InteropTagNames
	case tiff.GPSSpace:
		names = GPSTagNames
	case tiff.Panasonic1Space:
		names = Panasonic1TagNames
	}
	return names
}

var header = []byte("Exif\000\000")

// Return the size of a Exif header, as found in a JPEG APP1 segment.
func HeaderSize() uint32 {
	return uint32(len(header))
}

// Check if a slice starts with an Exif header, as found in a JPEG
// APP1 segment.  Returns a flag and the position of the next byte.
func GetHeader(buf []byte) (bool, uint32) {
	headLen := HeaderSize()
	if uint32(len(buf)) >= headLen && bytes.Compare(buf[:headLen], header) == 0 {
		return true, headLen
	} else {
		return false, 0
	}
}

// Put an Exif header, as for a JPEG APP1 segment, at the start of a slice,
// returning the position of the next byte.
func PutHeader(buf []byte) uint32 {
	copy(buf, header)
	return HeaderSize()
}

type Exif struct {
	Order     binary.ByteOrder
	Tree      *tiff.IFDNode // Tree with all IFDs and image data.
	TIFF      *tiff.IFD_T   // Pointer to TIFF IFD0 in Tree.
	Exif      *tiff.IFD_T   // Pointer to Exif IFD in Tree.
	GPS       *tiff.IFD_T   // Pointer to GPS IFD in Tree.
	Interop   *tiff.IFD_T   // Pointer to Interop IFD in Tree.
	Thumbnail *tiff.IFD_T   // Pointer to TIFF IFD1 in Tree.
}

// Unpack a TIFF header and tree from a slice, as for GetHeader and
// GetIFDTree from tiff66, and also return pointers to any Exif
// subIFDs that are present. As for GetIFDTree, field data in the
// structures points into the original byte slice, so modifying one
// will modify the other.
func GetExifTree(buf []byte) (*Exif, error) {
	valid, order, ifdpos := tiff.GetHeader(buf)
	if !valid {
		return nil, errors.New("GetExif: Invalid Tiff header")
	}
	node, err := tiff.GetIFDTree(buf, order, ifdpos, tiff.TIFFSpace)
	if err != nil {
		return nil, err
	}
	exif := Exif{}
	exif.Tree = node
	exif.Order = order
	exif.TIFF = &node.IFD
	for _, sub := range node.SubIFDs {
		if sub.Node.Space == tiff.ExifSpace {
			exif.Exif = &sub.Node.IFD
			for _, esub := range sub.Node.SubIFDs {
				if esub.Node.Space == tiff.InteropSpace {
					exif.Interop = &esub.Node.IFD
				}
			}
		} else if sub.Node.Space == tiff.GPSSpace {
			exif.GPS = &sub.Node.IFD
		}
	}
	if node.Next != nil {
		exif.Thumbnail = &node.Next.IFD
	}
	return &exif, nil
}

// Return the size of a buffer needed to serialize the tree in an Exif
// structure in TIFF format, including the TIFF header, but excluding
// the Exif header used in JPEG files.
func (exif Exif) TreeSize() uint32 {
	return tiff.HeaderSize() + exif.Tree.TreeSize()
}

// Pack Exif data into a slice in TIFF format. The slice should start
// with the first byte following any Exif header. Returns the position
// following the last byte used.
func (exif *Exif) Put(buf []byte) (uint32, error) {
	tiff.PutHeader(buf, exif.Order, tiff.HeaderSize())
	return exif.Tree.PutIFDTree(buf, tiff.HeaderSize(), exif.Order)
}
<commit_msg>Convert HeaderSize to a constant, and adjust for same change in tiff66.<commit_after>package exif44

import (
	"bytes"
	"encoding/binary"
	"errors"
	tiff "github.com/garyhouston/tiff66"
)

// Tags in the Exif IFD.
const (
	ExposureTime              = 0x829A
	FNumber                   = 0x829D
	ExposureProgram           = 0x8822
	SpectralSensitivity       = 0x8824
	PhotographicSensitivity   = 0x8827
	OECF                      = 0x8828
	SensitivityType           = 0x8830
	StandardOutputSensitivity = 0x8831
	RecommendedExposureIndex  = 0x8832
	ISOSpeed                  = 0x8833
	ISOSpeedLatitudeyyy       = 0x8834
	ISOSpeedLatitudezzz       = 0x8835
	ExifVersion               = 0x9000
	DateTimeOriginal          = 0x9003
	DateTimeDigitized         = 0x9004
	ComponentsConfiguration   = 0x9101
	CompressedBitsPerPixel    = 0x9102
	ShutterSpeedValue         = 0x9201
	ApertureValue             = 0x9202
	BrightnessValue           = 0x9203
	ExposureBiasValue         = 0x9204
	MaxApertureValue          = 0x9205
	SubjectDistance           = 0x9206
	MeteringMode              = 0x9207
	LightSource               = 0x9208
	Flash                     = 0x9209
	FocalLength               = 0x920A
	SubjectArea               = 0x9214
	MakerNote                 = 0x927C
	UserComment               = 0x9286
	SubSecTime                = 0x9290
	SubSecTimeOriginal        = 0x9291
	SubSecTimeDigitized       = 0x9292
	FlashpixVersion           = 0xA000
	ColorSpace                = 0xA001
	PixelXDimension           = 0xA002
	PixelYDimension           = 0xA003
	RelatedSoundFile          = 0xA004
	InteroperabilityIFD       = 0xA005
	FlashEnergy               = 0xA20B
	SpatialFrequencyResponse  = 0xA20C
	FocalPlaneXResolution     = 0xA20E
	FocalPlaneYResolution     = 0xA20F
	FocalPlaneResolutionUnit  = 0xA210
	SubjectLocation           = 0xA214
	ExposureIndex             = 0xA215
	SensingMethod             = 0xA217
	FileSource                = 0xA300
	SceneType                 = 0xA301
	CFAPattern                = 0xA302
	CustomRendered            = 0xA401
	ExposureMode              = 0xA402
	WhiteBalance              = 0xA403
	DigitalZoomRatio          = 0xA404
	FocalLengthIn35mmFilm     = 0xA405
	SceneCaptureType          = 0xA406
	GainControl               = 0xA407
	Contrast                  = 0xA408
	Saturation                = 0xA409
	Sharpness                 = 0xA40A
	DeviceSettingDescription  = 0xA40B
	SubjectDistanceRange      = 0xA40C
	ImageUniqueID             = 0xA420
	CameraOwnerName           = 0xA430
	BodySerialNumber          = 0xA431
	LensSpecification         = 0xA432
	LensMake                  = 0xA433
	LensModel                 = 0xA434
	LensSerialNumber          = 0xA435
	Gamma                     = 0xA500
)

// Mapping from Exif tags to strings.
var ExifTagNames = map[tiff.Tag]string{
	ExposureTime:            "ExposureTime",
	FNumber:                 "FNumber",
	ExposureProgram:         "ExposureProgram",
	SpectralSensitivity:     "SpectralSensitivity",
	PhotographicSensitivity: "PhotographicSensitivity",
	OECF:                      "OECF",
	SensitivityType:           "SensitivityType",
	StandardOutputSensitivity: "StandardOutputSensitivity",
	RecommendedExposureIndex:  "RecommendedExposureIndex",
	ISOSpeed:                  "ISOSpeed",
	ISOSpeedLatitudeyyy:       "ISOSpeedLatitudeyyy",
	ISOSpeedLatitudezzz:       "ISOSpeedLatitudezzz",
	ExifVersion:               "ExifVersion",
	DateTimeOriginal:          "DateTimeOriginal",
	DateTimeDigitized:         "DateTimeDigitized",
	ComponentsConfiguration:   "ComponentsConfiguration",
	CompressedBitsPerPixel:    "CompressedBitsPerPixel",
	ShutterSpeedValue:         "ShutterSpeedValue",
	ApertureValue:             "ApertureValue",
	BrightnessValue:           "BrightnessValue",
	ExposureBiasValue:         "ExposureBiasValue",
	MaxApertureValue:          "MaxApertureValue",
	SubjectDistance:           "SubjectDistance",
	MeteringMode:              "MeteringMode",
	LightSource:               "LightSource",
	Flash:                     "Flash",
	FocalLength:               "FocalLength",
	SubjectArea:               "SubjectArea",
	MakerNote:                 "MakerNote",
	UserComment:               "UserComment",
	SubSecTime:                "SubSecTime",
	SubSecTimeOriginal:        "SubSecTimeOriginal",
	SubSecTimeDigitized:       "SubSecTimeDigitized",
	FlashpixVersion:           "FlashpixVersion",
	ColorSpace:                "ColorSpace",
	PixelXDimension:           "PixelXDimension",
	PixelYDimension:           "PixelYDimension",
	RelatedSoundFile:          "RelatedSoundFile",
	InteroperabilityIFD:       "InteroperabilityIFD",
	FlashEnergy:               "FlashEnergy",
	SpatialFrequencyResponse:  "SpatialFrequencyResponse",
	FocalPlaneXResolution:     "FocalPlaneXResolution",
	FocalPlaneYResolution:     "FocalPlaneYResolution",
	FocalPlaneResolutionUnit:  "FocalPlaneResolutionUnit",
	SubjectLocation:           "SubjectLocation",
	ExposureIndex:             "ExposureIndex",
	SensingMethod:             "SensingMethod",
	FileSource:                "FileSource",
	SceneType:                 "SceneType",
	CFAPattern:                "CFAPattern",
	CustomRendered:            "CustomRendered",
	ExposureMode:              "ExposureMode",
	WhiteBalance:              "WhiteBalance",
	DigitalZoomRatio:          "DigitalZoomRatio",
	FocalLengthIn35mmFilm:     "FocalLengthIn35mmFilm",
	SceneCaptureType:          "SceneCaptureType",
	GainControl:               "GainControl",
	Contrast:                  "Contrast",
	Saturation:                "Saturation",
	Sharpness:                 "Sharpness",
	DeviceSettingDescription:  "DeviceSettingDescription",
	SubjectDistanceRange:      "SubjectDistanceRange",
	ImageUniqueID:             "ImageUniqueID",
	CameraOwnerName:           "CameraOwnerName",
	BodySerialNumber:          "BodySerialNumber",
	LensSpecification:         "LensSpecification",
	LensMake:                  "LensMake",
	LensModel:                 "LensModel",
	LensSerialNumber:          "LensSerialNumber",
	Gamma:                     "Gamma",
}

// Tags in the Interoperability IFD.
const (
	InteroperabilityIndex   = 0x1
	InteroperabilityVersion = 0x2
)

// Mapping from Interoperability tags to strings.
var InteropTagNames = map[tiff.Tag]string{
	InteroperabilityIndex:   "InteroperabilityIndex",
	InteroperabilityVersion: "InteroperabilityVersion",
}

// Tags in the GPS IFD.
const (
	GPSVersionID         = 0x00
	GPSLatitudeRef       = 0x01
	GPSLatitude          = 0x02
	GPSLongitudeRef      = 0x03
	GPSLongitude         = 0x04
	GPSAltitudeRef       = 0x05
	GPSAltitude          = 0x06
	GPSTimeStamp         = 0x07
	GPSSatellites        = 0x08
	GPSStatus            = 0x09
	GPSMeasureMode       = 0x0A
	GPSDOP               = 0x0B
	GPSSpeedRef          = 0x0C
	GPSSpeed             = 0x0D
	GPSTrackRef          = 0x0E
	GPSTrack             = 0x0F
	GPSImgDirectionRef   = 0x10
	GPSImgDirection      = 0x11
	GPSMapDatum          = 0x12
	GPSDestLatitudeRef   = 0x13
	GPSDestLatitude      = 0x14
	GPSDestLongitudeRef  = 0x15
	GPSDestLongitude     = 0x16
	GPSDestBearingRef    = 0x17
	GPSDestBearing       = 0x18
	GPSDestDistanceRef   = 0x19
	GPSDestDistance      = 0x1A
	GPSProcessingMethod  = 0x1B
	GPSAreaInformation   = 0x1C
	GPSDateStamp         = 0x1D
	GPSDifferential      = 0x1E
	GPSHPositioningError = 0x1F
)

// Mapping from GPS tags to strings.
var GPSTagNames = map[tiff.Tag]string{
	GPSVersionID:         "GPSVersionID",
	GPSLatitudeRef:       "GPSLatitudeRef",
	GPSLatitude:          "GPSLatitude",
	GPSLongitudeRef:      "GPSLongitudeRef",
	GPSLongitude:         "GPSLongitude",
	GPSAltitudeRef:       "GPSAltitudeRef",
	GPSAltitude:          "GPSAltitude",
	GPSTimeStamp:         "GPSTimeStamp",
	GPSSatellites:        "GPSSatellites",
	GPSStatus:            "GPSStatus",
	GPSMeasureMode:       "GPSMeasureMode",
	GPSDOP:               "GPSDOP",
	GPSSpeedRef:          "GPSSpeedRef",
	GPSSpeed:             "GPSSpeed",
	GPSTrackRef:          "GPSTrackRef",
	GPSTrack:             "GPSTrack",
	GPSImgDirectionRef:   "GPSImgDirectionRef",
	GPSImgDirection:      "GPSImgDirection",
	GPSMapDatum:          "GPSMapDatum",
	GPSDestLatitudeRef:   "GPSDestLatitudeRef",
	GPSDestLatitude:      "GPSDestLatitude",
	GPSDestLongitudeRef:  "GPSDestLongitudeRef",
	GPSDestLongitude:     "GPSDestLongitude",
	GPSDestBearingRef:    "GPSDestBearingRef",
	GPSDestBearing:       "GPSDestBearing",
	GPSDestDistanceRef:   "GPSDestDistanceRef",
	GPSDestDistance:      "GPSDestDistance",
	GPSProcessingMethod:  "GPSProcessingMethod",
	GPSAreaInformation:   "GPSAreaInformation",
	GPSDateStamp:         "GPSDateStamp",
	GPSDifferential:      "GPSDifferential",
	GPSHPositioningError: "GPSHPositioningError",
}

// Return the tag->name map for given namespace, or a nil map if the space
// is unknown.
func TagNameMap(space tiff.TagSpace) map[tiff.Tag]string {
	var names map[tiff.Tag]string
	switch space {
	case tiff.TIFFSpace:
		names = tiff.TagNames
	case tiff.ExifSpace:
		names = ExifTagNames
	case tiff.InteropSpace:
		names = InteropTagNames
	case tiff.GPSSpace:
		names = GPSTagNames
	case tiff.Panasonic1Space:
		names = Panasonic1TagNames
	}
	return names
}

// Exif header, as found in a JPEG APP1 segment.
var header = []byte("Exif\000\000")

// Size of an Exif header.
const HeaderSize = 6

// Check if a slice starts with an Exif header, as found in a JPEG
// APP1 segment.  Returns a flag and the position of the next byte.
func GetHeader(buf []byte) (bool, uint32) {
	if uint32(len(buf)) >= HeaderSize && bytes.Compare(buf[:HeaderSize], header) == 0 {
		return true, HeaderSize
	} else {
		return false, 0
	}
}

// Put an Exif header, as for a JPEG APP1 segment, at the start of a slice,
// returning the position of the next byte.
func PutHeader(buf []byte) uint32 {
	copy(buf, header)
	return HeaderSize
}

type Exif struct {
	Order     binary.ByteOrder
	Tree      *tiff.IFDNode // Tree with all IFDs and image data.
	TIFF      *tiff.IFD_T   // Pointer to TIFF IFD0 in Tree.
	Exif      *tiff.IFD_T   // Pointer to Exif IFD in Tree.
	GPS       *tiff.IFD_T   // Pointer to GPS IFD in Tree.
	Interop   *tiff.IFD_T   // Pointer to Interop IFD in Tree.
	Thumbnail *tiff.IFD_T   // Pointer to TIFF IFD1 in Tree.
}

// Unpack a TIFF header and tree from a slice, as for GetHeader and
// GetIFDTree from tiff66, and also return pointers to any Exif
// subIFDs that are present. As for GetIFDTree, field data in the
// structures points into the original byte slice, so modifying one
// will modify the other.
func GetExifTree(buf []byte) (*Exif, error) {
	valid, order, ifdpos := tiff.GetHeader(buf)
	if !valid {
		return nil, errors.New("GetExif: Invalid Tiff header")
	}
	node, err := tiff.GetIFDTree(buf, order, ifdpos, tiff.TIFFSpace)
	if err != nil {
		return nil, err
	}
	exif := Exif{}
	exif.Tree = node
	exif.Order = order
	exif.TIFF = &node.IFD
	for _, sub := range node.SubIFDs {
		if sub.Node.Space == tiff.ExifSpace {
			exif.Exif = &sub.Node.IFD
			for _, esub := range sub.Node.SubIFDs {
				if esub.Node.Space == tiff.InteropSpace {
					exif.Interop = &esub.Node.IFD
				}
			}
		} else if sub.Node.Space == tiff.GPSSpace {
			exif.GPS = &sub.Node.IFD
		}
	}
	if node.Next != nil {
		exif.Thumbnail = &node.Next.IFD
	}
	return &exif, nil
}

// Return the size of a buffer needed to serialize the tree in an Exif
// structure in TIFF format, including the TIFF header, but excluding
// the Exif header used in JPEG files.
func (exif Exif) TreeSize() uint32 {
	return tiff.HeaderSize + exif.Tree.TreeSize()
}

// Pack Exif data into a slice in TIFF format. The slice should start
// with the first byte following any Exif header. Returns the position
// following the last byte used.
func (exif *Exif) Put(buf []byte) (uint32, error) {
	tiff.PutHeader(buf, exif.Order, tiff.HeaderSize)
	return exif.Tree.PutIFDTree(buf, tiff.HeaderSize, exif.Order)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

var mainFinder = regexp.MustCompile(`^(?:/\*(?:.|[\r\n])*?\*/)*|(?://.*\n)*package\s+main`)

func getTargets() map[string]string {
	targets := map[string]string{}
	processed := map[string]bool{}
	toResolve := map[string][]string{}

	walkFn := func(path string, info os.FileInfo, err error) error {
		if string(path[0]) == "." {
			return filepath.SkipDir
		}
		if !info.IsDir() {
			// We're looking at a file somewhere

			fileDir := filepath.Dir(path)
			if processed[fileDir] {
				// only need to process one .go file per directory
				return nil
			}
			if filepath.Ext(path) == ".go" {
				// We've found the first .go file in this directory
				debug("looking at", path)

				// check for "package main"
				fh, err := os.Open(path)
				if err != nil {
					fatal("error opening source file:", err)
				}
				defer fh.Close()
				buf := bufio.NewReader(fh)

				if !mainFinder.MatchReader(buf) {
					// If the first .go file we find doesn't have "package main"
					// at the beginning, we're not interested in this directory
					debug(`does not have "package main"`)
					processed[fileDir] = true
					return nil
				}

				// Found package main, this is a binary
				targetName := filepath.Base(fileDir)
				debugf("found \"package main\", adding target [%s]\n", targetName)

				// See if there is a conflicting target name
				if _, found := targets[targetName]; found {
					debug("Conflicting target name", targetName, "found")
					// Is this the first conflict? Start the queue with the
					// original if so.
					if _, found := toResolve[targetName]; !found {
						toResolve[targetName] = []string{targets[targetName]}
					}
					// Add the conflicting target
					toResolve[targetName] = append(toResolve[targetName], fileDir)

				} else {
					// Add the target hame
					targets[targetName] = fileDir
				}
				processed[fileDir] = true
			}
		}
		return nil
	}
	err := filepath.Walk(cwd, walkFn)
	if err != nil {
		fatal("Error scanning directory:", err)
	}
	// resolve any conflicting target names
	for targetName, paths := range toResolve {
		// remove the conflicting entry from the targets list
		delete(targets, targetName)
		for _, path := range paths {
			newTargetName := strings.Replace(path, cwd, "", 1)
			newTargetName = strings.Replace(newTargetName, string(os.PathSeparator), "", 1)
			newTargetName = strings.Replace(newTargetName, string(os.PathSeparator), "_", -1)
			targets[newTargetName] = path
		}
	}
	return targets
}
<commit_msg>regex tweak<commit_after>package main

import (
	"bufio"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

var mainFinder = regexp.MustCompile(`^(?:(?:/\*(?:.|[\r\n])*?\*/)|(?://.*\n))*package\s+main`)

func getTargets() map[string]string {
	targets := map[string]string{}
	processed := map[string]bool{}
	toResolve := map[string][]string{}

	walkFn := func(path string, info os.FileInfo, err error) error {
		if string(path[0]) == "." {
			return filepath.SkipDir
		}
		if !info.IsDir() {
			// We're looking at a file somewhere

			fileDir := filepath.Dir(path)
			if processed[fileDir] {
				// only need to process one .go file per directory
				return nil
			}
			if filepath.Ext(path) == ".go" {
				// We've found the first .go file in this directory
				debug("looking at", path)

				// check for "package main"
				fh, err := os.Open(path)
				if err != nil {
					fatal("error opening source file:", err)
				}
				defer fh.Close()
				buf := bufio.NewReader(fh)

				if !mainFinder.MatchReader(buf) {
					// If the first .go file we find doesn't have "package main"
					// at the beginning, we're not interested in this directory
					debug(`does not have "package main"`)
					processed[fileDir] = true
					return nil
				}

				// Found package main, this is a binary
				targetName := filepath.Base(fileDir)
				debugf("found \"package main\", adding target [%s]\n", targetName)

				// See if there is a conflicting target name
				if _, found := targets[targetName]; found {
					debug("Conflicting target name", targetName, "found")
					// Is this the first conflict? Start the queue with the
					// original if so.
					if _, found := toResolve[targetName]; !found {
						toResolve[targetName] = []string{targets[targetName]}
					}
					// Add the conflicting target
					toResolve[targetName] = append(toResolve[targetName], fileDir)

				} else {
					// Add the target hame
					targets[targetName] = fileDir
				}
				processed[fileDir] = true
			}
		}
		return nil
	}
	err := filepath.Walk(cwd, walkFn)
	if err != nil {
		fatal("Error scanning directory:", err)
	}
	// resolve any conflicting target names
	for targetName, paths := range toResolve {
		// remove the conflicting entry from the targets list
		delete(targets, targetName)
		for _, path := range paths {
			newTargetName := strings.Replace(path, cwd, "", 1)
			newTargetName = strings.Replace(newTargetName, string(os.PathSeparator), "", 1)
			newTargetName = strings.Replace(newTargetName, string(os.PathSeparator), "_", -1)
			targets[newTargetName] = path
		}
	}
	return targets
}
<|endoftext|>


<commit_before>package instrumentedwriter

/* Original Instpiration comes from https://github.com/mash/go-accesslog/

 */

import (
	"bufio"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

type LogRecord struct {
	Time                                      time.Time
	Ip, Method, Uri, Protocol, Username, Host string
	Status                                    int
	Size                                      int64
	ElapsedTime                               time.Duration
	RequestHeader                             http.Header
	CustomRecords                             map[string]string
}

type LoggingWriter struct {
	http.ResponseWriter
	logRecord LogRecord
}

var (
	httpRequestDurationSummary = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name:       "service_http_request_processing_duration_seconds",
			Help:       "Http request processing time seconds",
			Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
		},
		[]string{"code", "port"},
	)
	httpRequestSizeHistogram = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "service_http_request_response_size_bytes",
			Help:    "HTTP request response historgram",
			Buckets: prometheus.ExponentialBuckets(10.0, 1.5, 20),
		},
		[]string{"code", "port"},
	)
)

func init() {
	// Register the summary and the histogram with Prometheus's default registry.
	prometheus.MustRegister(httpRequestDurationSummary)
	prometheus.MustRegister(httpRequestSizeHistogram)
}

func (r *LoggingWriter) Write(p []byte) (int, error) {
	if r.logRecord.Status == 0 {
		// The status will be StatusOK if WriteHeader has not been called yet
		r.logRecord.Status = http.StatusOK
	}
	written, err := r.ResponseWriter.Write(p)
	r.logRecord.Size += int64(written)
	return written, err
}

func (r *LoggingWriter) WriteHeader(status int) {
	r.logRecord.Status = status
	r.ResponseWriter.WriteHeader(status)
}

// w.(accesslogger.LoggingWriter).SetCustomLogRecord("X-User-Id", "3")
func (r *LoggingWriter) SetCustomLogRecord(key, value string) {
	if r.logRecord.CustomRecords == nil {
		r.logRecord.CustomRecords = map[string]string{}
	}
	r.logRecord.CustomRecords[key] = value
}

//  w.(accesslogger.LoggingWriter).SetUsername("alice")
func (r *LoggingWriter) SetUsername(username string) {
	r.logRecord.Username = username
}

// http.CloseNotifier interface
func (r *LoggingWriter) CloseNotify() <-chan bool {
	if w, ok := r.ResponseWriter.(http.CloseNotifier); ok {
		return w.CloseNotify()
	}
	return make(chan bool)
}

func (r *LoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
		return hijacker.Hijack()
	}
	return nil, nil, fmt.Errorf("ResponseWriter doesn't support Hijacker interface")
}

// http.Flusher
func (r *LoggingWriter) Flush() {
	flusher, ok := r.ResponseWriter.(http.Flusher)
	if ok {
		flusher.Flush()
	}
}

// http.Pusher
func (r *LoggingWriter) Push(target string, opts *http.PushOptions) error {
	pusher, ok := r.ResponseWriter.(http.Pusher)
	if ok {
		return pusher.Push(target, opts)
	}
	return fmt.Errorf("ResponseWriter doesn't support Pusher interface")
}

type Logger interface {
	Log(record LogRecord)
}

type LoggingHandler struct {
	handler   http.Handler
	logger    Logger
	logBefore bool
}

func NewLoggingHandler(handler http.Handler, logger Logger) http.Handler {
	return &LoggingHandler{
		handler:   handler,
		logger:    logger,
		logBefore: false,
	}
}

func NewAroundLoggingHandler(handler http.Handler, logger Logger) http.Handler {
	return &LoggingHandler{
		handler:   handler,
		logger:    logger,
		logBefore: true,
	}
}

func NewLoggingMiddleware(logger Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		handler := NewLoggingHandler(next, logger)
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			handler.ServeHTTP(w, r)
		})
	}
}

func NewAroundLoggingMiddleware(logger Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		handler := NewAroundLoggingHandler(next, logger)
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			handler.ServeHTTP(w, r)
		})
	}
}

// readIp return the real ip when behide nginx or apache
func (h *LoggingHandler) realIp(r *http.Request) string {
	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		ip = r.RemoteAddr
	}
	if ip != "127.0.0.1" {
		return ip
	}
	// Check if behide nginx or apache
	xRealIP := r.Header.Get("X-Real-Ip")
	xForwardedFor := r.Header.Get("X-Forwarded-For")

	for _, address := range strings.Split(xForwardedFor, ",") {
		address = strings.TrimSpace(address)
		if address != "" {
			return address
		}
	}

	if xRealIP != "" {
		return xRealIP
	}
	return ip
}

func (h *LoggingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	ip := h.realIp(r)
	username := "-"
	if r.URL.User != nil {
		if name := r.URL.User.Username(); name != "" {
			username = name
		}
	}

	startTime := time.Now()
	writer := &LoggingWriter{
		ResponseWriter: rw,
		logRecord: LogRecord{
			Time:          startTime.UTC(),
			Ip:            ip,
			Method:        r.Method,
			Uri:           r.RequestURI,
			Username:      username,
			Protocol:      r.Proto,
			Host:          r.Host,
			Status:        0,
			Size:          0,
			ElapsedTime:   time.Duration(0),
			RequestHeader: r.Header,
		},
	}

	if h.logBefore {
		writer.SetCustomLogRecord("at", "before")
		h.logger.Log(writer.logRecord)
	}
	h.handler.ServeHTTP(writer, r)
	finishTime := time.Now()

	writer.logRecord.Time = finishTime.UTC()
	writer.logRecord.ElapsedTime = finishTime.Sub(startTime)

	if h.logBefore {
		writer.SetCustomLogRecord("at", "after")
	}
	port := ""
	portHost := strings.Split(r.Host, ":")
	if len(portHost) == 2 {
		port = portHost[1]
	}

	httpRequestDurationSummary.WithLabelValues(fmt.Sprintf("%d", writer.logRecord.Status),
		port).Observe(writer.logRecord.ElapsedTime.Seconds())
	httpRequestSizeHistogram.WithLabelValues(fmt.Sprintf("%d", writer.logRecord.Status),
		port).Observe(float64(writer.logRecord.Size))
	h.logger.Log(writer.logRecord)
}
<commit_msg>cleanup unused funcs<commit_after>package instrumentedwriter

/* Original Instpiration comes from https://github.com/mash/go-accesslog/

 */

import (
	"bufio"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

type LogRecord struct {
	Time                                      time.Time
	Ip, Method, Uri, Protocol, Username, Host string
	Status                                    int
	Size                                      int64
	ElapsedTime                               time.Duration
	RequestHeader                             http.Header
	CustomRecords                             map[string]string
}

type LoggingWriter struct {
	http.ResponseWriter
	logRecord LogRecord
}

var (
	httpRequestDurationSummary = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name:       "service_http_request_processing_duration_seconds",
			Help:       "Http request processing time seconds",
			Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
		},
		[]string{"code", "port"},
	)
	httpRequestSizeHistogram = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "service_http_request_response_size_bytes",
			Help:    "HTTP request response historgram",
			Buckets: prometheus.ExponentialBuckets(10.0, 1.5, 20),
		},
		[]string{"code", "port"},
	)
)

func init() {
	// Register the summary and the histogram with Prometheus's default registry.
	prometheus.MustRegister(httpRequestDurationSummary)
	prometheus.MustRegister(httpRequestSizeHistogram)
}

func (r *LoggingWriter) Write(p []byte) (int, error) {
	if r.logRecord.Status == 0 {
		// The status will be StatusOK if WriteHeader has not been called yet
		r.logRecord.Status = http.StatusOK
	}
	written, err := r.ResponseWriter.Write(p)
	r.logRecord.Size += int64(written)
	return written, err
}

func (r *LoggingWriter) WriteHeader(status int) {
	r.logRecord.Status = status
	r.ResponseWriter.WriteHeader(status)
}

// w.(accesslogger.LoggingWriter).SetCustomLogRecord("X-User-Id", "3")
func (r *LoggingWriter) SetCustomLogRecord(key, value string) {
	if r.logRecord.CustomRecords == nil {
		r.logRecord.CustomRecords = map[string]string{}
	}
	r.logRecord.CustomRecords[key] = value
}

//  w.(accesslogger.LoggingWriter).SetUsername("alice")
func (r *LoggingWriter) SetUsername(username string) {
	r.logRecord.Username = username
}

// http.CloseNotifier interface
func (r *LoggingWriter) CloseNotify() <-chan bool {
	if w, ok := r.ResponseWriter.(http.CloseNotifier); ok {
		return w.CloseNotify()
	}
	return make(chan bool)
}

func (r *LoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
		return hijacker.Hijack()
	}
	return nil, nil, fmt.Errorf("ResponseWriter doesn't support Hijacker interface")
}

// http.Flusher
func (r *LoggingWriter) Flush() {
	flusher, ok := r.ResponseWriter.(http.Flusher)
	if ok {
		flusher.Flush()
	}
}

// http.Pusher
func (r *LoggingWriter) Push(target string, opts *http.PushOptions) error {
	pusher, ok := r.ResponseWriter.(http.Pusher)
	if ok {
		return pusher.Push(target, opts)
	}
	return fmt.Errorf("ResponseWriter doesn't support Pusher interface")
}

type Logger interface {
	Log(record LogRecord)
}

type LoggingHandler struct {
	handler   http.Handler
	logger    Logger
	logBefore bool
}

func NewLoggingHandler(handler http.Handler, logger Logger) http.Handler {
	return &LoggingHandler{
		handler:   handler,
		logger:    logger,
		logBefore: false,
	}
}

func NewAroundLoggingHandler(handler http.Handler, logger Logger) http.Handler {
	return &LoggingHandler{
		handler:   handler,
		logger:    logger,
		logBefore: true,
	}
}

// readIp return the real ip when behide nginx or apache
func (h *LoggingHandler) realIp(r *http.Request) string {
	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		ip = r.RemoteAddr
	}
	if ip != "127.0.0.1" {
		return ip
	}
	// Check if behide nginx or apache
	xRealIP := r.Header.Get("X-Real-Ip")
	xForwardedFor := r.Header.Get("X-Forwarded-For")

	for _, address := range strings.Split(xForwardedFor, ",") {
		address = strings.TrimSpace(address)
		if address != "" {
			return address
		}
	}

	if xRealIP != "" {
		return xRealIP
	}
	return ip
}

func (h *LoggingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	ip := h.realIp(r)
	username := "-"
	if r.URL.User != nil {
		if name := r.URL.User.Username(); name != "" {
			username = name
		}
	}

	startTime := time.Now()
	writer := &LoggingWriter{
		ResponseWriter: rw,
		logRecord: LogRecord{
			Time:          startTime.UTC(),
			Ip:            ip,
			Method:        r.Method,
			Uri:           r.RequestURI,
			Username:      username,
			Protocol:      r.Proto,
			Host:          r.Host,
			Status:        0,
			Size:          0,
			ElapsedTime:   time.Duration(0),
			RequestHeader: r.Header,
		},
	}

	if h.logBefore {
		writer.SetCustomLogRecord("at", "before")
		h.logger.Log(writer.logRecord)
	}
	h.handler.ServeHTTP(writer, r)
	finishTime := time.Now()

	writer.logRecord.Time = finishTime.UTC()
	writer.logRecord.ElapsedTime = finishTime.Sub(startTime)

	if h.logBefore {
		writer.SetCustomLogRecord("at", "after")
	}
	port := ""
	portHost := strings.Split(r.Host, ":")
	if len(portHost) == 2 {
		port = portHost[1]
	}

	httpRequestDurationSummary.WithLabelValues(fmt.Sprintf("%d", writer.logRecord.Status),
		port).Observe(writer.logRecord.ElapsedTime.Seconds())
	httpRequestSizeHistogram.WithLabelValues(fmt.Sprintf("%d", writer.logRecord.Status),
		port).Observe(float64(writer.logRecord.Size))
	h.logger.Log(writer.logRecord)
}
<|endoftext|>


<commit_before>package maker

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"strings"

	"golang.org/x/tools/imports"
)

type Method struct {
	Code string
	Docs []string
}

func (m *Method) Lines() []string {
	var lines []string
	lines = append(lines, m.Docs...)
	lines = append(lines, m.Code)
	return lines
}

func GetReceiverTypeName(src []byte, fl interface{}) (string, *ast.FuncDecl) {
	fd, ok := fl.(*ast.FuncDecl)
	if !ok {
		return "", nil
	}
	if fd.Recv.NumFields() != 1 {
		return "", nil
	}
	t := fd.Recv.List[0].Type
	st := string(src[t.Pos()-1 : t.End()-1])
	if len(st) > 0 && st[0] == '*' {
		st = st[1:]
	}
	return st, fd

}

func GetParameters(src []byte, fl *ast.FieldList) ([]string, bool) {
	if fl == nil {
		return nil, false
	}
	merged := false
	parts := []string{}

	for _, l := range fl.List {
		names := make([]string, len(l.Names))
		if len(names) > 1 {
			merged = true
		}
		for i, n := range l.Names {
			names[i] = n.Name
		}

		t := string(src[l.Type.Pos()-1 : l.Type.End()-1])

		var v string
		if len(names) > 0 {
			v = fmt.Sprintf("%s %s", strings.Join(names, ", "), t)
			merged = true
		} else {
			v = t
		}
		parts = append(parts, v)

		//log.Println(reflect.TypeOf(l.Type).String())
	}
	return parts, merged || len(parts) > 1
}

func FormatCode(code string) ([]byte, error) {
	opts := &imports.Options{
		TabIndent: true,
		TabWidth:  2,
		Fragment:  true,
		Comments:  true,
	}
	return imports.Process("", []byte(code), opts)
}

func MakeInterface(comment, pkgName, ifaceName string, methods []string, imports []string) ([]byte, error) {
	output := []string{
		"// " + comment,
		"package " + pkgName,
		"import (",
	}
	output = append(output, imports...)
	output = append(output,
		")",
		fmt.Sprintf("type %s interface {", ifaceName),
	)
	output = append(output, methods...)
	output = append(output, "}")
	code := strings.Join(output, "\n")
	return FormatCode(code)
}

func ParseStruct(src []byte, structName string, copyDocs bool) (methods []Method, imports []string) {
	fset := token.NewFileSet()
	a, err := parser.ParseFile(fset, "", src, parser.ParseComments)
	if err != nil {
		log.Fatal(err.Error())
	}

	for _, i := range a.Imports {
		if i.Name != nil {
			imports = append(imports, fmt.Sprintf("%s %s", i.Name.String(), i.Path.Value))
		} else {
			imports = append(imports, fmt.Sprintf("%s", i.Path.Value))
		}
	}

	for _, d := range a.Decls {
		if a, fd := GetReceiverTypeName(src, d); a == structName {
			methodName := fd.Name.String()
			if methodName[0] > 'Z' {
				continue
			}
			params, _ := GetParameters(src, fd.Type.Params)
			ret, merged := GetParameters(src, fd.Type.Results)

			var retValues string
			if merged {
				retValues = fmt.Sprintf("(%s)", strings.Join(ret, ", "))
			} else {
				retValues = strings.Join(ret, ", ")
			}
			method := fmt.Sprintf("%s(%s) %s", methodName, strings.Join(params, ", "), retValues)
			var docs []string
			if fd.Doc != nil && copyDocs {
				for _, d := range fd.Doc.List {
					docs = append(docs, string(src[d.Pos()-1:d.End()-1]))
				}
			}
			methods = append(methods, Method{
				Code: method,
				Docs: docs,
			})
		}
	}
	return
}
<commit_msg>Fix golint thinking auto-generate comment is a comment on the pkg and not file<commit_after>package maker

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"strings"

	"golang.org/x/tools/imports"
)

// Method describes the code and documentation
// tied into a method
type Method struct {
	Code string
	Docs []string
}

func (m *Method) Lines() []string {
	var lines []string
	lines = append(lines, m.Docs...)
	lines = append(lines, m.Code)
	return lines
}

func GetReceiverTypeName(src []byte, fl interface{}) (string, *ast.FuncDecl) {
	fd, ok := fl.(*ast.FuncDecl)
	if !ok {
		return "", nil
	}
	if fd.Recv.NumFields() != 1 {
		return "", nil
	}
	t := fd.Recv.List[0].Type
	st := string(src[t.Pos()-1 : t.End()-1])
	if len(st) > 0 && st[0] == '*' {
		st = st[1:]
	}
	return st, fd

}

func GetParameters(src []byte, fl *ast.FieldList) ([]string, bool) {
	if fl == nil {
		return nil, false
	}
	merged := false
	parts := []string{}

	for _, l := range fl.List {
		names := make([]string, len(l.Names))
		if len(names) > 1 {
			merged = true
		}
		for i, n := range l.Names {
			names[i] = n.Name
		}

		t := string(src[l.Type.Pos()-1 : l.Type.End()-1])

		var v string
		if len(names) > 0 {
			v = fmt.Sprintf("%s %s", strings.Join(names, ", "), t)
			merged = true
		} else {
			v = t
		}
		parts = append(parts, v)

		//log.Println(reflect.TypeOf(l.Type).String())
	}
	return parts, merged || len(parts) > 1
}

func FormatCode(code string) ([]byte, error) {
	opts := &imports.Options{
		TabIndent: true,
		TabWidth:  2,
		Fragment:  true,
		Comments:  true,
	}
	return imports.Process("", []byte(code), opts)
}

func MakeInterface(comment, pkgName, ifaceName string, methods []string, imports []string) ([]byte, error) {
	output := []string{
		"// " + comment,
		"",
		"package " + pkgName,
		"import (",
	}
	output = append(output, imports...)
	output = append(output,
		")",
		fmt.Sprintf("type %s interface {", ifaceName),
	)
	output = append(output, methods...)
	output = append(output, "}")
	code := strings.Join(output, "\n")
	return FormatCode(code)
}

func ParseStruct(src []byte, structName string, copyDocs bool) (methods []Method, imports []string) {
	fset := token.NewFileSet()
	a, err := parser.ParseFile(fset, "", src, parser.ParseComments)
	if err != nil {
		log.Fatal(err.Error())
	}

	for _, i := range a.Imports {
		if i.Name != nil {
			imports = append(imports, fmt.Sprintf("%s %s", i.Name.String(), i.Path.Value))
		} else {
			imports = append(imports, fmt.Sprintf("%s", i.Path.Value))
		}
	}

	for _, d := range a.Decls {
		if a, fd := GetReceiverTypeName(src, d); a == structName {
			methodName := fd.Name.String()
			if methodName[0] > 'Z' {
				continue
			}
			params, _ := GetParameters(src, fd.Type.Params)
			ret, merged := GetParameters(src, fd.Type.Results)

			var retValues string
			if merged {
				retValues = fmt.Sprintf("(%s)", strings.Join(ret, ", "))
			} else {
				retValues = strings.Join(ret, ", ")
			}
			method := fmt.Sprintf("%s(%s) %s", methodName, strings.Join(params, ", "), retValues)
			var docs []string
			if fd.Doc != nil && copyDocs {
				for _, d := range fd.Doc.List {
					docs = append(docs, string(src[d.Pos()-1:d.End()-1]))
				}
			}
			methods = append(methods, Method{
				Code: method,
				Docs: docs,
			})
		}
	}
	return
}
<|endoftext|>


<commit_before>package batchproducer

import (
	"errors"
	"log"
	"time"

	"github.com/timehop/go-kinesis"
)

// BatchProducer collects records individually and then sends them to Kinesis in
// batches in the background using PutRecords, with retries.
// A BatchProducer will do nothing until Start is called.
type BatchProducer interface {
	// Start starts the main goroutine. No need to call it using `go`.
	Start() error
	Stop() error

	// Add might block if the BatchProducer has a buffer and the buffer is full.
	// In order to prevent filling the buffer and eventually blocking indefinitely,
	// Add will fail and return an error if the BatchProducer is not started.
	Add(data []byte, partitionKey string) error

	// Setters
	SetMaxAttemptsPerRecord(int) error
	SetStatReceiver(BatchStatReceiver) error

	// This interval will be used to make a *best effort* attempt to send stats *approximately*
	// when this interval elapses. There’s no guarantee, however, since the main goroutine is
	// used to send the stats and therefore there may be some skew.
	SetStatInterval(time.Duration) error
}

// BatchStatReceiver defines an object that can accept stats.
type BatchStatReceiver interface {
	// Receive will be called by the main BatchProducer goroutine so it will block all batches from being
	// sent, so make sure it is either very fast or never blocks at all!
	Receive(BatchStat)
}

// BatchStat is a kind of a snapshot of activity and happenings. Some of its fields represent
// "moment-in-time" values e.g. BufferSize is the size of the buffer at the moment the Stat is
// sent. Other fields are cumulative since the last Stat, i.e. ErrorsSinceLastStat.
type BatchStat struct {
	// Moment-in-time stats
	BufferSize int

	// Cumulative stats
	KinesisErrorsSinceLastStat           int
	RecordsSentSuccessfullySinceLastStat int
	RecordsDroppedSinceLastStat          int
}

// BatchingKinesisClient is a subset of KinesisClient to ease mocking.
type BatchingKinesisClient interface {
	PutRecords(args *kinesis.RequestArgs) (resp *kinesis.PutRecordsResp, err error)
}

// NewBatchProducer creates and returns a BatchProducer.
// The BatchProducer that is returned will flush a batch to Kinesis whenever either
// the flushInterval occurs (if flushInterval > 0) or the batchSize is reached,
// whichever happens first. If the number of records in the buffer is equal to
// or greater than bufferSize then the Add method will block.
// TODO: this is too many args. Maybe instead have some defaults and add some setter methods for the less critical
// params
func NewBatchProducer(
	client BatchingKinesisClient,
	streamName string,
	bufferSize int,
	flushInterval time.Duration,
	batchSize int,
	logger *log.Logger,
) (BatchProducer, error) {
	if batchSize < 1 || batchSize > 500 {
		return nil, errors.New("batchSize must be between 1 and 500 inclusive")
	}

	if bufferSize < batchSize && flushInterval <= 0 {
		return nil, errors.New("If bufferSize < batchSize && flushInterval <= 0 then the buffer will eventually fill up and Add will block forever.")
	}

	if flushInterval > 0 && flushInterval < 50*time.Millisecond {
		return nil, errors.New("Are you crazy?")
	}

	batchProducer := batchProducer{
		client:               client,
		streamName:           streamName,
		flushInterval:        flushInterval,
		batchSize:            batchSize,
		maxAttemptsPerRecord: 10,
		logger:               logger,
		statInterval:         time.Second,
		currentStat:          new(BatchStat),
		records:              make(chan batchRecord, bufferSize),
		stop:                 make(chan interface{}),
	}

	return &batchProducer, nil
}

type batchProducer struct {
	client               BatchingKinesisClient
	streamName           string
	flushInterval        time.Duration
	batchSize            int
	maxAttemptsPerRecord int
	logger               *log.Logger
	running              bool
	consecutiveErrors    int
	currentDelay         time.Duration
	statInterval         time.Duration
	statReceiver         BatchStatReceiver
	currentStat          *BatchStat
	records              chan batchRecord
	stop                 chan interface{}
}

type batchRecord struct {
	data         []byte
	partitionKey string
	sendAttempts int
}

// from/for interface BatchProducer
func (b *batchProducer) Add(data []byte, partitionKey string) error {
	if !b.running {
		return errors.New("Cannot call Add when BatchProducer is not running (to prevent the buffer filling up and Add blocking indefinitely).")
	}
	b.records <- batchRecord{data: data, partitionKey: partitionKey}
	return nil
}

// from/for interface BatchProducer
func (b *batchProducer) Start() error {
	if b.running {
		return nil
	}

	go b.run()

	for !b.running {
		// Give the goroutine a chance to start before returning.
		time.Sleep(1 * time.Microsecond)
	}

	return nil
}

func (b *batchProducer) run() {
	flushTick := time.Tick(b.flushInterval)

	var statTick <-chan time.Time
	if b.statReceiver != nil {
		statTick = time.Tick(b.statInterval)
	}

	b.running = true
	defer func() { b.running = false }()

	for {
		select {
		case <-flushTick:
			b.sendBatch()
		case <-statTick:
			b.sendStats()
		case <-b.stop:
			break
		default:
			if len(b.records) >= b.batchSize {
				b.sendBatch()
			} else {
				time.Sleep(1 * time.Millisecond)
			}
		}
	}
}

// from/for interface BatchProducer
func (b *batchProducer) Stop() error {
	if b.running {
		b.stop <- true
	}
	return nil
}

func (b *batchProducer) SetMaxAttemptsPerRecord(v int) error {
	if b.running {
		return errors.New("Cannot set max attempts per record while BatchProducer is running.")
	}
	b.maxAttemptsPerRecord = v
	return nil
}

func (b *batchProducer) SetStatReceiver(sr BatchStatReceiver) error {
	if b.running {
		return errors.New("Cannot set BatchStatReceiver while BatchProducer is running.")
	}
	b.statReceiver = sr
	return nil
}

func (b *batchProducer) SetStatInterval(si time.Duration) error {
	if b.running {
		return errors.New("Cannot set stat interval while BatchProducer is running.")
	}
	b.statInterval = si
	return nil
}

func (b *batchProducer) sendBatch() {
	if len(b.records) == 0 {
		return
	}

	if b.consecutiveErrors == 1 {
		b.currentDelay = 50 * time.Millisecond
	} else if b.consecutiveErrors > 1 {
		b.currentDelay *= 2
	}

	if b.currentDelay > 0 {
		b.logger.Printf("Delaying the batch by %v because of %v consecutive errors", b.currentDelay, b.consecutiveErrors)
		time.Sleep(b.currentDelay)
	}

	records := b.takeRecordsFromBuffer()
	res, err := b.client.PutRecords(b.recordsToArgs(records))

	if err != nil {
		b.consecutiveErrors++
		b.currentStat.KinesisErrorsSinceLastStat++
		b.logger.Printf("Error occurred when sending PutRecords request to Kinesis stream %v: %v", b.streamName, err)
		b.returnRecordsToBuffer(records)
	} else {
		b.consecutiveErrors = 0
		b.currentDelay = 0
		succeeded := len(records) - res.FailedRecordCount

		b.currentStat.RecordsSentSuccessfullySinceLastStat += succeeded

		if res.FailedRecordCount > 0 {
			b.logger.Printf("Partial success when sending a PutRecords request to Kinesis stream %v: %v succeeded, %v failed. Re-enqueueing failed records.", b.streamName, succeeded, res.FailedRecordCount)
			b.returnSomeFailedRecordsToBuffer(res, records)
		}
	}
}

func (b *batchProducer) takeRecordsFromBuffer() []batchRecord {
	var size int
	bufferLen := len(b.records)
	if bufferLen >= b.batchSize {
		size = b.batchSize
	} else {
		size = bufferLen
	}

	result := make([]batchRecord, size)
	for i := 0; i < size; i++ {
		result[i] = <-b.records
	}
	return result
}

func (b *batchProducer) recordsToArgs(records []batchRecord) *kinesis.RequestArgs {
	args := kinesis.NewArgs()
	args.Add("StreamName", b.streamName)
	for _, record := range records {
		args.AddRecord(record.data, record.partitionKey)
	}
	return args
}

// TODO: perhaps we should use a deque internally as the buffer so we can return records to
// the front of the queue.
func (b *batchProducer) returnRecordsToBuffer(records []batchRecord) {
	for _, record := range records {
		// Not using b.Add because we want to preserve the value of record.sendAttempts.
		b.records <- record
	}
}

func (b *batchProducer) returnSomeFailedRecordsToBuffer(res *kinesis.PutRecordsResp, records []batchRecord) {
	for i, result := range res.Records {
		record := records[i]
		if result.ErrorCode != "" {
			record.sendAttempts++

			if record.sendAttempts < b.maxAttemptsPerRecord {
				b.logger.Printf("Re-enqueueing failed record to buffer for retry. Error code was: '%v' and message was '%v'", result.ErrorCode, result.ErrorMessage)
				// Not using b.Add because we want to preserve the value of record.sendAttempts.
				b.records <- record
			} else {
				b.currentStat.RecordsDroppedSinceLastStat++
				msg := "NOT re-enqueueing failed record for retry, as it has hit %v attempts, " +
					"which is the maximum. Error code was: '%v' and message was '%v'"
				b.logger.Printf(msg, record.sendAttempts, result.ErrorCode, result.ErrorMessage)
			}
		}
	}
}

func (b *batchProducer) sendStats() {
	b.currentStat.BufferSize = len(b.records)

	// TEMP TMEP TEMP
	// fmt.Printf("Sending stat: %+v\n", b.currentStat)

	// I considered running this as a goroutine, but I’m concerned about leaks. So instead, for now,
	// the provider of the BatchStatReceiver must ensure that it is either very fast or non-blocking.
	b.statReceiver.Receive(*b.currentStat)

	b.currentStat = new(BatchStat)
}
<commit_msg>Fix a race condition in BatchProducer<commit_after>package batchproducer

import (
	"errors"
	"log"
	"sync"
	"time"

	"github.com/timehop/go-kinesis"
)

// BatchProducer collects records individually and then sends them to Kinesis in
// batches in the background using PutRecords, with retries.
// A BatchProducer will do nothing until Start is called.
type BatchProducer interface {
	// Start starts the main goroutine. No need to call it using `go`.
	Start() error
	Stop() error

	// Add might block if the BatchProducer has a buffer and the buffer is full.
	// In order to prevent filling the buffer and eventually blocking indefinitely,
	// Add will fail and return an error if the BatchProducer is not started.
	Add(data []byte, partitionKey string) error

	// Setters
	SetMaxAttemptsPerRecord(int) error
	SetStatReceiver(BatchStatReceiver) error

	// This interval will be used to make a *best effort* attempt to send stats *approximately*
	// when this interval elapses. There’s no guarantee, however, since the main goroutine is
	// used to send the stats and therefore there may be some skew.
	SetStatInterval(time.Duration) error
}

// BatchStatReceiver defines an object that can accept stats.
type BatchStatReceiver interface {
	// Receive will be called by the main BatchProducer goroutine so it will block all batches from being
	// sent, so make sure it is either very fast or never blocks at all!
	Receive(BatchStat)
}

// BatchStat is a kind of a snapshot of activity and happenings. Some of its fields represent
// "moment-in-time" values e.g. BufferSize is the size of the buffer at the moment the Stat is
// sent. Other fields are cumulative since the last Stat, i.e. ErrorsSinceLastStat.
type BatchStat struct {
	// Moment-in-time stats
	BufferSize int

	// Cumulative stats
	KinesisErrorsSinceLastStat           int
	RecordsSentSuccessfullySinceLastStat int
	RecordsDroppedSinceLastStat          int
}

// BatchingKinesisClient is a subset of KinesisClient to ease mocking.
type BatchingKinesisClient interface {
	PutRecords(args *kinesis.RequestArgs) (resp *kinesis.PutRecordsResp, err error)
}

// NewBatchProducer creates and returns a BatchProducer.
// The BatchProducer that is returned will flush a batch to Kinesis whenever either
// the flushInterval occurs (if flushInterval > 0) or the batchSize is reached,
// whichever happens first. If the number of records in the buffer is equal to
// or greater than bufferSize then the Add method will block.
// TODO: this is too many args. Maybe instead have some defaults and add some setter methods for the less critical
// params
func NewBatchProducer(
	client BatchingKinesisClient,
	streamName string,
	bufferSize int,
	flushInterval time.Duration,
	batchSize int,
	logger *log.Logger,
) (BatchProducer, error) {
	if batchSize < 1 || batchSize > 500 {
		return nil, errors.New("batchSize must be between 1 and 500 inclusive")
	}

	if bufferSize < batchSize && flushInterval <= 0 {
		return nil, errors.New("If bufferSize < batchSize && flushInterval <= 0 then the buffer will eventually fill up and Add will block forever.")
	}

	if flushInterval > 0 && flushInterval < 50*time.Millisecond {
		return nil, errors.New("Are you crazy?")
	}

	batchProducer := batchProducer{
		client:               client,
		streamName:           streamName,
		flushInterval:        flushInterval,
		batchSize:            batchSize,
		maxAttemptsPerRecord: 10,
		logger:               logger,
		statInterval:         time.Second,
		currentStat:          new(BatchStat),
		records:              make(chan batchRecord, bufferSize),
		stop:                 make(chan interface{}),
	}

	return &batchProducer, nil
}

type batchProducer struct {
	client               BatchingKinesisClient
	streamName           string
	flushInterval        time.Duration
	batchSize            int
	maxAttemptsPerRecord int
	logger               *log.Logger
	running              bool
	runningMu            sync.Mutex
	consecutiveErrors    int
	currentDelay         time.Duration
	statInterval         time.Duration
	statReceiver         BatchStatReceiver
	currentStat          *BatchStat
	records              chan batchRecord
	stop                 chan interface{}
}

type batchRecord struct {
	data         []byte
	partitionKey string
	sendAttempts int
}

// from/for interface BatchProducer
func (b *batchProducer) Add(data []byte, partitionKey string) error {
	if !b.isRunning() {
		return errors.New("Cannot call Add when BatchProducer is not running (to prevent the buffer filling up and Add blocking indefinitely).")
	}
	b.records <- batchRecord{data: data, partitionKey: partitionKey}
	return nil
}

// from/for interface BatchProducer
func (b *batchProducer) Start() error {
	if b.isRunning() {
		return nil
	}

	go b.run()

	for !b.isRunning() {
		time.Sleep(1 * time.Millisecond)
	}

	return nil
}

func (b *batchProducer) run() {
	flushTick := time.Tick(b.flushInterval)

	var statTick <-chan time.Time
	if b.statReceiver != nil {
		statTick = time.Tick(b.statInterval)
	}

	b.setRunning(true)
	defer b.setRunning(false)

	for {
		select {
		case <-flushTick:
			b.sendBatch()
		case <-statTick:
			b.sendStats()
		case <-b.stop:
			break
		default:
			if len(b.records) >= b.batchSize {
				b.sendBatch()
			} else {
				time.Sleep(1 * time.Millisecond)
			}
		}
	}
}

// from/for interface BatchProducer
func (b *batchProducer) Stop() error {
	if b.isRunning() {
		b.stop <- true
	}
	return nil
}

func (b *batchProducer) SetMaxAttemptsPerRecord(v int) error {
	if b.isRunning() {
		return errors.New("Cannot set max attempts per record while BatchProducer is running.")
	}
	b.maxAttemptsPerRecord = v
	return nil
}

func (b *batchProducer) SetStatReceiver(sr BatchStatReceiver) error {
	if b.isRunning() {
		return errors.New("Cannot set BatchStatReceiver while BatchProducer is running.")
	}
	b.statReceiver = sr
	return nil
}

func (b *batchProducer) SetStatInterval(si time.Duration) error {
	if b.isRunning() {
		return errors.New("Cannot set stat interval while BatchProducer is running.")
	}
	b.statInterval = si
	return nil
}

func (b *batchProducer) setRunning(running bool) {
	b.runningMu.Lock()
	defer b.runningMu.Unlock()
	b.running = running
}

func (b *batchProducer) isRunning() bool {
	b.runningMu.Lock()
	defer b.runningMu.Unlock()
	return b.running
}

func (b *batchProducer) sendBatch() {
	if len(b.records) == 0 {
		return
	}

	if b.consecutiveErrors == 1 {
		b.currentDelay = 50 * time.Millisecond
	} else if b.consecutiveErrors > 1 {
		b.currentDelay *= 2
	}

	if b.currentDelay > 0 {
		b.logger.Printf("Delaying the batch by %v because of %v consecutive errors", b.currentDelay, b.consecutiveErrors)
		time.Sleep(b.currentDelay)
	}

	records := b.takeRecordsFromBuffer()
	res, err := b.client.PutRecords(b.recordsToArgs(records))

	if err != nil {
		b.consecutiveErrors++
		b.currentStat.KinesisErrorsSinceLastStat++
		b.logger.Printf("Error occurred when sending PutRecords request to Kinesis stream %v: %v", b.streamName, err)
		b.returnRecordsToBuffer(records)
	} else {
		b.consecutiveErrors = 0
		b.currentDelay = 0
		succeeded := len(records) - res.FailedRecordCount

		b.currentStat.RecordsSentSuccessfullySinceLastStat += succeeded

		if res.FailedRecordCount > 0 {
			b.logger.Printf("Partial success when sending a PutRecords request to Kinesis stream %v: %v succeeded, %v failed. Re-enqueueing failed records.", b.streamName, succeeded, res.FailedRecordCount)
			b.returnSomeFailedRecordsToBuffer(res, records)
		}
	}
}

func (b *batchProducer) takeRecordsFromBuffer() []batchRecord {
	var size int
	bufferLen := len(b.records)
	if bufferLen >= b.batchSize {
		size = b.batchSize
	} else {
		size = bufferLen
	}

	result := make([]batchRecord, size)
	for i := 0; i < size; i++ {
		result[i] = <-b.records
	}
	return result
}

func (b *batchProducer) recordsToArgs(records []batchRecord) *kinesis.RequestArgs {
	args := kinesis.NewArgs()
	args.Add("StreamName", b.streamName)
	for _, record := range records {
		args.AddRecord(record.data, record.partitionKey)
	}
	return args
}

// TODO: perhaps we should use a deque internally as the buffer so we can return records to
// the front of the queue.
func (b *batchProducer) returnRecordsToBuffer(records []batchRecord) {
	for _, record := range records {
		// Not using b.Add because we want to preserve the value of record.sendAttempts.
		b.records <- record
	}
}

func (b *batchProducer) returnSomeFailedRecordsToBuffer(res *kinesis.PutRecordsResp, records []batchRecord) {
	for i, result := range res.Records {
		record := records[i]
		if result.ErrorCode != "" {
			record.sendAttempts++

			if record.sendAttempts < b.maxAttemptsPerRecord {
				b.logger.Printf("Re-enqueueing failed record to buffer for retry. Error code was: '%v' and message was '%v'", result.ErrorCode, result.ErrorMessage)
				// Not using b.Add because we want to preserve the value of record.sendAttempts.
				b.records <- record
			} else {
				b.currentStat.RecordsDroppedSinceLastStat++
				msg := "NOT re-enqueueing failed record for retry, as it has hit %v attempts, " +
					"which is the maximum. Error code was: '%v' and message was '%v'"
				b.logger.Printf(msg, record.sendAttempts, result.ErrorCode, result.ErrorMessage)
			}
		}
	}
}

func (b *batchProducer) sendStats() {
	b.currentStat.BufferSize = len(b.records)

	// TEMP TMEP TEMP
	// fmt.Printf("Sending stat: %+v\n", b.currentStat)

	// I considered running this as a goroutine, but I’m concerned about leaks. So instead, for now,
	// the provider of the BatchStatReceiver must ensure that it is either very fast or non-blocking.
	b.statReceiver.Receive(*b.currentStat)

	b.currentStat = new(BatchStat)
}
<|endoftext|>


<commit_before>package main

import (
	"../pili" // or "github.com/pili-engineering/pili-sdk-go/pili"
	"fmt"
)

const (

	// Replace with your keys here
	ACCESS_KEY = "Qiniu_AccessKey"
	SECRET_KEY = "Qiniu_SecretKey"

	// The Hub must be exists before use
	HUB_NAME = "Pili_Hub_Name"
)

func main() {

	// Change API host as necessary
	//
	// pili.qiniuapi.com as deafult
	// pili-lte.qiniuapi.com is the latest RC version
	//
	pili.API_HOST = "pili-lte.qiniuapi.com"

	// Instantiate a Pili Hub object
	credentials := pili.NewCredentials(ACCESS_KEY, SECRET_KEY)
	hub := pili.NewHub(credentials, HUB_NAME)

	// Create a new stream
	options := pili.OptionalArguments{ // optional
		Title:           "stream_name",       // optional, auto-generated as default
		PublishKey:      "some_secret_words", // optional, auto-generated as default
		PublishSecurity: "dynamic",           // optional, can be "dynamic" or "static", "dynamic" as default
	}
	stream, err := hub.CreateStream(options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("CreateStream:\n", stream)

	// Get a stream
	stream, err = hub.GetStream(stream.Id)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("GetStream:\n", stream)

	// List streams
	options = pili.OptionalArguments{ // optional
		Marker: "",        // optional, returned by server response
		Limit:  50,        // optional
		Title:  "prefix_", // optional, title prefix
	}
	listResult, err := hub.ListStreams(options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("ListStreams:\n", listResult)
	for _, stream := range listResult.Items {
		fmt.Println("Stream:\n", stream)
	}

	// To JSON String
	streamJson, err := stream.ToJSONString()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream ToJSONString:\n", streamJson)

	// Update a stream
	stream.PublishKey = "new_secret_words" // optional
	stream.PublishSecurity = "static"      // optional
	stream, err = stream.Update()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Updated:\n", stream)

	// Disable a stream
	stream, err = stream.Disable()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Disabled:\n", stream)

	// Enable a stream
	stream, err = stream.Enable()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Enabled:\n", stream)

	// Generate RTMP publish URL
	url := stream.RtmpPublishUrl()
	fmt.Println("Stream RtmpPublishUrl:\n", url)

	// Generate RTMP live play URLs
	urls, err := stream.RtmpLiveUrls()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("RtmpLiveUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Generate HLS live play URLs
	urls, err = stream.HlsLiveUrls()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("HlsLiveUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Generate Http-Flv live play URLs
	urls, err = stream.HttpFlvLiveUrls()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("HttpFlvLiveUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Get stream status
	streamStatus, err := stream.Status()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Status:\n", streamStatus)

	// Get stream segments
	options = pili.OptionalArguments{ // optional
		Start: 1440379800, // optional, in second, unix timestamp
		End:   1440479880, // optional, in second, unix timestamp
		Limit: 20,         // optional, uint
	}
	segments, err := stream.Segments(options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Segments:\n", segments)

	// Generate HLS playback URLs
	start := 1440379847
	end := 1440379857
	urls, err = stream.HlsPlaybackUrls(int64(start), int64(end))
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("HlsPlaybackUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Save Stream as a file
	name := "fileName.mp4" // required, string
	format := "mp4"        // required, string
	start = 1440379847     // required, int64, in second, unix timestamp
	end = 1440379857       // required, int64, in second, unix timestamp
	options = pili.OptionalArguments{
		NotifyUrl: "http://remote_callback_url",
	} // optional
	saveAsRes, err := stream.SaveAs(name, format, int64(start), int64(end), options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream save as:\n", saveAsRes)

	// Snapshot Stream
	name = "fileName.jpg" // required, string
	format = "jpg"        // required, string
	options = pili.OptionalArguments{
		Time:      1440379847, // optional, int64, in second, unit timestamp
		NotifyUrl: "http://remote_callback_url",
	} // optional
	snapshotRes, err := stream.Snapshot(name, format, options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Snapshot:\n", snapshotRes)

	// Delete a stream
	deleteResult, err := stream.Delete()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Deleted:\n", deleteResult)
}
<commit_msg>Update example.go<commit_after>package main

import (
	"../pili" // or "github.com/pili-engineering/pili-sdk-go/pili"
	"fmt"
)

const (

	// Replace with your keys here
	ACCESS_KEY = "Qiniu_AccessKey"
	SECRET_KEY = "Qiniu_SecretKey"

	// The Hub must be exists before use
	HUB_NAME = "Pili_Hub_Name"
)

func main() {

	// Change API host as necessary
	//
	// pili.qiniuapi.com as default
	// pili-lte.qiniuapi.com is the latest RC version
	//
	// pili.API_HOST = "pili.qiniuapi.com" // default

	// Instantiate a Pili Hub object
	credentials := pili.NewCredentials(ACCESS_KEY, SECRET_KEY)
	hub := pili.NewHub(credentials, HUB_NAME)

	// Create a new stream
	options := pili.OptionalArguments{ // optional
		Title:           "stream_name",       // optional, auto-generated as default
		PublishKey:      "some_secret_words", // optional, auto-generated as default
		PublishSecurity: "dynamic",           // optional, can be "dynamic" or "static", "dynamic" as default
	}
	stream, err := hub.CreateStream(options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("CreateStream:\n", stream)

	// Get a stream
	stream, err = hub.GetStream(stream.Id)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("GetStream:\n", stream)

	// List streams
	options = pili.OptionalArguments{ // optional
		Marker: "",        // optional, returned by server response
		Limit:  50,        // optional
		Title:  "prefix_", // optional, title prefix
	}
	listResult, err := hub.ListStreams(options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("ListStreams:\n", listResult)
	for _, stream := range listResult.Items {
		fmt.Println("Stream:\n", stream)
	}

	// To JSON String
	streamJson, err := stream.ToJSONString()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream ToJSONString:\n", streamJson)

	// Update a stream
	stream.PublishKey = "new_secret_words" // optional
	stream.PublishSecurity = "static"      // optional
	stream, err = stream.Update()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Updated:\n", stream)

	// Disable a stream
	stream, err = stream.Disable()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Disabled:\n", stream)

	// Enable a stream
	stream, err = stream.Enable()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Enabled:\n", stream)

	// Generate RTMP publish URL
	url := stream.RtmpPublishUrl()
	fmt.Println("Stream RtmpPublishUrl:\n", url)

	// Generate RTMP live play URLs
	urls, err := stream.RtmpLiveUrls()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("RtmpLiveUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Generate HLS live play URLs
	urls, err = stream.HlsLiveUrls()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("HlsLiveUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Generate Http-Flv live play URLs
	urls, err = stream.HttpFlvLiveUrls()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("HttpFlvLiveUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Get stream status
	streamStatus, err := stream.Status()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Status:\n", streamStatus)

	// Get stream segments
	options = pili.OptionalArguments{ // optional
		Start: 1440379800, // optional, in second, unix timestamp
		End:   1440479880, // optional, in second, unix timestamp
		Limit: 20,         // optional, uint
	}
	segments, err := stream.Segments(options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Segments:\n", segments)

	// Generate HLS playback URLs
	start := 1440379847
	end := 1440379857
	urls, err = stream.HlsPlaybackUrls(int64(start), int64(end))
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("HlsPlaybackUrls:", urls)
	for k, v := range urls {
		fmt.Printf("%s:%s\n", k, v)
	}

	// Save Stream as a file
	name := "fileName.mp4" // required, string
	format := "mp4"        // required, string
	start = 1440379847     // required, int64, in second, unix timestamp
	end = 1440379857       // required, int64, in second, unix timestamp
	options = pili.OptionalArguments{
		NotifyUrl: "http://remote_callback_url",
	} // optional
	saveAsRes, err := stream.SaveAs(name, format, int64(start), int64(end), options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream save as:\n", saveAsRes)

	// Snapshot Stream
	name = "fileName.jpg" // required, string
	format = "jpg"        // required, string
	options = pili.OptionalArguments{
		Time:      1440379847, // optional, int64, in second, unit timestamp
		NotifyUrl: "http://remote_callback_url",
	} // optional
	snapshotRes, err := stream.Snapshot(name, format, options)
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Snapshot:\n", snapshotRes)

	// Delete a stream
	deleteResult, err := stream.Delete()
	if err != nil {
		fmt.Println("Error:", err)
	}
	fmt.Println("Stream Deleted:\n", deleteResult)
}
<|endoftext|>


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

package cors

import (
	"net/textproto"
	"strconv"
	"strings"

	"github.com/google/go-safeweb/safehttp"
)

const customHeader string = "X-Cors"

var disallowedContentTypes = map[string]bool{
	"application/x-www-form-urlencoded": true,
	"multipart/form-data":               true,
	"text/plain":                        true,
}

// Interceptor handles CORS requests based on its settings.
//
// For more info about CORS, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
//
// Constraints
//
// - The request methods GET, HEAD and POST are banned and will result in a 405
//   Method Not Allowed response.
// - The content types "application/x-www-form-urlencoded", "multipart/form-data"
//   and "text/plain" are also banned and will result in a  415 Unsupported Media
//   Type response.
// - Each CORS request must contain the header "X-Cors: 1" or "Sec-Fetch-Mode: cors".
// All of this is to prevent XSRF.
type Interceptor struct {
	// AllowedOrigins determines which origins should be allowed in the
	// Access-Control-Allow-Origin header.
	AllowedOrigins map[string]bool
	allowedHeaders map[string]bool
	// ExposedHeaders determines which headers should be set in the
	// Access-Control-Expose-Headers header. If ExposedHeaders is nil, then the
	// header is not set.
	ExposedHeaders []string
	// AllowCredentials determines if Access-Control-Allow-Credentials should be
	// set to true when requests containing Cookie headers arrive.
	AllowCredentials bool
	// MaxAge sets the Access-Control-Max-Age header.
	// MaxAge=0 results in MaxAge: 5.
	MaxAge int
}

// Default creates a CORS Interceptor with default settings.
// Those defaults are:
//   - No Exposed Headers
//   - No Allowed Headers
//   - AllowCredentials: false
//   - MaxAge: 5 seconds
func Default(allowedOrigins ...string) *Interceptor {
	ao := map[string]bool{}
	for _, o := range allowedOrigins {
		ao[o] = true
	}
	return &Interceptor{
		AllowedOrigins: ao,
		allowedHeaders: map[string]bool{},
	}
}

// SetAllowedHeaders sets which headers should be allowed in the Access-Control-Allow-Headers
// header. The headers are first canonicalized using textproto.CanonicalMIMEHeaderKey.
func (it *Interceptor) SetAllowedHeaders(headers ...string) {
	it.allowedHeaders = map[string]bool{}
	for _, h := range headers {
		it.allowedHeaders[textproto.CanonicalMIMEHeaderKey(h)] = true
	}
}

// Before handles the incoming request and sets headers or responds with an error
// accordingly.
func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
	origin := r.Header.Get("Origin")
	if origin != "" && !it.AllowedOrigins[origin] {
		return w.ClientError(safehttp.StatusForbidden)
	}
	h := w.Header()
	allowOrigin, err := h.Claim("Access-Control-Allow-Origin")
	if err != nil {
		return w.ServerError(safehttp.StatusInternalServerError)
	}
	vary, err := h.Claim("Vary")
	if err != nil {
		return w.ServerError(safehttp.StatusInternalServerError)
	}

	allowCredentials, err := h.Claim("Access-Control-Allow-Credentials")
	if err != nil {
		return w.ServerError(safehttp.StatusInternalServerError)
	}
	if r.Header.Get("Cookie") != "" && !it.AllowCredentials {
		return w.ClientError(safehttp.StatusForbidden)
	}

	var status safehttp.StatusCode
	if r.Method() == safehttp.MethodOptions {
		status = it.preflight(w, r)
	} else {
		status = it.request(w, r)
	}

	if status != 0 && status != safehttp.StatusNoContent {
		if 400 <= status && status < 500 {
			return w.ClientError(status)
		} else {
			return w.ServerError(status)
		}
	}

	if origin != "" {
		allowOrigin([]string{origin})
		vary([]string{"Origin"})
	}
	if r.Header.Get("Cookie") != "" && it.AllowCredentials {
		// TODO: handle other credentials than cookies:
		// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
		allowCredentials([]string{"true"})
	}

	if status == safehttp.StatusNoContent {
		return w.NoContent()
	}
	return safehttp.Result{}
}

// preflight handles requests that have the method OPTIONS.
func (it *Interceptor) preflight(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.StatusCode {
	rh := r.Header
	method := rh.Get("Access-Control-Request-Method")
	if method == "" {
		return safehttp.StatusForbidden
	}
	wh := w.Header()
	allowMethods, err := wh.Claim("Access-Control-Allow-Methods")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	headers := rh.Get("Access-Control-Request-Headers")
	if headers != "" {
		for _, h := range strings.Split(headers, ", ") {
			h = textproto.CanonicalMIMEHeaderKey(h)
			if !it.allowedHeaders[h] && h != customHeader {
				return safehttp.StatusForbidden
			}
		}
	}
	allowHeaders, err := wh.Claim("Access-Control-Allow-Headers")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	maxAge, err := wh.Claim("Access-Control-Max-Age")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	allowMethods([]string{method})
	if headers != "" {
		allowHeaders([]string{headers})
	}
	n := it.MaxAge
	if n == 0 {
		n = 5
	}
	maxAge([]string{strconv.Itoa(n)})
	return safehttp.StatusNoContent
}

// request handles all requests that are not preflight requests.
func (it *Interceptor) request(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.StatusCode {
	h := r.Header
	if h.Get(customHeader) != "1" {
		return safehttp.StatusPreconditionFailed
	}

	if ct := h.Get("Content-Type"); ct == "" || disallowedContentTypes[ct] {
		return safehttp.StatusUnsupportedMediaType
	}

	exposeHeaders, err := w.Header().Claim("Access-Control-Expose-Headers")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	if it.ExposedHeaders != nil {
		exposeHeaders([]string{strings.Join(it.ExposedHeaders, ", ")})
	}
	return 0
}
<commit_msg>Improved comments<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cors

import (
	"net/textproto"
	"strconv"
	"strings"

	"github.com/google/go-safeweb/safehttp"
)

const customHeader string = "X-Cors"

var disallowedContentTypes = map[string]bool{
	"application/x-www-form-urlencoded": true,
	"multipart/form-data":               true,
	"text/plain":                        true,
}

// Interceptor handles CORS requests based on its settings.
//
// For more info about CORS, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
//
// Constraints
//
// - The request methods GET, HEAD and POST are banned and will result in a 405
//   Method Not Allowed response.
// - The content types "application/x-www-form-urlencoded", "multipart/form-data"
//   and "text/plain" are also banned and will result in a  415 Unsupported Media
//   Type response.
// - Each CORS request must contain the header "X-Cors: 1" or "Sec-Fetch-Mode: cors".
// All of this is to prevent XSRF.
type Interceptor struct {
	// AllowedOrigins determines which origins should be allowed in the
	// Access-Control-Allow-Origin header.
	AllowedOrigins map[string]bool
	allowedHeaders map[string]bool
	// ExposedHeaders determines which headers should be set in the
	// Access-Control-Expose-Headers header. This controls which headers in the
	// response should be accessible by JavaScript.
	//
	// If ExposedHeaders is nil, then the header is not set, meaning that nothing
	// is exposed.
	ExposedHeaders []string
	// AllowCredentials determines if Access-Control-Allow-Credentials should be
	// set to true, which would allow cookies to be attached to requests.
	AllowCredentials bool
	// MaxAge sets the Access-Control-Max-Age header.
	// MaxAge=0 results in MaxAge: 5.
	// This default of 5 seconds is set because it is the default used by Chromium
	// according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
	MaxAge int
}

// Default creates a CORS Interceptor with default settings.
// Those defaults are:
//   - No Exposed Headers
//   - No Allowed Headers
//   - AllowCredentials: false
//   - MaxAge: 5 seconds
func Default(allowedOrigins ...string) *Interceptor {
	ao := map[string]bool{}
	for _, o := range allowedOrigins {
		ao[o] = true
	}
	return &Interceptor{
		AllowedOrigins: ao,
		allowedHeaders: map[string]bool{},
	}
}

// SetAllowedHeaders sets which headers should be allowed in the Access-Control-Allow-Headers
// header. The headers are first canonicalized using textproto.CanonicalMIMEHeaderKey.
func (it *Interceptor) SetAllowedHeaders(headers ...string) {
	it.allowedHeaders = map[string]bool{}
	for _, h := range headers {
		it.allowedHeaders[textproto.CanonicalMIMEHeaderKey(h)] = true
	}
}

// Before handles the incoming request and sets headers or responds with an error
// accordingly.
func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
	origin := r.Header.Get("Origin")
	if origin != "" && !it.AllowedOrigins[origin] {
		return w.ClientError(safehttp.StatusForbidden)
	}
	h := w.Header()
	allowOrigin, err := h.Claim("Access-Control-Allow-Origin")
	if err != nil {
		return w.ServerError(safehttp.StatusInternalServerError)
	}
	vary, err := h.Claim("Vary")
	if err != nil {
		return w.ServerError(safehttp.StatusInternalServerError)
	}

	allowCredentials, err := h.Claim("Access-Control-Allow-Credentials")
	if err != nil {
		return w.ServerError(safehttp.StatusInternalServerError)
	}
	if r.Header.Get("Cookie") != "" && !it.AllowCredentials {
		return w.ClientError(safehttp.StatusForbidden)
	}

	var status safehttp.StatusCode
	if r.Method() == safehttp.MethodOptions {
		status = it.preflight(w, r)
	} else {
		status = it.request(w, r)
	}

	if status != 0 && status != safehttp.StatusNoContent {
		if 400 <= status && status < 500 {
			return w.ClientError(status)
		} else {
			return w.ServerError(status)
		}
	}

	if origin != "" {
		allowOrigin([]string{origin})
		vary([]string{"Origin"})
	}
	if r.Header.Get("Cookie") != "" && it.AllowCredentials {
		// TODO: handle other credentials than cookies:
		// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
		allowCredentials([]string{"true"})
	}

	if status == safehttp.StatusNoContent {
		return w.NoContent()
	}
	return safehttp.Result{}
}

// preflight handles requests that have the method OPTIONS.
func (it *Interceptor) preflight(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.StatusCode {
	rh := r.Header
	method := rh.Get("Access-Control-Request-Method")
	if method == "" {
		return safehttp.StatusForbidden
	}
	wh := w.Header()
	allowMethods, err := wh.Claim("Access-Control-Allow-Methods")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	headers := rh.Get("Access-Control-Request-Headers")
	if headers != "" {
		for _, h := range strings.Split(headers, ", ") {
			h = textproto.CanonicalMIMEHeaderKey(h)
			if !it.allowedHeaders[h] && h != customHeader {
				return safehttp.StatusForbidden
			}
		}
	}
	allowHeaders, err := wh.Claim("Access-Control-Allow-Headers")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	maxAge, err := wh.Claim("Access-Control-Max-Age")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	allowMethods([]string{method})
	if headers != "" {
		allowHeaders([]string{headers})
	}
	n := it.MaxAge
	if n == 0 {
		n = 5
	}
	maxAge([]string{strconv.Itoa(n)})
	return safehttp.StatusNoContent
}

// request handles all requests that are not preflight requests.
func (it *Interceptor) request(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.StatusCode {
	h := r.Header
	if h.Get(customHeader) != "1" {
		return safehttp.StatusPreconditionFailed
	}

	if ct := h.Get("Content-Type"); ct == "" || disallowedContentTypes[ct] {
		return safehttp.StatusUnsupportedMediaType
	}

	exposeHeaders, err := w.Header().Claim("Access-Control-Expose-Headers")
	if err != nil {
		return safehttp.StatusInternalServerError
	}

	if it.ExposedHeaders != nil {
		exposeHeaders([]string{strings.Join(it.ExposedHeaders, ", ")})
	}
	return 0
}
<|endoftext|>


<commit_before>package parg

import (
	"strings"
	"testing" //import go package for testing related functionality
)

// TestExtractFlags_NoArgs tests to ensure that when no arguments are provided,
// no flags or arguments are returned by extractFlags.
func TestExtractFlags_NoArgs(t *testing.T) {
	var noArgs []string
	flags, args := extractFlags(noArgs...)

	if len(flags) != 0 {
		t.Error("No flags should have been extracted")
	}

	if len(args) != 0 {
		t.Error("No arguments should have been extracted")
	}
}

// TestExtractFlags_OnlyFlags tests to ensure that if only flag arguments are
// provided, that the same number of flag arguments are returned & with no
// additional arguments by extractFlags.
func TestExtractFlags_OnlyFlags(t *testing.T) {
	onlyFlagArgs := []string{"-f", "--foobar"}
	flags, args := extractFlags(onlyFlagArgs...)

	if len(flags) != len(onlyFlagArgs) {
		t.Errorf(
			"%d number of flags expected, but only %d were extracted",
			len(onlyFlagArgs),
			len(flags),
		)
	}

	if len(args) != 0 {
		t.Error("No arguments should have been extracted")
	}
}

// TestExtractFlags_MutliShortFlags tests to ensure that multiple short-flags
// residing beside each other are properly recognized and extract individually,
// and no other arguments are returned.
func TestExtractFlags_MultiShortFlags(t *testing.T) {
	shortFlags := []string{"a", "b", "c"}
	shortFlagArgs := []string{"-" + strings.Join(shortFlags, "")}

	flags, args := extractFlags(shortFlagArgs...)

	if len(flags) != len(shortFlags) {
		t.Errorf(
			"%d number of flags expected, but only %d were extracted",
			len(shortFlags),
			len(flags),
		)
	}

	if len(args) != 0 {
		t.Error("No arguments should have been extracted")
	}
}

// TestExtractFlags_OnlyArgs tests to ensure that if only passive arguments are
// provided, that the same number of passive arguments are returned & with no
// flags extracted by extractFlags.
func TestExtractFlags_OnlyArgs(t *testing.T) {
	onlyArgs := []string{"arg1", "arg2", "arg3", "arg4"}
	flags, args := extractFlags(onlyArgs...)

	if len(args) != len(onlyArgs) {
		t.Errorf(
			"%d number of passive argumentss expected, but only %d were extracted",
			len(onlyArgs),
			len(args),
		)
	}

	if len(flags) != 0 {
		t.Error("No flags should have been extracted")
	}
}

// TestExtractFlags tests to ensure that the expected number of flags & passive
// arguments are extracted by extractFlags.
func TestExtractFlags(t *testing.T) {
	allArgs := []string{"-f", "foobar", "--fizzbuzz", "four", "five", "-irtusc"}
	numFlags := 8
	numArgs := 3

	flags, args := extractFlags(allArgs...)

	if len(flags) != numFlags {
		t.Errorf(
			"%d number of flags expected, but only %d were extracted",
			numFlags,
			len(flags),
		)
	}

	if len(args) != numArgs {
		t.Errorf(
			"%d number of passive argumentss expected, but only %d were extracted",
			numArgs,
			len(args),
		)
	}
}
<commit_msg>One more test for utils remaining<commit_after>package parg

import (
	"strings"
	"testing" //import go package for testing related functionality
)

// TestExtractFlags_NoArgs tests to ensure that when no arguments are provided,
// no flags or arguments are returned by extractFlags.
func TestExtractFlags_NoArgs(t *testing.T) {
	var noArgs []string
	flags, args := extractFlags(noArgs...)

	if len(flags) != 0 {
		t.Error("No flags should have been extracted")
	}

	if len(args) != 0 {
		t.Error("No arguments should have been extracted")
	}
}

// TestExtractFlags_OnlyFlags tests to ensure that if only flag arguments are
// provided, that the same number of flag arguments are returned & with no
// additional arguments by extractFlags.
func TestExtractFlags_OnlyFlags(t *testing.T) {
	onlyFlagArgs := []string{"-f", "--foobar"}
	flags, args := extractFlags(onlyFlagArgs...)

	if len(flags) != len(onlyFlagArgs) {
		t.Errorf(
			"%d number of flags expected, but only %d were extracted",
			len(onlyFlagArgs),
			len(flags),
		)
	}

	if len(args) != 0 {
		t.Error("No arguments should have been extracted")
	}
}

// TestExtractFlags_MutliShortFlags tests to ensure that multiple short-flags
// residing beside each other are properly recognized and extract individually,
// and no other arguments are returned.
func TestExtractFlags_MultiShortFlags(t *testing.T) {
	shortFlags := []string{"a", "b", "c"}
	shortFlagArgs := []string{"-" + strings.Join(shortFlags, "")}

	flags, args := extractFlags(shortFlagArgs...)

	if len(flags) != len(shortFlags) {
		t.Errorf(
			"%d number of flags expected, but only %d were extracted",
			len(shortFlags),
			len(flags),
		)
	}

	if len(args) != 0 {
		t.Error("No arguments should have been extracted")
	}
}

// TestExtractFlags_OnlyArgs tests to ensure that if only passive arguments are
// provided, that the same number of passive arguments are returned & with no
// flags extracted by extractFlags.
func TestExtractFlags_OnlyArgs(t *testing.T) {
	onlyArgs := []string{"arg1", "arg2", "arg3", "arg4"}
	flags, args := extractFlags(onlyArgs...)

	if len(args) != len(onlyArgs) {
		t.Errorf(
			"%d number of passive argumentss expected, but only %d were extracted",
			len(onlyArgs),
			len(args),
		)
	}

	if len(flags) != 0 {
		t.Error("No flags should have been extracted")
	}
}

// TestExtractFlags tests to ensure that the expected number of flags & passive
// arguments are extracted by extractFlags.
func TestExtractFlags(t *testing.T) {
	allArgs := []string{"-f", "foobar", "--fizzbuzz", "four", "five", "-irtusc"}
	numFlags := 8
	numArgs := 3

	flags, args := extractFlags(allArgs...)

	if len(flags) != numFlags {
		t.Errorf(
			"%d number of flags expected, but only %d were extracted",
			numFlags,
			len(flags),
		)
	}

	if len(args) != numArgs {
		t.Errorf(
			"%d number of passive argumentss expected, but only %d were extracted",
			numArgs,
			len(args),
		)
	}
}

// TestGetScreenWidth tests to ensure that a positive, non-zero integer value is returned
// to represent the width of the current screen.
func TestGetScreenWidth(t *testing.T) {
	// I am not really sure the best way to test this.
	// TODO: make a better test!

	width := getScreenWidth()
	if width <= 0 {
		t.Error("Retrieved screen width should be a positive, non-zero integer")
	}
}

// TestJoin tests to ensure that a variety of string slices can be joined in the
// correct, expected manner.
func TestJoin(t *testing.T) {
	testStrings := [][]string{
		[]string{"one", "two"},
		[]string{""},
		[]string{"three", "four", "five"},
	}

	expectedStrings := []string{
		"one two",
		"",
		"three four five",
	}

	for i, test := range testStrings {
		actual := join(" ", test...)
		expected := expectedStrings[i]

		if actual != expected {
			t.Errorf(
				"Expected: '%s' but received: '%s'",
				expected,
				actual,
			)
		}
	}

	actual := join("-*-", "abc", "def")
	expected := "abc-*-def"
	if actual != expected {
		t.Errorf(
			"Expected: '%s' but received: '%s'",
			expected,
			actual,
		)
	}
}

// TestSpacer tests to make sure the proper length strings are returned, as expected.
func TestSpacer(t *testing.T) {
	intTests := []int{-1000, -100, -10, -1, 0, 1, 10, 100, 1000}

	for _, test := range intTests {
		actual := spacer(test)
		expectedLen := test
		if expectedLen < 0 {
			expectedLen = 0
		}

		if len(actual) != expectedLen {
			if len(actual) != 0 {
				t.Errorf("Expected string of length: %d but received: '%s'", expectedLen, actual)
			}
		}
	}
}
<|endoftext|>