<commit_before>package main

import (
	"bytes"
	"fmt"
)

// pow computes z to the (nonnegative integer) power i.
func pow(z complex128, i int) complex128 {
	if i < 0 {
		panic("negative power")
	}

	var res complex128 = 1

	for i > 0 {
		res *= z
		i--
	}

	return res
}

// A Function can be evaluated at a complex point.
type Function interface {
	// evaluate computes the value of the function at z.
	Evaluate(z complex128) complex128
}

// A poly is a (complex) polynomial.
type poly []complex128

// Degree returns the degree of the polynomial.
//
// The degree of the zero polynomial is 0.
func (f poly) Degree() int {
	if len(f) == 0 {
		return 0
	}

	var deg int

	for i, v := range f {
		if v != 0 {
			deg = i
		}
	}

	if deg != 0 && f[deg] == 0 {
		return deg - 1
	}

	return deg
}

// Normalise returns a new poly with any excess zeroes culled from the end.
func (f poly) Normalise() poly {
	deg := f.Degree()

	if deg == 0 && len(f) > 0 && f[0] == 0 {
		return poly([]complex128{})
	}

	if deg < len(f) {
		return f[:deg+1]
	}

	return f
}

// IsConstant checks whether the (normalised) polynomial is constant.
func (f poly) IsConstant() bool {
	return len(f) <= 1
}

func (f poly) String() string {
	var b bytes.Buffer

	if len(f) == 0 {
		return "0"
	}

	b.WriteString(fmt.Sprint(f[0]))

	if len(f) >= 2 {
		part := fmt.Sprintf(" + %vt", f[1])
		b.WriteString(part)
	} else {
		return b.String()
	}

	for i, z := range f[2:] {
		part := fmt.Sprintf(" + %vt^%v", z, i+2)
		b.WriteString(part)
	}

	return b.String()
}

func (f poly) Evaluate(z complex128) complex128 {
	var res complex128

	for i, coeff := range f {
		res += coeff * pow(z, i)
	}

	return res
}

// NewPoly returns a Function coefficients coeffs, starting with the constant term.
func NewPoly(coeffs ...complex128) Function {
	return poly(coeffs)
}

// A rat is a rational function.
type rat struct {
	numerator   poly
	denominator poly
}

func (f rat) Evaluate(z complex128) complex128 {
	num := f.numerator.Evaluate(z)
	denom := f.denominator.Evaluate(z)
	return num / denom
}
<commit_msg>Remove redundant check in Degree<commit_after>package main

import (
	"bytes"
	"fmt"
)

// pow computes z to the (nonnegative integer) power i.
func pow(z complex128, i int) complex128 {
	if i < 0 {
		panic("negative power")
	}

	var res complex128 = 1

	for i > 0 {
		res *= z
		i--
	}

	return res
}

// A Function can be evaluated at a complex point.
type Function interface {
	// evaluate computes the value of the function at z.
	Evaluate(z complex128) complex128
}

// A poly is a (complex) polynomial.
type poly []complex128

// Degree returns the degree of the polynomial.
//
// The degree of the zero polynomial is 0.
func (f poly) Degree() int {
	if len(f) == 0 {
		return 0
	}

	var deg int

	for i, v := range f {
		if v != 0 {
			deg = i
		}
	}

	return deg
}

// Normalise returns a new poly with any excess zeroes culled from the end.
func (f poly) Normalise() poly {
	deg := f.Degree()

	if deg == 0 && len(f) > 0 && f[0] == 0 {
		return poly([]complex128{})
	}

	if deg < len(f) {
		return f[:deg+1]
	}

	return f
}

// IsConstant checks whether the (normalised) polynomial is constant.
func (f poly) IsConstant() bool {
	return len(f) <= 1
}

func (f poly) String() string {
	var b bytes.Buffer

	if len(f) == 0 {
		return "0"
	}

	b.WriteString(fmt.Sprint(f[0]))

	if len(f) >= 2 {
		part := fmt.Sprintf(" + %vt", f[1])
		b.WriteString(part)
	} else {
		return b.String()
	}

	for i, z := range f[2:] {
		part := fmt.Sprintf(" + %vt^%v", z, i+2)
		b.WriteString(part)
	}

	return b.String()
}

func (f poly) Evaluate(z complex128) complex128 {
	var res complex128

	for i, coeff := range f {
		res += coeff * pow(z, i)
	}

	return res
}

// NewPoly returns a Function coefficients coeffs, starting with the constant term.
func NewPoly(coeffs ...complex128) Function {
	return poly(coeffs)
}

// A rat is a rational function.
type rat struct {
	numerator   poly
	denominator poly
}

func (f rat) Evaluate(z complex128) complex128 {
	num := f.numerator.Evaluate(z)
	denom := f.denominator.Evaluate(z)
	return num / denom
}
<|endoftext|>


<commit_before>package kv

import (
	"fmt"
	"os"
	"path"
	"github.com/s3git/s3git-go/config"
	mdb "github.com/szferi/gomdb"
	"encoding/hex"
)

var env *mdb.Env

// KV databases containing root level digests for different object types
// When a particular key is present, the value is as follows:
//   - when empty, underlying chunk(s) are not cached locally
//   - when set, it is the concatenation of the leaf level digests for all nodes
//     (and thus necessarily needs to correspond when BLAKE2'd to its key)
//
// If you know the type of the key, you can fetch it directly for the corresponding database
// If you do not know the type, you need to search all stores

var dbiLevel1Blobs mdb.DBI
var dbiLevel1Commits mdb.DBI
var dbiLevel1Prefixes mdb.DBI
var dbiLevel1Trees mdb.DBI

// KV database containing overview of added/removed blobs in stage
var dbiStage mdb.DBI

func OpenDatabase() error {

	mdbDir := path.Join(config.Config.S3gitCasPath, ".mdb")
	err := os.MkdirAll(mdbDir, 0777)
	if err != nil {
		return err
	}

	env, _ = mdb.NewEnv()
	// TODO: Figure out proper size for lmdb
	env.SetMapSize(1 << 36) // max file size
	env.SetMaxDBs(10)		// up to 10 named databases
	env.Open(mdbDir, 0, 0664)
	txn, _ := env.BeginTxn(nil, 0)

	// overview of blobs in stage
	dbstage := "stage"
	dbiStage, _ = txn.DBIOpen(&dbstage, mdb.CREATE)

	// Level 1 databases
	dbl1blobs := "l1blobs"
	dbiLevel1Blobs, _ = txn.DBIOpen(&dbl1blobs, mdb.CREATE)
	dbl1commits := "l1commits"
	dbiLevel1Commits, _ = txn.DBIOpen(&dbl1commits, mdb.CREATE)
	dbl1prefixes := "l1prefixes"
	dbiLevel1Prefixes, _ = txn.DBIOpen(&dbl1prefixes, mdb.CREATE)
	dbl1trees := "l1trees"
	dbiLevel1Trees, _ = txn.DBIOpen(&dbl1trees, mdb.CREATE)

	txn.Commit()

	// TODO: Make sure all databases are flushed before exiting program
	//	defer env.DBIClose(dbi)
	//	defer env.Close()

	return nil
}

func AddToStage(key string) error {

	hex, _ := hex.DecodeString(key)

	txn, _ := env.BeginTxn(nil, 0)
	txn.Put(dbiStage, hex, nil, 0)
	txn.Commit()

	return nil
}

func RemoveFromStage(keys []string) error {

	txn, _ := env.BeginTxn(nil, 0)
	for _, k := range keys {
		b, _ := hex.DecodeString(k)
		txn.Del(dbiStage, b, nil)
	}
	txn.Commit()

	return nil
}

func ListStage() (<-chan []byte, error) {

	return listMdb(&dbiStage, "")
}

func ListLevel1Commits() (<-chan []byte, error) {

	return listMdb(&dbiLevel1Commits, "")
}

func ListLevel1Prefixes() (<-chan []byte, error) {

	return listMdb(&dbiLevel1Prefixes, "")
}

func ListLevel1Trees() (<-chan []byte, error) {

	return listMdb(&dbiLevel1Trees, "")
}

func ListLevel1Blobs(query string) (<-chan []byte, error) {

	return listMdb(&dbiLevel1Blobs, query)
}

// TODO: Remove duplicate definitions
const BLOB="blob"
const COMMIT="commit"
const PREFIX="prefix"
const TREE="tree"

func getDbForObjectType( objType string) *mdb.DBI {

	var dbi *mdb.DBI
	switch objType {
	case BLOB:
		dbi = &dbiLevel1Blobs
	case COMMIT:
		dbi = &dbiLevel1Commits
	case PREFIX:
		dbi = &dbiLevel1Prefixes
	case TREE:
		dbi = &dbiLevel1Trees
	default:
		panic(fmt.Sprintf("Bad type: %s", objType))
	}
	return dbi
}

func AddToLevel1(key, value []byte, objType string) error {

	dbi := getDbForObjectType(objType)
	txn, _ := env.BeginTxn(nil, 0)
	txn.Put(*dbi, key, value, 0)
	txn.Commit()

	return nil
}

func AddMultiToLevel1(keys, values [][]byte, objType string) error {

	dbi := getDbForObjectType(objType)
	txn, _ := env.BeginTxn(nil, 0)
	for index, key := range keys {
		txn.Put(*dbi, key, values[index], 0)
	}
	txn.Commit()

	return nil
}

// Get object of any type, return value and type
func GetLevel1(key []byte) ([]byte, string, error) {

	txn, _ := env.BeginTxn(nil, mdb.RDONLY)
	defer txn.Abort()

	val, err := txn.Get(dbiLevel1Blobs, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, BLOB, err
	}

	val, err = txn.Get(dbiLevel1Commits, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, COMMIT, err
	}

	val, err = txn.Get(dbiLevel1Prefixes, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, PREFIX, err
	}

	val, err = txn.Get(dbiLevel1Trees, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, TREE, err
	}

	return nil, "", mdb.NotFound
}

func listMdb(dbi *mdb.DBI, query string) (<-chan []byte, error) {

	result := make(chan []byte)

	go func() {
		// make sure we always close the channel
		defer close(result)

		// scan the database
		txn, _ := env.BeginTxn(nil, mdb.RDONLY)
		defer txn.Abort()
		cursor, _ := txn.CursorOpen(*dbi)
		defer cursor.Close()

		setRangeUponStart := len(query) > 0
		var queryKey []byte
		if setRangeUponStart {
			q := query
			if len(q) % 2 == 1 {
				q = q + "0"
			}
			queryKey, _ = hex.DecodeString(q)
		}

		for {

			var bkey []byte
			if setRangeUponStart {
				bval, _, err := cursor.GetVal(queryKey, nil, mdb.SET_RANGE)
				if err == mdb.NotFound {
					break
				}
				if err != nil {
					// TODO: Log error
					return
				}

				bkey = bval.Bytes()

				setRangeUponStart = false
			} else {
				var err error
				bkey, _, err = cursor.Get(nil, nil, mdb.NEXT)
				if err == mdb.NotFound {
					break
				}
				if err != nil {
					// TODO: Log error
					return
				}

			}

			// break early is start of key is not longer
			if hex.EncodeToString(bkey)[:len(query)] != query {
				break
			}
			result <- bkey
		}

	}()

	return result, nil
}
<commit_msg>Formatting<commit_after>package kv

import (
	"fmt"
	"os"
	"path"
	"github.com/s3git/s3git-go/config"
	mdb "github.com/szferi/gomdb"
	"encoding/hex"
)

var env *mdb.Env

// KV databases containing root level digests for different object types
// When a particular key is present, the value is as follows:
//   - when empty, underlying chunk(s) are not cached locally
//   - when set, it is the concatenation of the leaf level digests for all nodes
//     (and thus necessarily needs to correspond when BLAKE2'd to its key)
//
// If you know the type of the key, you can fetch it directly for the corresponding database
// If you do not know the type, you need to search all stores

var dbiLevel1Blobs mdb.DBI
var dbiLevel1Commits mdb.DBI
var dbiLevel1Prefixes mdb.DBI
var dbiLevel1Trees mdb.DBI

// KV database containing overview of added/removed blobs in stage
var dbiStage mdb.DBI

func OpenDatabase() error {

	mdbDir := path.Join(config.Config.S3gitCasPath, ".mdb")
	err := os.MkdirAll(mdbDir, 0777)
	if err != nil {
		return err
	}

	env, _ = mdb.NewEnv()
	// TODO: Figure out proper size for lmdb
	env.SetMapSize(1 << 36) // max file size
	env.SetMaxDBs(10)		// up to 10 named databases
	env.Open(mdbDir, 0, 0664)
	txn, _ := env.BeginTxn(nil, 0)

	// overview of blobs in stage
	dbstage := "stage"
	dbiStage, _ = txn.DBIOpen(&dbstage, mdb.CREATE)

	// Level 1 databases
	dbl1blobs := "l1blobs"
	dbiLevel1Blobs, _ = txn.DBIOpen(&dbl1blobs, mdb.CREATE)
	dbl1commits := "l1commits"
	dbiLevel1Commits, _ = txn.DBIOpen(&dbl1commits, mdb.CREATE)
	dbl1prefixes := "l1prefixes"
	dbiLevel1Prefixes, _ = txn.DBIOpen(&dbl1prefixes, mdb.CREATE)
	dbl1trees := "l1trees"
	dbiLevel1Trees, _ = txn.DBIOpen(&dbl1trees, mdb.CREATE)

	txn.Commit()

	// TODO: Make sure all databases are flushed before exiting program
	//	defer env.DBIClose(dbi)
	//	defer env.Close()

	return nil
}

func AddToStage(key string) error {

	hex, _ := hex.DecodeString(key)

	txn, _ := env.BeginTxn(nil, 0)
	txn.Put(dbiStage, hex, nil, 0)
	txn.Commit()

	return nil
}

func RemoveFromStage(keys []string) error {

	txn, _ := env.BeginTxn(nil, 0)
	for _, k := range keys {
		b, _ := hex.DecodeString(k)
		txn.Del(dbiStage, b, nil)
	}
	txn.Commit()

	return nil
}

func ListStage() (<-chan []byte, error) {

	return listMdb(&dbiStage, "")
}

func ListLevel1Commits() (<-chan []byte, error) {

	return listMdb(&dbiLevel1Commits, "")
}

func ListLevel1Prefixes() (<-chan []byte, error) {

	return listMdb(&dbiLevel1Prefixes, "")
}

func ListLevel1Trees() (<-chan []byte, error) {

	return listMdb(&dbiLevel1Trees, "")
}

func ListLevel1Blobs(query string) (<-chan []byte, error) {

	return listMdb(&dbiLevel1Blobs, query)
}

// TODO: Remove duplicate definitions
const BLOB="blob"
const COMMIT="commit"
const PREFIX="prefix"
const TREE="tree"

func getDbForObjectType( objType string) *mdb.DBI {

	var dbi *mdb.DBI
	switch objType {
	case BLOB:
		dbi = &dbiLevel1Blobs
	case COMMIT:
		dbi = &dbiLevel1Commits
	case PREFIX:
		dbi = &dbiLevel1Prefixes
	case TREE:
		dbi = &dbiLevel1Trees
	default:
		panic(fmt.Sprintf("Bad type: %s", objType))
	}
	return dbi
}

func AddToLevel1(key, value []byte, objType string) error {

	dbi := getDbForObjectType(objType)
	txn, _ := env.BeginTxn(nil, 0)
	txn.Put(*dbi, key, value, 0)
	txn.Commit()

	return nil
}

func AddMultiToLevel1(keys, values [][]byte, objType string) error {

	dbi := getDbForObjectType(objType)
	txn, _ := env.BeginTxn(nil, 0)
	for index, key := range keys {
		txn.Put(*dbi, key, values[index], 0)
	}
	txn.Commit()

	return nil
}

// Get object of any type, return value and type
func GetLevel1(key []byte) ([]byte, string, error) {

	txn, _ := env.BeginTxn(nil, mdb.RDONLY)
	defer txn.Abort()

	val, err := txn.Get(dbiLevel1Blobs, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, BLOB, err
	}

	val, err = txn.Get(dbiLevel1Commits, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, COMMIT, err
	}

	val, err = txn.Get(dbiLevel1Prefixes, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, PREFIX, err
	}

	val, err = txn.Get(dbiLevel1Trees, key)
	if err != nil && !(err == mdb.NotFound) {
		return nil, "", err
	} else if !(err == mdb.NotFound) {
		return val, TREE, err
	}

	return nil, "", mdb.NotFound
}

func listMdb(dbi *mdb.DBI, query string) (<-chan []byte, error) {

	result := make(chan []byte)

	go func() {
		// make sure we always close the channel
		defer close(result)

		// scan the database
		txn, _ := env.BeginTxn(nil, mdb.RDONLY)
		defer txn.Abort()
		cursor, _ := txn.CursorOpen(*dbi)
		defer cursor.Close()

		setRangeUponStart := len(query) > 0
		var queryKey []byte
		if setRangeUponStart {
			q := query
			if len(q) % 2 == 1 {
				q = q + "0"
			}
			queryKey, _ = hex.DecodeString(q)
		}

		for {

			var bkey []byte
			if setRangeUponStart {
				bval, _, err := cursor.GetVal(queryKey, nil, mdb.SET_RANGE)
				if err == mdb.NotFound {
					break
				}
				if err != nil {
					// TODO: Log error
					return
				}

				bkey = bval.Bytes()

				setRangeUponStart = false
			} else {
				var err error
				bkey, _, err = cursor.Get(nil, nil, mdb.NEXT)
				if err == mdb.NotFound {
					break
				}
				if err != nil {
					// TODO: Log error
					return
				}

			}

			// break early is start of key is not longer
			if hex.EncodeToString(bkey)[:len(query)] != query {
				break
			}
			result <- bkey
		}

	}()

	return result, nil
}<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Simon Schmidt
 *
 * This Source Code is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at:
 *
 *                 http://mozilla.org/MPL/2.0/
 */

package lexer

import "regexp"

type ParserError int
func (i ParserError) Error() string {
	switch i {
	case PE_NO_TOKEN_MATCHING: return "PE_NO_TOKEN_MATCHING"
	default: return "generic error (unknown)"
	}
}

const (
	PE_UNKNOWN ParserError = iota
	PE_NO_TOKEN_MATCHING
)

type Result struct{
	Number int
	Text   string
	Count  int
	Start  int
}

type Token struct{
	Number int
	Regexp string
}
type rule struct{
	number int
	expr   *regexp.Regexp
}

type Ruleset []rule
func Compile(t []Token) (rs Ruleset,err error) {
	rs = make(Ruleset,len(t))
	for i,tt := range t {
		rs[i].number = tt.Number
		rs[i].expr,err = regexp.Compile("^"+tt.Regexp)
		if err!=nil { rs = nil; return }
	}
	return
}
func (r Ruleset) Parse(txt string) (res []Result,err error) {
	var fi []int
	var num int
	lastnum := 0
	pos := 0
	for len(txt)>0 {
		fi = nil
		for _,ru := range r {
			num = ru.number
			if fi = ru.expr.FindStringIndex(txt); fi!=nil { break }
		}
		if fi==nil { err=PE_NO_TOKEN_MATCHING; return }
		cur := txt[:fi[1]]
		txt  = txt[fi[1]:]
		pos += fi[1]
		if num<0 {
			num =- num
			if lastnum==num {
				res[len(res)-1].Count++
				continue
			}
			lastnum = num
		} else if num==0 {
			continue
		} else {
			lastnum = 0
		}
		res = append(res,Result{num,cur,1,pos-fi[1]})
	}
	return
}
<commit_msg>crlf>lf<commit_after>/*
 * Copyright (C) 2015 Simon Schmidt
 *
 * This Source Code is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at:
 *
 *                 http://mozilla.org/MPL/2.0/
 */

package lexer

import "regexp"

type ParserError int
func (i ParserError) Error() string {
	switch i {
	case PE_NO_TOKEN_MATCHING: return "PE_NO_TOKEN_MATCHING"
	default: return "generic error (unknown)"
	}
}

const (
	PE_UNKNOWN ParserError = iota
	PE_NO_TOKEN_MATCHING
)

type Result struct{
	Number int
	Text   string
	Count  int
	Start  int
}

type Token struct{
	Number int
	Regexp string
}
type rule struct{
	number int
	expr   *regexp.Regexp
}

type Ruleset []rule
func Compile(t []Token) (rs Ruleset,err error) {
	rs = make(Ruleset,len(t))
	for i,tt := range t {
		rs[i].number = tt.Number
		rs[i].expr,err = regexp.Compile("^"+tt.Regexp)
		if err!=nil { rs = nil; return }
	}
	return
}
func (r Ruleset) Parse(txt string) (res []Result,err error) {
	var fi []int
	var num int
	lastnum := 0
	pos := 0
	for len(txt)>0 {
		fi = nil
		for _,ru := range r {
			num = ru.number
			if fi = ru.expr.FindStringIndex(txt); fi!=nil { break }
		}
		if fi==nil { err=PE_NO_TOKEN_MATCHING; return }
		cur := txt[:fi[1]]
		txt  = txt[fi[1]:]
		pos += fi[1]
		if num<0 {
			num =- num
			if lastnum==num {
				res[len(res)-1].Count++
				continue
			}
			lastnum = num
		} else if num==0 {
			continue
		} else {
			lastnum = 0
		}
		res = append(res,Result{num,cur,1,pos-fi[1]})
	}
	return
}
<|endoftext|>


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

package minio

import (
	"bytes"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/xml"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
)

// Comprehensive put object operation involving multipart resumable uploads.
//
// Following code handles these types of readers.
//
//  - *os.File
//  - *minio.Object
//  - Any reader which has a method 'ReadAt()'
//
// If we exhaust all the known types, code proceeds to use stream as
// is where each part is re-downloaded, checksummed and verified
// before upload.
func (c Client) putObjectMultipart(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
	if size > 0 && size > minPartSize {
		// Verify if reader is *os.File, then use file system functionalities.
		if isFile(reader) {
			return c.putObjectMultipartFromFile(bucketName, objectName, reader.(*os.File), size, contentType, progress)
		}
		// Verify if reader is *minio.Object or io.ReaderAt.
		// NOTE: Verification of object is kept for a specific purpose
		// while it is going to be duck typed similar to io.ReaderAt.
		// It is to indicate that *minio.Object implements io.ReaderAt.
		// and such a functionality is used in the subsequent code
		// path.
		if isObject(reader) || isReadAt(reader) {
			return c.putObjectMultipartFromReadAt(bucketName, objectName, reader.(io.ReaderAt), size, contentType, progress)
		}
	}
	// For any other data size and reader type we do generic multipart
	// approach by staging data in temporary files and uploading them.
	return c.putObjectMultipartStream(bucketName, objectName, reader, size, contentType, progress)
}

// putObjectStream uploads files bigger than 5MiB, and also supports
// special case where size is unknown i.e '-1'.
func (c Client) putObjectMultipartStream(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return 0, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return 0, err
	}

	// Total data read and written to server. should be equal to 'size' at the end of the call.
	var totalUploadedSize int64

	// Complete multipart upload.
	var complMultipartUpload completeMultipartUpload

	// A map of all previously uploaded parts.
	var partsInfo = make(map[int]objectPart)

	// getUploadID for an object, initiates a new multipart request
	// if it cannot find any previously partially uploaded object.
	uploadID, isNew, err := c.getUploadID(bucketName, objectName, contentType)
	if err != nil {
		return 0, err
	}

	// If This session is a continuation of a previous session fetch all
	// previously uploaded parts info and as a special case only fetch partsInfo
	// for only known upload size.
	if !isNew {
		// Fetch previously uploaded parts and maximum part size.
		partsInfo, err = c.listObjectParts(bucketName, objectName, uploadID)
		if err != nil {
			return 0, err
		}
	}

	// Calculate the optimal parts info for a given size.
	totalPartsCount, partSize, _, err := optimalPartInfo(size)
	if err != nil {
		return 0, err
	}

	// Part number always starts with '1'.
	partNumber := 1

	// Initialize a temporary buffer.
	tmpBuffer := new(bytes.Buffer)

	for partNumber <= totalPartsCount {
		// Choose hash algorithms to be calculated by hashCopyN, avoid sha256
		// with non-v4 signature request or HTTPS connection
		hashSums := make(map[string][]byte)
		hashAlgos := make(map[string]hash.Hash)
		hashAlgos["md5"] = md5.New()
		if c.signature.isV4() && !c.secure {
			hashAlgos["sha256"] = sha256.New()
		}

		// Calculates hash sums while copying partSize bytes into tmpBuffer.
		prtSize, rErr := hashCopyN(hashAlgos, hashSums, tmpBuffer, reader, partSize)
		if rErr != nil {
			if rErr != io.EOF {
				return 0, rErr
			}
		}

		var reader io.Reader
		// Update progress reader appropriately to the latest offset
		// as we read from the source.
		reader = newHook(tmpBuffer, progress)

		part, ok := partsInfo[partNumber]

		// Verify if part should be uploaded.
		if ok && shouldUploadPart(objectPart{
			ETag:       hex.EncodeToString(hashSums["md5"]),
			PartNumber: partNumber,
			Size:       prtSize,
		}, uploadPartReq{PartNum: partNumber, Part: &part}) {
			// Proceed to upload the part.
			var objPart objectPart
			objPart, err = c.uploadPart(bucketName, objectName, uploadID, reader, partNumber, hashSums["md5"], hashSums["sha256"], prtSize)
			if err != nil {
				// Reset the temporary buffer upon any error.
				tmpBuffer.Reset()
				return totalUploadedSize, err
			}
			// Save successfully uploaded part metadata.
			partsInfo[partNumber] = objPart
		} else {
			// Update the progress reader for the skipped part.
			if progress != nil {
				if _, err = io.CopyN(ioutil.Discard, progress, prtSize); err != nil {
					return totalUploadedSize, err
				}
			}
		}

		// Reset the temporary buffer.
		tmpBuffer.Reset()

		// Save successfully uploaded size.
		totalUploadedSize += prtSize

		// Increment part number.
		partNumber++

		// For unknown size, Read EOF we break away.
		// We do not have to upload till totalPartsCount.
		if size < 0 && rErr == io.EOF {
			break
		}
	}

	// Verify if we uploaded all the data.
	if size > 0 {
		if totalUploadedSize != size {
			return totalUploadedSize, ErrUnexpectedEOF(totalUploadedSize, size, bucketName, objectName)
		}
	}

	// Loop over total uploaded parts to save them in
	// Parts array before completing the multipart request.
	for i := 1; i < partNumber; i++ {
		part, ok := partsInfo[i]
		if !ok {
			return 0, ErrInvalidArgument(fmt.Sprintf("Missing part number %d", i))
		}
		complMultipartUpload.Parts = append(complMultipartUpload.Parts, completePart{
			ETag:       part.ETag,
			PartNumber: part.PartNumber,
		})
	}

	// Sort all completed parts.
	sort.Sort(completedParts(complMultipartUpload.Parts))
	_, err = c.completeMultipartUpload(bucketName, objectName, uploadID, complMultipartUpload)
	if err != nil {
		return totalUploadedSize, err
	}

	// Return final size.
	return totalUploadedSize, nil
}

// initiateMultipartUpload - Initiates a multipart upload and returns an upload ID.
func (c Client) initiateMultipartUpload(bucketName, objectName, contentType string) (initiateMultipartUploadResult, error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return initiateMultipartUploadResult{}, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return initiateMultipartUploadResult{}, err
	}

	// Initialize url queries.
	urlValues := make(url.Values)
	urlValues.Set("uploads", "")

	if contentType == "" {
		contentType = "application/octet-stream"
	}

	// Set ContentType header.
	customHeader := make(http.Header)
	customHeader.Set("Content-Type", contentType)

	reqMetadata := requestMetadata{
		bucketName:   bucketName,
		objectName:   objectName,
		queryValues:  urlValues,
		customHeader: customHeader,
	}

	// Execute POST on an objectName to initiate multipart upload.
	resp, err := c.executeMethod("POST", reqMetadata)
	defer closeResponse(resp)
	if err != nil {
		return initiateMultipartUploadResult{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return initiateMultipartUploadResult{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}
	// Decode xml for new multipart upload.
	initiateMultipartUploadResult := initiateMultipartUploadResult{}
	err = xmlDecoder(resp.Body, &initiateMultipartUploadResult)
	if err != nil {
		return initiateMultipartUploadResult, err
	}
	return initiateMultipartUploadResult, nil
}

// uploadPart - Uploads a part in a multipart upload.
func (c Client) uploadPart(bucketName, objectName, uploadID string, reader io.Reader, partNumber int, md5Sum, sha256Sum []byte, size int64) (objectPart, error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return objectPart{}, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return objectPart{}, err
	}
	if size > maxPartSize {
		return objectPart{}, ErrEntityTooLarge(size, maxPartSize, bucketName, objectName)
	}
	if size <= -1 {
		return objectPart{}, ErrEntityTooSmall(size, bucketName, objectName)
	}
	if partNumber <= 0 {
		return objectPart{}, ErrInvalidArgument("Part number cannot be negative or equal to zero.")
	}
	if uploadID == "" {
		return objectPart{}, ErrInvalidArgument("UploadID cannot be empty.")
	}

	// Get resources properly escaped and lined up before using them in http request.
	urlValues := make(url.Values)
	// Set part number.
	urlValues.Set("partNumber", strconv.Itoa(partNumber))
	// Set upload id.
	urlValues.Set("uploadId", uploadID)

	reqMetadata := requestMetadata{
		bucketName:         bucketName,
		objectName:         objectName,
		queryValues:        urlValues,
		contentBody:        reader,
		contentLength:      size,
		contentMD5Bytes:    md5Sum,
		contentSHA256Bytes: sha256Sum,
	}

	// Execute PUT on each part.
	resp, err := c.executeMethod("PUT", reqMetadata)
	defer closeResponse(resp)
	if err != nil {
		return objectPart{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return objectPart{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}
	// Once successfully uploaded, return completed part.
	objPart := objectPart{}
	objPart.Size = size
	objPart.PartNumber = partNumber
	// Trim off the odd double quotes from ETag in the beginning and end.
	objPart.ETag = strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
	objPart.ETag = strings.TrimSuffix(objPart.ETag, "\"")
	return objPart, nil
}

// completeMultipartUpload - Completes a multipart upload by assembling previously uploaded parts.
func (c Client) completeMultipartUpload(bucketName, objectName, uploadID string, complete completeMultipartUpload) (completeMultipartUploadResult, error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return completeMultipartUploadResult{}, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return completeMultipartUploadResult{}, err
	}

	// Initialize url queries.
	urlValues := make(url.Values)
	urlValues.Set("uploadId", uploadID)

	// Marshal complete multipart body.
	completeMultipartUploadBytes, err := xml.Marshal(complete)
	if err != nil {
		return completeMultipartUploadResult{}, err
	}

	// Instantiate all the complete multipart buffer.
	completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
	reqMetadata := requestMetadata{
		bucketName:         bucketName,
		objectName:         objectName,
		queryValues:        urlValues,
		contentBody:        completeMultipartUploadBuffer,
		contentLength:      int64(len(completeMultipartUploadBytes)),
		contentSHA256Bytes: sum256(completeMultipartUploadBytes),
	}

	// Execute POST to complete multipart upload for an objectName.
	resp, err := c.executeMethod("POST", reqMetadata)
	defer closeResponse(resp)
	if err != nil {
		return completeMultipartUploadResult{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return completeMultipartUploadResult{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}

	// Read resp.Body into a []bytes to parse for Error response inside the body
	var b []byte
	b, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return completeMultipartUploadResult{}, err
	}
	// Decode completed multipart upload response on success.
	completeMultipartUploadResult := completeMultipartUploadResult{}
	err = xmlDecoder(bytes.NewReader(b), &completeMultipartUploadResult)
	if err != nil {
		// xml parsing failure due to presence an ill-formed xml fragment
		return completeMultipartUploadResult, err
	} else if completeMultipartUploadResult.Bucket == "" {
		// xml's Decode method ignores well-formed xml that don't apply to the type of value supplied.
		// In this case, it would leave completeMultipartUploadResult with the corresponding zero-values
		// of the members.

		// Decode completed multipart upload response on failure
		completeMultipartUploadErr := ErrorResponse{}
		err = xmlDecoder(bytes.NewReader(b), &completeMultipartUploadErr)
		if err != nil {
			// xml parsing failure due to presence an ill-formed xml fragment
			return completeMultipartUploadResult, err
		}
		return completeMultipartUploadResult, completeMultipartUploadErr
	}
	return completeMultipartUploadResult, nil
}
<commit_msg>Fix wrong check in uploading multipart stream (#546)<commit_after>/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package minio

import (
	"bytes"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/xml"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
)

// Comprehensive put object operation involving multipart resumable uploads.
//
// Following code handles these types of readers.
//
//  - *os.File
//  - *minio.Object
//  - Any reader which has a method 'ReadAt()'
//
// If we exhaust all the known types, code proceeds to use stream as
// is where each part is re-downloaded, checksummed and verified
// before upload.
func (c Client) putObjectMultipart(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
	if size > 0 && size > minPartSize {
		// Verify if reader is *os.File, then use file system functionalities.
		if isFile(reader) {
			return c.putObjectMultipartFromFile(bucketName, objectName, reader.(*os.File), size, contentType, progress)
		}
		// Verify if reader is *minio.Object or io.ReaderAt.
		// NOTE: Verification of object is kept for a specific purpose
		// while it is going to be duck typed similar to io.ReaderAt.
		// It is to indicate that *minio.Object implements io.ReaderAt.
		// and such a functionality is used in the subsequent code
		// path.
		if isObject(reader) || isReadAt(reader) {
			return c.putObjectMultipartFromReadAt(bucketName, objectName, reader.(io.ReaderAt), size, contentType, progress)
		}
	}
	// For any other data size and reader type we do generic multipart
	// approach by staging data in temporary files and uploading them.
	return c.putObjectMultipartStream(bucketName, objectName, reader, size, contentType, progress)
}

// putObjectStream uploads files bigger than 5MiB, and also supports
// special case where size is unknown i.e '-1'.
func (c Client) putObjectMultipartStream(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return 0, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return 0, err
	}

	// Total data read and written to server. should be equal to 'size' at the end of the call.
	var totalUploadedSize int64

	// Complete multipart upload.
	var complMultipartUpload completeMultipartUpload

	// A map of all previously uploaded parts.
	var partsInfo = make(map[int]objectPart)

	// getUploadID for an object, initiates a new multipart request
	// if it cannot find any previously partially uploaded object.
	uploadID, isNew, err := c.getUploadID(bucketName, objectName, contentType)
	if err != nil {
		return 0, err
	}

	// If This session is a continuation of a previous session fetch all
	// previously uploaded parts info and as a special case only fetch partsInfo
	// for only known upload size.
	if !isNew {
		// Fetch previously uploaded parts and maximum part size.
		partsInfo, err = c.listObjectParts(bucketName, objectName, uploadID)
		if err != nil {
			return 0, err
		}
	}

	// Calculate the optimal parts info for a given size.
	totalPartsCount, partSize, _, err := optimalPartInfo(size)
	if err != nil {
		return 0, err
	}

	// Part number always starts with '1'.
	partNumber := 1

	// Initialize a temporary buffer.
	tmpBuffer := new(bytes.Buffer)

	for partNumber <= totalPartsCount {
		// Choose hash algorithms to be calculated by hashCopyN, avoid sha256
		// with non-v4 signature request or HTTPS connection
		hashSums := make(map[string][]byte)
		hashAlgos := make(map[string]hash.Hash)
		hashAlgos["md5"] = md5.New()
		if c.signature.isV4() && !c.secure {
			hashAlgos["sha256"] = sha256.New()
		}

		// Calculates hash sums while copying partSize bytes into tmpBuffer.
		prtSize, rErr := hashCopyN(hashAlgos, hashSums, tmpBuffer, reader, partSize)
		if rErr != nil {
			if rErr != io.EOF {
				return 0, rErr
			}
		}

		var reader io.Reader
		// Update progress reader appropriately to the latest offset
		// as we read from the source.
		reader = newHook(tmpBuffer, progress)

		part, ok := partsInfo[partNumber]

		// Verify if part should be uploaded.
		if !ok || shouldUploadPart(objectPart{
			ETag:       hex.EncodeToString(hashSums["md5"]),
			PartNumber: partNumber,
			Size:       prtSize,
		}, uploadPartReq{PartNum: partNumber, Part: &part}) {
			// Proceed to upload the part.
			var objPart objectPart
			objPart, err = c.uploadPart(bucketName, objectName, uploadID, reader, partNumber, hashSums["md5"], hashSums["sha256"], prtSize)
			if err != nil {
				// Reset the temporary buffer upon any error.
				tmpBuffer.Reset()
				return totalUploadedSize, err
			}
			// Save successfully uploaded part metadata.
			partsInfo[partNumber] = objPart
		} else {
			// Update the progress reader for the skipped part.
			if progress != nil {
				if _, err = io.CopyN(ioutil.Discard, progress, prtSize); err != nil {
					return totalUploadedSize, err
				}
			}
		}

		// Reset the temporary buffer.
		tmpBuffer.Reset()

		// Save successfully uploaded size.
		totalUploadedSize += prtSize

		// Increment part number.
		partNumber++

		// For unknown size, Read EOF we break away.
		// We do not have to upload till totalPartsCount.
		if size < 0 && rErr == io.EOF {
			break
		}
	}

	// Verify if we uploaded all the data.
	if size > 0 {
		if totalUploadedSize != size {
			return totalUploadedSize, ErrUnexpectedEOF(totalUploadedSize, size, bucketName, objectName)
		}
	}

	// Loop over total uploaded parts to save them in
	// Parts array before completing the multipart request.
	for i := 1; i < partNumber; i++ {
		part, ok := partsInfo[i]
		if !ok {
			return 0, ErrInvalidArgument(fmt.Sprintf("Missing part number %d", i))
		}
		complMultipartUpload.Parts = append(complMultipartUpload.Parts, completePart{
			ETag:       part.ETag,
			PartNumber: part.PartNumber,
		})
	}

	// Sort all completed parts.
	sort.Sort(completedParts(complMultipartUpload.Parts))
	_, err = c.completeMultipartUpload(bucketName, objectName, uploadID, complMultipartUpload)
	if err != nil {
		return totalUploadedSize, err
	}

	// Return final size.
	return totalUploadedSize, nil
}

// initiateMultipartUpload - Initiates a multipart upload and returns an upload ID.
func (c Client) initiateMultipartUpload(bucketName, objectName, contentType string) (initiateMultipartUploadResult, error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return initiateMultipartUploadResult{}, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return initiateMultipartUploadResult{}, err
	}

	// Initialize url queries.
	urlValues := make(url.Values)
	urlValues.Set("uploads", "")

	if contentType == "" {
		contentType = "application/octet-stream"
	}

	// Set ContentType header.
	customHeader := make(http.Header)
	customHeader.Set("Content-Type", contentType)

	reqMetadata := requestMetadata{
		bucketName:   bucketName,
		objectName:   objectName,
		queryValues:  urlValues,
		customHeader: customHeader,
	}

	// Execute POST on an objectName to initiate multipart upload.
	resp, err := c.executeMethod("POST", reqMetadata)
	defer closeResponse(resp)
	if err != nil {
		return initiateMultipartUploadResult{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return initiateMultipartUploadResult{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}
	// Decode xml for new multipart upload.
	initiateMultipartUploadResult := initiateMultipartUploadResult{}
	err = xmlDecoder(resp.Body, &initiateMultipartUploadResult)
	if err != nil {
		return initiateMultipartUploadResult, err
	}
	return initiateMultipartUploadResult, nil
}

// uploadPart - Uploads a part in a multipart upload.
func (c Client) uploadPart(bucketName, objectName, uploadID string, reader io.Reader, partNumber int, md5Sum, sha256Sum []byte, size int64) (objectPart, error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return objectPart{}, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return objectPart{}, err
	}
	if size > maxPartSize {
		return objectPart{}, ErrEntityTooLarge(size, maxPartSize, bucketName, objectName)
	}
	if size <= -1 {
		return objectPart{}, ErrEntityTooSmall(size, bucketName, objectName)
	}
	if partNumber <= 0 {
		return objectPart{}, ErrInvalidArgument("Part number cannot be negative or equal to zero.")
	}
	if uploadID == "" {
		return objectPart{}, ErrInvalidArgument("UploadID cannot be empty.")
	}

	// Get resources properly escaped and lined up before using them in http request.
	urlValues := make(url.Values)
	// Set part number.
	urlValues.Set("partNumber", strconv.Itoa(partNumber))
	// Set upload id.
	urlValues.Set("uploadId", uploadID)

	reqMetadata := requestMetadata{
		bucketName:         bucketName,
		objectName:         objectName,
		queryValues:        urlValues,
		contentBody:        reader,
		contentLength:      size,
		contentMD5Bytes:    md5Sum,
		contentSHA256Bytes: sha256Sum,
	}

	// Execute PUT on each part.
	resp, err := c.executeMethod("PUT", reqMetadata)
	defer closeResponse(resp)
	if err != nil {
		return objectPart{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return objectPart{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}
	// Once successfully uploaded, return completed part.
	objPart := objectPart{}
	objPart.Size = size
	objPart.PartNumber = partNumber
	// Trim off the odd double quotes from ETag in the beginning and end.
	objPart.ETag = strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
	objPart.ETag = strings.TrimSuffix(objPart.ETag, "\"")
	return objPart, nil
}

// completeMultipartUpload - Completes a multipart upload by assembling previously uploaded parts.
func (c Client) completeMultipartUpload(bucketName, objectName, uploadID string, complete completeMultipartUpload) (completeMultipartUploadResult, error) {
	// Input validation.
	if err := isValidBucketName(bucketName); err != nil {
		return completeMultipartUploadResult{}, err
	}
	if err := isValidObjectName(objectName); err != nil {
		return completeMultipartUploadResult{}, err
	}

	// Initialize url queries.
	urlValues := make(url.Values)
	urlValues.Set("uploadId", uploadID)

	// Marshal complete multipart body.
	completeMultipartUploadBytes, err := xml.Marshal(complete)
	if err != nil {
		return completeMultipartUploadResult{}, err
	}

	// Instantiate all the complete multipart buffer.
	completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
	reqMetadata := requestMetadata{
		bucketName:         bucketName,
		objectName:         objectName,
		queryValues:        urlValues,
		contentBody:        completeMultipartUploadBuffer,
		contentLength:      int64(len(completeMultipartUploadBytes)),
		contentSHA256Bytes: sum256(completeMultipartUploadBytes),
	}

	// Execute POST to complete multipart upload for an objectName.
	resp, err := c.executeMethod("POST", reqMetadata)
	defer closeResponse(resp)
	if err != nil {
		return completeMultipartUploadResult{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return completeMultipartUploadResult{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}

	// Read resp.Body into a []bytes to parse for Error response inside the body
	var b []byte
	b, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return completeMultipartUploadResult{}, err
	}
	// Decode completed multipart upload response on success.
	completeMultipartUploadResult := completeMultipartUploadResult{}
	err = xmlDecoder(bytes.NewReader(b), &completeMultipartUploadResult)
	if err != nil {
		// xml parsing failure due to presence an ill-formed xml fragment
		return completeMultipartUploadResult, err
	} else if completeMultipartUploadResult.Bucket == "" {
		// xml's Decode method ignores well-formed xml that don't apply to the type of value supplied.
		// In this case, it would leave completeMultipartUploadResult with the corresponding zero-values
		// of the members.

		// Decode completed multipart upload response on failure
		completeMultipartUploadErr := ErrorResponse{}
		err = xmlDecoder(bytes.NewReader(b), &completeMultipartUploadErr)
		if err != nil {
			// xml parsing failure due to presence an ill-formed xml fragment
			return completeMultipartUploadResult, err
		}
		return completeMultipartUploadResult, completeMultipartUploadErr
	}
	return completeMultipartUploadResult, nil
}
<|endoftext|>


<commit_before>/* Copyright (c) 2017 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package evereader

import (
	"github.com/jasonish/evebox/core"
	"github.com/jasonish/evebox/eve"
	"github.com/jasonish/evebox/log"
	"io"
	"sync"
	"time"
)

const BATCH_SIZE = 1000

// EveFileProcessor processes eve files by reading events from reader, applying
// any filters then sending the events to an event sink.
type EveFileProcessor struct {
	Filename          string
	BookmarkDirectory string
	Sink              core.EveEventSink

	filters []eve.EveFilter

	customFields map[string]interface{}

	bookmarker *Bookmarker

	// Total number of events processed.
	count uint64

	// Flag to set to stop processing.
	stop bool

	wg sync.WaitGroup

	// Internal metrics counting.
	lastStatCount uint64
	lastStatTime  time.Time
	eofs          uint64

	// Start reading at end of file if no valid bookmark exists.
	End bool
}

func (p *EveFileProcessor) AddFilter(filter eve.EveFilter) {
	p.filters = append(p.filters, filter)
}

func (p *EveFileProcessor) AddCustomField(field string, value interface{}) {
	if p.customFields == nil {
		p.customFields = make(map[string]interface{})
	}
	p.customFields[field] = value
}

func (p *EveFileProcessor) Start() {
	p.lastStatTime = time.Now()
	p.wg.Add(1)
	go p.Run()
}

func (p *EveFileProcessor) Stop() {
	p.stop = true
	p.wg.Wait()
}

func (p *EveFileProcessor) Run() {
	// Outer loop retries opening the file in case of failures...
	for {
		var bookmarker *Bookmarker

		reader, err := NewFollowingReader(p.Filename)
		if err != nil {
			log.Warning("Failed to open %s (will try again): %v",
				p.Filename, err)
			goto Retry
		}

		bookmarker, err = NewBookmarker(reader, p.BookmarkDirectory, p.End)
		if err != nil {
			log.Warning("Failed to get bookmarker (will try again): %v", err)
			reader.Close()
			goto Retry
		}

		if err := p.process(reader, bookmarker); err != nil {
			log.Error("Processing error, will retry: %v", err)
			goto Retry
		}

	Retry:
		if p.stop {
			break
		}
		time.Sleep(1 * time.Second)
	}

	p.wg.Done()
}

func (p *EveFileProcessor) process(reader *FollowingReader, bookmarker *Bookmarker) error {

	count := uint64(0)

	for {
		eof := false

		event, err := reader.Next()
		if err != nil {
			if err == io.EOF {
				eof = true
				p.eofs++
			} else {
				if _, ok := err.(MalformedEventError); ok {
					log.Error("Maleformed event error: %v", err)
					continue
				}
				return err
			}
		}

		if !eof {
			for _, filter := range p.filters {
				filter.Filter(event)
			}
			p.addCustomFields(event)
			if err := p.Sink.Submit(event); err != nil {
				log.Error("Failed to submit event: %v", err)
				continue
			}
			count++
		}

		// On every EOF or batch size, commit.
		if (eof && count > 0) || (count > 0 && count%BATCH_SIZE == 0) {
			start := time.Now()
			if err := p.commit(); err != nil {
				log.Error("Commit failed: %v", err)
				return err
			}
			log.Debug("Committed %d events in %v", count,
				time.Now().Sub(start))
			bookmarker.UpdateBookmark()
			p.count += count
			count = 0
		}

		// Print stats.
		now := time.Now()
		if now.Sub(p.lastStatTime).Seconds() > 60 {
			log.Info("Total: %d; last minute: %d; EOFs: %d",
				p.count,
				p.count-p.lastStatCount,
				p.eofs)
			p.lastStatCount = p.count
			p.lastStatTime = now
			p.eofs = 0
		}

		if p.stop {
			break
		}

		// If eof, sleep for a moment.
		if eof {
			time.Sleep(1 * time.Second)
		}
	}

	return nil
}

func (p *EveFileProcessor) commit() error {
	for {
		_, err := p.Sink.Commit()
		if err == nil {
			return nil
		}
		if p.stop {
			return err
		}
		log.Error("Failed to commit events, will try again: %v", err)
		time.Sleep(1 * time.Second)
		continue
	}
}

func (p *EveFileProcessor) addCustomFields(event eve.EveEvent) {
	for key := range p.customFields {
		event[key] = p.customFields[key]
	}
}
<commit_msg>Make per minte eve file read stats debug.<commit_after>/* Copyright (c) 2017-2019 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package evereader

import (
	"github.com/jasonish/evebox/core"
	"github.com/jasonish/evebox/eve"
	"github.com/jasonish/evebox/log"
	"io"
	"sync"
	"time"
)

const BATCH_SIZE = 1000

// EveFileProcessor processes eve files by reading events from reader, applying
// any filters then sending the events to an event sink.
type EveFileProcessor struct {
	Filename          string
	BookmarkDirectory string
	Sink              core.EveEventSink

	filters []eve.EveFilter

	customFields map[string]interface{}

	bookmarker *Bookmarker

	// Total number of events processed.
	count uint64

	// Flag to set to stop processing.
	stop bool

	wg sync.WaitGroup

	// Internal metrics counting.
	lastStatCount uint64
	lastStatTime  time.Time
	eofs          uint64
	lastEof       time.Time
	eofAlarm      bool

	// Start reading at end of file if no valid bookmark exists.
	End bool
}

func (p *EveFileProcessor) AddFilter(filter eve.EveFilter) {
	p.filters = append(p.filters, filter)
}

func (p *EveFileProcessor) AddCustomField(field string, value interface{}) {
	if p.customFields == nil {
		p.customFields = make(map[string]interface{})
	}
	p.customFields[field] = value
}

func (p *EveFileProcessor) Start() {
	now := time.Now()
	p.lastStatTime = now
	p.lastEof = now
	p.wg.Add(1)
	go p.Run()
}

func (p *EveFileProcessor) Stop() {
	p.stop = true
	p.wg.Wait()
}

func (p *EveFileProcessor) Run() {
	// Outer loop retries opening the file in case of failures...
	for {
		var bookmarker *Bookmarker

		reader, err := NewFollowingReader(p.Filename)
		if err != nil {
			log.Warning("Failed to open %s (will try again): %v",
				p.Filename, err)
			goto Retry
		}

		bookmarker, err = NewBookmarker(reader, p.BookmarkDirectory, p.End)
		if err != nil {
			log.Warning("Failed to get bookmarker (will try again): %v", err)
			reader.Close()
			goto Retry
		}

		if err := p.process(reader, bookmarker); err != nil {
			log.Error("Processing error, will retry: %v", err)
			goto Retry
		}

	Retry:
		if p.stop {
			break
		}
		time.Sleep(1 * time.Second)
	}

	p.wg.Done()
}

func (p *EveFileProcessor) process(reader *FollowingReader, bookmarker *Bookmarker) error {

	count := uint64(0)

	for {
		eof := false

		event, err := reader.Next()
		if err != nil {
			if err == io.EOF {
				eof = true
				p.eofs++
				p.lastEof = time.Now()
				p.eofAlarm = false
			} else {
				if _, ok := err.(MalformedEventError); ok {
					log.Error("Malformed event error: %v", err)
					continue
				}
				return err
			}
		}

		if !eof {
			for _, filter := range p.filters {
				filter.Filter(event)
			}
			p.addCustomFields(event)
			if err := p.Sink.Submit(event); err != nil {
				log.Error("Failed to submit event: %v", err)
				continue
			}
			count++
		}

		// On every EOF or batch size, commit.
		if (eof && count > 0) || (count > 0 && count%BATCH_SIZE == 0) {
			start := time.Now()
			if err := p.commit(); err != nil {
				log.Error("Commit failed: %v", err)
				return err
			}
			log.Debug("Committed %d events in %v", count,
				time.Now().Sub(start))
			bookmarker.UpdateBookmark()
			p.count += count
			count = 0
		}

		// Print stats.
		now := time.Now()
		if now.Sub(p.lastStatTime).Seconds() > 60 {
			log.Debug("Total: %d; last minute: %d; EOFs: %d",
				p.count,
				p.count-p.lastStatCount,
				p.eofs)
			p.lastStatCount = p.count
			p.lastStatTime = now
			p.eofs = 0
		}

		if !p.eofAlarm && now.Sub(p.lastEof).Seconds() > 60 {
			log.Error("No EOF seen in 60 seconds of log processing. May be overloaded")
			p.eofAlarm = true
		}

		if p.stop {
			break
		}

		// If eof, sleep for a moment.
		if eof {
			time.Sleep(1 * time.Second)
		}
	}

	return nil
}

func (p *EveFileProcessor) commit() error {
	for {
		_, err := p.Sink.Commit()
		if err == nil {
			return nil
		}
		if p.stop {
			return err
		}
		log.Error("Failed to commit events, will try again: %v", err)
		time.Sleep(1 * time.Second)
		continue
	}
}

func (p *EveFileProcessor) addCustomFields(event eve.EveEvent) {
	for key := range p.customFields {
		event[key] = p.customFields[key]
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//This file will be the future home for more policies
package gocql

//RetryableQuery is an interface that represents a query or batch statement that
//exposes the correct functions for the retry policy logic to evaluate correctly.
type RetryableQuery interface {
	Attempts() int
}

// RetryPolicy interace is used by gocql to determine if a query can be attempted
// again after a retryable error has been received. The interface allows gocql
// users to implement their own logic to determine if a query can be attempted
// again.
// See SimpleRetryPolicy as an example of implementing the RetryPolicy interface.
type RetryPolicy interface {
	Attempt(RetryableQuery) bool
}

// SimpleRetryPolicy has simple logic for attempting a query a fixed number of times.
type SimpleRetryPolicy struct {
	NumRetries int //Number of times to retry a query
}

// Attempt tells gocql to attempt the query again based on query.Attempts being less
// than the NumRetries defined in the policy.
func (s *SimpleRetryPolicy) Attempt(q RetryableQuery) bool {
	return q.Attempts() <= s.NumRetries
}
<commit_msg>Updated SimpleRetryPolicy documentation to include usage examples.<commit_after>// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//This file will be the future home for more policies
package gocql

//RetryableQuery is an interface that represents a query or batch statement that
//exposes the correct functions for the retry policy logic to evaluate correctly.
type RetryableQuery interface {
	Attempts() int
}

// RetryPolicy interace is used by gocql to determine if a query can be attempted
// again after a retryable error has been received. The interface allows gocql
// users to implement their own logic to determine if a query can be attempted
// again.
//
// See SimpleRetryPolicy as an example of implementing and using a RetryPolicy
// interface.
type RetryPolicy interface {
	Attempt(RetryableQuery) bool
}

/*
SimpleRetryPolicy has simple logic for attempting a query a fixed number of times.

See below for examples of usage:

	//Assign to the cluster
	cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}

	//Assign to a query
 	query.RetryPolicy(&gocql.SimpleRetryPolicy{NumRetries: 1})
*/
type SimpleRetryPolicy struct {
	NumRetries int //Number of times to retry a query
}

// Attempt tells gocql to attempt the query again based on query.Attempts being less
// than the NumRetries defined in the policy.
func (s *SimpleRetryPolicy) Attempt(q RetryableQuery) bool {
	return q.Attempts() <= s.NumRetries
}
<|endoftext|>


<commit_before>package cloudflare

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/pkg/errors"
)

// AccessGroup defines a group for allowing or disallowing access to
// one or more Access applications.
type AccessGroup struct {
	ID        string     `json:"id,omitempty"`
	CreatedAt *time.Time `json:"created_at"`
	UpdatedAt *time.Time `json:"updated_at"`
	Name      string     `json:"name"`

	// The include group works like an OR logical operator. The user must
	// satisfy one of the rules.
	Include []interface{} `json:"include"`

	// The exclude group works like a NOT logical operator. The user must
	// not satisfy all of the rules in exclude.
	Exclude []interface{} `json:"exclude"`

	// The require group works like a AND logical operator. The user must
	// satisfy all of the rules in require.
	Require []interface{} `json:"require"`
}

// AccessGroupEmail is used for managing access based on the email.
// For example, restrict access to users with the email addresses
// `test@example.com` or `someone@example.com`.
type AccessGroupEmail struct {
	Email struct {
		Email string `json:"email"`
	} `json:"email"`
}

// AccessGroupEmailDomain is used for managing access based on an email
// domain domain such as `example.com` instead of individual addresses.
type AccessGroupEmailDomain struct {
	EmailDomain struct {
		Domain string `json:"domain"`
	} `json:"email_domain"`
}

// AccessGroupIP is used for managing access based in the IP. It
// accepts individual IPs or CIDRs.
type AccessGroupIP struct {
	IP struct {
		IP string `json:"ip"`
	} `json:"ip"`
}

// AccessGroupGeo is used for managing access based on the country code.
type AccessGroupGeo struct {
	Geo struct {
		CountryCode string `json:"country_code"`
	} `json:"geo"`
}

// AccessGroupEveryone is used for managing access to everyone.
type AccessGroupEveryone struct {
	Everyone struct{} `json:"everyone"`
}

// AccessGroupServiceToken is used for managing access based on a specific
// service token.
type AccessGroupServiceToken struct {
	ServiceToken struct {
		ID string `json:"token_id"`
	} `json:"service_token"`
}

// AccessGroupAnyValidServiceToken is used for managing access for all valid
// service tokens (not restricted).
type AccessGroupAnyValidServiceToken struct {
	AnyValidServiceToken struct{} `json:"any_valid_service_token"`
}

// AccessGroupAccessGroup is used for managing access based on an
// access group.
type AccessGroupAccessGroup struct {
	Group struct {
		ID string `json:"id"`
	} `json:"group"`
}

// AccessGroupCertificate is used for managing access to based on a valid
// mTLS certificate being presented.
type AccessGroupCertificate struct {
	Certificate struct{} `json:"certificate"`
}

// AccessGroupCertificateCommonName is used for managing access based on a
// common name within a certificate.
type AccessGroupCertificateCommonName struct {
	CommonName struct {
		CommonName string `json:"common_name"`
	} `json:"common_name"`
}

// AccessGroupGSuite is used to configure access based on GSuite group.
type AccessGroupGSuite struct {
	Gsuite struct {
		Email              string `json:"email"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"gsuite"`
}

// AccessGroupGitHub is used to configure access based on a GitHub organisation.
type AccessGroupGitHub struct {
	GitHubOrganization struct {
		Name               string `json:"name"`
		Team               string `json:"team,omitempty"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"github-organization"`
}

// AccessGroupAzure is used to configure access based on a Azure group.
type AccessGroupAzure struct {
	AzureAD struct {
		ID                 string `json:"id"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"azureAD"`
}

// AccessGroupOkta is used to configure access based on a Okta group.
type AccessGroupOkta struct {
	Okta struct {
		Name               string `json:"name"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"okta"`
}

// AccessGroupSAML is used to allow SAML users with a specific attribute
// configuration.
type AccessGroupSAML struct {
	Saml struct {
		AttributeName      string `json:"attribute_name"`
		AttributeValue     string `json:"attribute_value"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"saml"`
}

// AccessGroupAuthMethod is used for managing access by the "amr"
// (Authentication Methods References) identifier. For example, an
// application may want to require that users authenticate using a hardware
// key by setting the "auth_method" to "swk". A list of values are listed
// here: https://tools.ietf.org/html/rfc8176#section-2. Custom values are
// supported as well.
type AccessGroupAuthMethod struct {
	AuthMethod struct {
		AuthMethod string `json:"auth_method"`
	} `json:"auth_method"`
}

// AccessGroupLoginMethod restricts the application to specific IdP instances.
type AccessGroupLoginMethod struct {
	LoginMethod struct {
		ID string `json:"id"`
	} `json:"login_method"`
}

type AccessGroupDevicePosture struct {
	DevicePosture struct {
		ID string `json:"integration_uid"`
	} `json:"device_posture"`
}

// AccessGroupListResponse represents the response from the list
// access group endpoint.
type AccessGroupListResponse struct {
	Result []AccessGroup `json:"result"`
	Response
	ResultInfo `json:"result_info"`
}

// AccessGroupDetailResponse is the API response, containing a single
// access group.
type AccessGroupDetailResponse struct {
	Success  bool        `json:"success"`
	Errors   []string    `json:"errors"`
	Messages []string    `json:"messages"`
	Result   AccessGroup `json:"result"`
}

// AccessGroups returns all access groups for an access application.
//
// API reference: https://api.cloudflare.com/#access-groups-list-access-groups
func (api *API) AccessGroups(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]AccessGroup, ResultInfo, error) {
	return api.accessGroups(ctx, accountID, pageOpts, AccountRouteRoot)
}

// ZoneLevelAccessGroups returns all zone level access groups for an access application.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-list-access-groups
func (api *API) ZoneLevelAccessGroups(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]AccessGroup, ResultInfo, error) {
	return api.accessGroups(ctx, zoneID, pageOpts, ZoneRouteRoot)
}

func (api *API) accessGroups(ctx context.Context, id string, pageOpts PaginationOptions, routeRoot RouteRoot) ([]AccessGroup, ResultInfo, error) {
	v := url.Values{}
	if pageOpts.PerPage > 0 {
		v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
	}
	if pageOpts.Page > 0 {
		v.Set("page", strconv.Itoa(pageOpts.Page))
	}

	uri := fmt.Sprintf(
		"/%s/%s/access/groups",
		routeRoot,
		id,
	)

	if len(v) > 0 {
		uri = uri + "?" + v.Encode()
	}

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []AccessGroup{}, ResultInfo{}, err
	}

	var accessGroupListResponse AccessGroupListResponse
	err = json.Unmarshal(res, &accessGroupListResponse)
	if err != nil {
		return []AccessGroup{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupListResponse.Result, accessGroupListResponse.ResultInfo, nil
}

// AccessGroup returns a single group based on the group ID.
//
// API reference: https://api.cloudflare.com/#access-groups-access-group-details
func (api *API) AccessGroup(ctx context.Context, accountID, groupID string) (AccessGroup, error) {
	return api.accessGroup(ctx, accountID, groupID, AccountRouteRoot)
}

// ZoneLevelAccessGroup returns a single zone level group based on the group ID.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-access-group-details
func (api *API) ZoneLevelAccessGroup(ctx context.Context, zoneID, groupID string) (AccessGroup, error) {
	return api.accessGroup(ctx, zoneID, groupID, ZoneRouteRoot)
}

func (api *API) accessGroup(ctx context.Context, id, groupID string, routeRoot RouteRoot) (AccessGroup, error) {
	uri := fmt.Sprintf(
		"/%s/%s/access/groups/%s",
		routeRoot,
		id,
		groupID,
	)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return AccessGroup{}, err
	}

	var accessGroupDetailResponse AccessGroupDetailResponse
	err = json.Unmarshal(res, &accessGroupDetailResponse)
	if err != nil {
		return AccessGroup{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupDetailResponse.Result, nil
}

// CreateAccessGroup creates a new access group.
//
// API reference: https://api.cloudflare.com/#access-groups-create-access-group
func (api *API) CreateAccessGroup(ctx context.Context, accountID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.createAccessGroup(ctx, accountID, accessGroup, AccountRouteRoot)
}

// CreateZoneLevelAccessGroup creates a new zone level access group.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-create-access-group
func (api *API) CreateZoneLevelAccessGroup(ctx context.Context, zoneID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.createAccessGroup(ctx, zoneID, accessGroup, ZoneRouteRoot)
}

func (api *API) createAccessGroup(ctx context.Context, id string, accessGroup AccessGroup, routeRoot RouteRoot) (AccessGroup, error) {
	uri := fmt.Sprintf(
		"/%s/%s/access/groups",
		routeRoot,
		id,
	)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessGroup)
	if err != nil {
		return AccessGroup{}, err
	}

	var accessGroupDetailResponse AccessGroupDetailResponse
	err = json.Unmarshal(res, &accessGroupDetailResponse)
	if err != nil {
		return AccessGroup{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupDetailResponse.Result, nil
}

// UpdateAccessGroup updates an existing access group.
//
// API reference: https://api.cloudflare.com/#access-groups-update-access-group
func (api *API) UpdateAccessGroup(ctx context.Context, accountID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.updateAccessGroup(ctx, accountID, accessGroup, AccountRouteRoot)
}

// UpdateZoneLevelAccessGroup updates an existing zone level access group.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-update-access-group
func (api *API) UpdateZoneLevelAccessGroup(ctx context.Context, zoneID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.updateAccessGroup(ctx, zoneID, accessGroup, ZoneRouteRoot)
}

func (api *API) updateAccessGroup(ctx context.Context, id string, accessGroup AccessGroup, routeRoot RouteRoot) (AccessGroup, error) {
	if accessGroup.ID == "" {
		return AccessGroup{}, errors.Errorf("access group ID cannot be empty")
	}
	uri := fmt.Sprintf(
		"/%s/%s/access/groups/%s",
		routeRoot,
		id,
		accessGroup.ID,
	)

	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, accessGroup)
	if err != nil {
		return AccessGroup{}, err
	}

	var accessGroupDetailResponse AccessGroupDetailResponse
	err = json.Unmarshal(res, &accessGroupDetailResponse)
	if err != nil {
		return AccessGroup{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupDetailResponse.Result, nil
}

// DeleteAccessGroup deletes an access group.
//
// API reference: https://api.cloudflare.com/#access-groups-delete-access-group
func (api *API) DeleteAccessGroup(ctx context.Context, accountID, groupID string) error {
	return api.deleteAccessGroup(ctx, accountID, groupID, AccountRouteRoot)
}

// DeleteZoneLevelAccessGroup deletes a zone level access group.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-delete-access-group
func (api *API) DeleteZoneLevelAccessGroup(ctx context.Context, zoneID, groupID string) error {
	return api.deleteAccessGroup(ctx, zoneID, groupID, ZoneRouteRoot)
}

func (api *API) deleteAccessGroup(ctx context.Context, id string, groupID string, routeRoot RouteRoot) error {
	uri := fmt.Sprintf(
		"/%s/%s/access/groups/%s",
		routeRoot,
		id,
		groupID,
	)

	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>comment for lint<commit_after>package cloudflare

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/pkg/errors"
)

// AccessGroup defines a group for allowing or disallowing access to
// one or more Access applications.
type AccessGroup struct {
	ID        string     `json:"id,omitempty"`
	CreatedAt *time.Time `json:"created_at"`
	UpdatedAt *time.Time `json:"updated_at"`
	Name      string     `json:"name"`

	// The include group works like an OR logical operator. The user must
	// satisfy one of the rules.
	Include []interface{} `json:"include"`

	// The exclude group works like a NOT logical operator. The user must
	// not satisfy all of the rules in exclude.
	Exclude []interface{} `json:"exclude"`

	// The require group works like a AND logical operator. The user must
	// satisfy all of the rules in require.
	Require []interface{} `json:"require"`
}

// AccessGroupEmail is used for managing access based on the email.
// For example, restrict access to users with the email addresses
// `test@example.com` or `someone@example.com`.
type AccessGroupEmail struct {
	Email struct {
		Email string `json:"email"`
	} `json:"email"`
}

// AccessGroupEmailDomain is used for managing access based on an email
// domain domain such as `example.com` instead of individual addresses.
type AccessGroupEmailDomain struct {
	EmailDomain struct {
		Domain string `json:"domain"`
	} `json:"email_domain"`
}

// AccessGroupIP is used for managing access based in the IP. It
// accepts individual IPs or CIDRs.
type AccessGroupIP struct {
	IP struct {
		IP string `json:"ip"`
	} `json:"ip"`
}

// AccessGroupGeo is used for managing access based on the country code.
type AccessGroupGeo struct {
	Geo struct {
		CountryCode string `json:"country_code"`
	} `json:"geo"`
}

// AccessGroupEveryone is used for managing access to everyone.
type AccessGroupEveryone struct {
	Everyone struct{} `json:"everyone"`
}

// AccessGroupServiceToken is used for managing access based on a specific
// service token.
type AccessGroupServiceToken struct {
	ServiceToken struct {
		ID string `json:"token_id"`
	} `json:"service_token"`
}

// AccessGroupAnyValidServiceToken is used for managing access for all valid
// service tokens (not restricted).
type AccessGroupAnyValidServiceToken struct {
	AnyValidServiceToken struct{} `json:"any_valid_service_token"`
}

// AccessGroupAccessGroup is used for managing access based on an
// access group.
type AccessGroupAccessGroup struct {
	Group struct {
		ID string `json:"id"`
	} `json:"group"`
}

// AccessGroupCertificate is used for managing access to based on a valid
// mTLS certificate being presented.
type AccessGroupCertificate struct {
	Certificate struct{} `json:"certificate"`
}

// AccessGroupCertificateCommonName is used for managing access based on a
// common name within a certificate.
type AccessGroupCertificateCommonName struct {
	CommonName struct {
		CommonName string `json:"common_name"`
	} `json:"common_name"`
}

// AccessGroupGSuite is used to configure access based on GSuite group.
type AccessGroupGSuite struct {
	Gsuite struct {
		Email              string `json:"email"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"gsuite"`
}

// AccessGroupGitHub is used to configure access based on a GitHub organisation.
type AccessGroupGitHub struct {
	GitHubOrganization struct {
		Name               string `json:"name"`
		Team               string `json:"team,omitempty"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"github-organization"`
}

// AccessGroupAzure is used to configure access based on a Azure group.
type AccessGroupAzure struct {
	AzureAD struct {
		ID                 string `json:"id"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"azureAD"`
}

// AccessGroupOkta is used to configure access based on a Okta group.
type AccessGroupOkta struct {
	Okta struct {
		Name               string `json:"name"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"okta"`
}

// AccessGroupSAML is used to allow SAML users with a specific attribute
// configuration.
type AccessGroupSAML struct {
	Saml struct {
		AttributeName      string `json:"attribute_name"`
		AttributeValue     string `json:"attribute_value"`
		IdentityProviderID string `json:"identity_provider_id"`
	} `json:"saml"`
}

// AccessGroupAuthMethod is used for managing access by the "amr"
// (Authentication Methods References) identifier. For example, an
// application may want to require that users authenticate using a hardware
// key by setting the "auth_method" to "swk". A list of values are listed
// here: https://tools.ietf.org/html/rfc8176#section-2. Custom values are
// supported as well.
type AccessGroupAuthMethod struct {
	AuthMethod struct {
		AuthMethod string `json:"auth_method"`
	} `json:"auth_method"`
}

// AccessGroupLoginMethod restricts the application to specific IdP instances.
type AccessGroupLoginMethod struct {
	LoginMethod struct {
		ID string `json:"id"`
	} `json:"login_method"`
}

// AccessGroupDevicePosture restricts the application to specific devices
type AccessGroupDevicePosture struct {
	DevicePosture struct {
		ID string `json:"integration_uid"`
	} `json:"device_posture"`
}

// AccessGroupListResponse represents the response from the list
// access group endpoint.
type AccessGroupListResponse struct {
	Result []AccessGroup `json:"result"`
	Response
	ResultInfo `json:"result_info"`
}

// AccessGroupDetailResponse is the API response, containing a single
// access group.
type AccessGroupDetailResponse struct {
	Success  bool        `json:"success"`
	Errors   []string    `json:"errors"`
	Messages []string    `json:"messages"`
	Result   AccessGroup `json:"result"`
}

// AccessGroups returns all access groups for an access application.
//
// API reference: https://api.cloudflare.com/#access-groups-list-access-groups
func (api *API) AccessGroups(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]AccessGroup, ResultInfo, error) {
	return api.accessGroups(ctx, accountID, pageOpts, AccountRouteRoot)
}

// ZoneLevelAccessGroups returns all zone level access groups for an access application.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-list-access-groups
func (api *API) ZoneLevelAccessGroups(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]AccessGroup, ResultInfo, error) {
	return api.accessGroups(ctx, zoneID, pageOpts, ZoneRouteRoot)
}

func (api *API) accessGroups(ctx context.Context, id string, pageOpts PaginationOptions, routeRoot RouteRoot) ([]AccessGroup, ResultInfo, error) {
	v := url.Values{}
	if pageOpts.PerPage > 0 {
		v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
	}
	if pageOpts.Page > 0 {
		v.Set("page", strconv.Itoa(pageOpts.Page))
	}

	uri := fmt.Sprintf(
		"/%s/%s/access/groups",
		routeRoot,
		id,
	)

	if len(v) > 0 {
		uri = uri + "?" + v.Encode()
	}

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []AccessGroup{}, ResultInfo{}, err
	}

	var accessGroupListResponse AccessGroupListResponse
	err = json.Unmarshal(res, &accessGroupListResponse)
	if err != nil {
		return []AccessGroup{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupListResponse.Result, accessGroupListResponse.ResultInfo, nil
}

// AccessGroup returns a single group based on the group ID.
//
// API reference: https://api.cloudflare.com/#access-groups-access-group-details
func (api *API) AccessGroup(ctx context.Context, accountID, groupID string) (AccessGroup, error) {
	return api.accessGroup(ctx, accountID, groupID, AccountRouteRoot)
}

// ZoneLevelAccessGroup returns a single zone level group based on the group ID.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-access-group-details
func (api *API) ZoneLevelAccessGroup(ctx context.Context, zoneID, groupID string) (AccessGroup, error) {
	return api.accessGroup(ctx, zoneID, groupID, ZoneRouteRoot)
}

func (api *API) accessGroup(ctx context.Context, id, groupID string, routeRoot RouteRoot) (AccessGroup, error) {
	uri := fmt.Sprintf(
		"/%s/%s/access/groups/%s",
		routeRoot,
		id,
		groupID,
	)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return AccessGroup{}, err
	}

	var accessGroupDetailResponse AccessGroupDetailResponse
	err = json.Unmarshal(res, &accessGroupDetailResponse)
	if err != nil {
		return AccessGroup{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupDetailResponse.Result, nil
}

// CreateAccessGroup creates a new access group.
//
// API reference: https://api.cloudflare.com/#access-groups-create-access-group
func (api *API) CreateAccessGroup(ctx context.Context, accountID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.createAccessGroup(ctx, accountID, accessGroup, AccountRouteRoot)
}

// CreateZoneLevelAccessGroup creates a new zone level access group.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-create-access-group
func (api *API) CreateZoneLevelAccessGroup(ctx context.Context, zoneID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.createAccessGroup(ctx, zoneID, accessGroup, ZoneRouteRoot)
}

func (api *API) createAccessGroup(ctx context.Context, id string, accessGroup AccessGroup, routeRoot RouteRoot) (AccessGroup, error) {
	uri := fmt.Sprintf(
		"/%s/%s/access/groups",
		routeRoot,
		id,
	)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessGroup)
	if err != nil {
		return AccessGroup{}, err
	}

	var accessGroupDetailResponse AccessGroupDetailResponse
	err = json.Unmarshal(res, &accessGroupDetailResponse)
	if err != nil {
		return AccessGroup{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupDetailResponse.Result, nil
}

// UpdateAccessGroup updates an existing access group.
//
// API reference: https://api.cloudflare.com/#access-groups-update-access-group
func (api *API) UpdateAccessGroup(ctx context.Context, accountID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.updateAccessGroup(ctx, accountID, accessGroup, AccountRouteRoot)
}

// UpdateZoneLevelAccessGroup updates an existing zone level access group.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-update-access-group
func (api *API) UpdateZoneLevelAccessGroup(ctx context.Context, zoneID string, accessGroup AccessGroup) (AccessGroup, error) {
	return api.updateAccessGroup(ctx, zoneID, accessGroup, ZoneRouteRoot)
}

func (api *API) updateAccessGroup(ctx context.Context, id string, accessGroup AccessGroup, routeRoot RouteRoot) (AccessGroup, error) {
	if accessGroup.ID == "" {
		return AccessGroup{}, errors.Errorf("access group ID cannot be empty")
	}
	uri := fmt.Sprintf(
		"/%s/%s/access/groups/%s",
		routeRoot,
		id,
		accessGroup.ID,
	)

	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, accessGroup)
	if err != nil {
		return AccessGroup{}, err
	}

	var accessGroupDetailResponse AccessGroupDetailResponse
	err = json.Unmarshal(res, &accessGroupDetailResponse)
	if err != nil {
		return AccessGroup{}, errors.Wrap(err, errUnmarshalError)
	}

	return accessGroupDetailResponse.Result, nil
}

// DeleteAccessGroup deletes an access group.
//
// API reference: https://api.cloudflare.com/#access-groups-delete-access-group
func (api *API) DeleteAccessGroup(ctx context.Context, accountID, groupID string) error {
	return api.deleteAccessGroup(ctx, accountID, groupID, AccountRouteRoot)
}

// DeleteZoneLevelAccessGroup deletes a zone level access group.
//
// API reference: https://api.cloudflare.com/#zone-level-access-groups-delete-access-group
func (api *API) DeleteZoneLevelAccessGroup(ctx context.Context, zoneID, groupID string) error {
	return api.deleteAccessGroup(ctx, zoneID, groupID, ZoneRouteRoot)
}

func (api *API) deleteAccessGroup(ctx context.Context, id string, groupID string, routeRoot RouteRoot) error {
	uri := fmt.Sprintf(
		"/%s/%s/access/groups/%s",
		routeRoot,
		id,
		groupID,
	)

	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package script

import (
	"image"
	"image/draw"

	"github.com/anthonynsimon/bild/blur"
	"github.com/anthonynsimon/bild/effect"
	"github.com/anthonynsimon/bild/transform"
	"github.com/yuin/gopher-lua"
)

type scriptCtx struct {
	exports map[string]lua.LGFunction
	rgba    *image.RGBA
}

func newScriptCtx(rgba *image.RGBA) *scriptCtx {
	c := scriptCtx{
		exports: make(map[string]lua.LGFunction),
		rgba:    rgba,
	}

	c.exports["grayscale"] = c.luaGrayscale
	c.exports["boxBlur"] = c.luaBoxBlur
	c.exports["gaussianBlue"] = c.luaGaussianBlur
	c.exports["edgeDetection"] = c.luaEdgeDetection
	c.exports["emboss"] = c.luaEmboss
	c.exports["invert"] = c.luaInvert
	c.exports["flipH"] = c.luaFlipH
	c.exports["flipV"] = c.luaFlipV
	c.exports["rotate"] = c.luaRotate
	c.exports["resize"] = c.luaResize
	c.exports["crop"] = c.luaCrop

	c.exports["getInputImage"] = c.luaGetImage
	c.exports["setOutputImage"] = c.luaSetImage

	return &c
}

func (sc *scriptCtx) load(L *lua.LState) int {
	mod := L.SetFuncs(L.NewTable(), sc.exports)
	L.Push(mod)
	return 1
}

func (sc *scriptCtx) luaGetImage(L *lua.LState) int {
	ud := L.NewUserData()
	ud.Value = sc.rgba
	L.Push(ud)
	return 1
}

func (sc *scriptCtx) luaSetImage(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		sc.rgba = data.Value.(*image.RGBA)
	}
	return 1
}

func (sc *scriptCtx) luaGrayscale(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		src := effect.Grayscale(data.Value.(image.Image))

		bounds := src.Bounds()
		img := image.NewRGBA(bounds)
		draw.Draw(img, bounds, src, bounds.Min, draw.Src)

		data.Value = img
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaGaussianBlur(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		radius := float64(L.Get(2).(lua.LNumber))
		data.Value = blur.Gaussian(data.Value.(image.Image), radius)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaBoxBlur(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		radius := float64(L.Get(2).(lua.LNumber))
		data.Value = blur.Box(data.Value.(image.Image), radius)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaEdgeDetection(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		radius := float64(L.Get(2).(lua.LNumber))
		data.Value = effect.EdgeDetection(data.Value.(image.Image), radius)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaEmboss(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = effect.Emboss(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaInvert(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = effect.Invert(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaFlipH(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = transform.FlipH(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaFlipV(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = transform.FlipV(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaRotate(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		val := float64(L.Get(2).(lua.LNumber))
		data.Value = transform.Rotate(data.Value.(image.Image), val, nil)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaResize(L *lua.LState) int {
	if L.GetTop() == 3 {
		data := L.Get(1).(*lua.LUserData)
		w := int(L.Get(2).(lua.LNumber))
		h := int(L.Get(3).(lua.LNumber))
		data.Value = transform.Resize(data.Value.(image.Image), w, h, transform.Linear)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaCrop(L *lua.LState) int {
	if L.GetTop() == 5 {
		data := L.Get(1).(*lua.LUserData)
		x1 := int(L.Get(2).(lua.LNumber))
		y1 := int(L.Get(3).(lua.LNumber))
		x2 := int(L.Get(4).(lua.LNumber))
		y2 := int(L.Get(5).(lua.LNumber))
		data.Value = transform.Crop(data.Value.(image.Image), image.Rect(x1, y1, x2, y2))
		L.Push(data)
	}
	return 1
}
<commit_msg>Update module.go<commit_after>package script

import (
	"image"
	"image/draw"

	"github.com/anthonynsimon/bild/blur"
	"github.com/anthonynsimon/bild/effect"
	"github.com/anthonynsimon/bild/transform"
	"github.com/yuin/gopher-lua"
)

type scriptCtx struct {
	exports map[string]lua.LGFunction
	rgba    *image.RGBA
}

func newScriptCtx(rgba *image.RGBA) *scriptCtx {
	c := scriptCtx{
		exports: make(map[string]lua.LGFunction),
		rgba:    rgba,
	}

	c.exports["grayscale"] = c.luaGrayscale
	c.exports["boxBlur"] = c.luaBoxBlur
	c.exports["gaussianBlur"] = c.luaGaussianBlur
	c.exports["edgeDetection"] = c.luaEdgeDetection
	c.exports["emboss"] = c.luaEmboss
	c.exports["invert"] = c.luaInvert
	c.exports["flipH"] = c.luaFlipH
	c.exports["flipV"] = c.luaFlipV
	c.exports["rotate"] = c.luaRotate
	c.exports["resize"] = c.luaResize
	c.exports["crop"] = c.luaCrop

	c.exports["getInputImage"] = c.luaGetImage
	c.exports["setOutputImage"] = c.luaSetImage

	return &c
}

func (sc *scriptCtx) load(L *lua.LState) int {
	mod := L.SetFuncs(L.NewTable(), sc.exports)
	L.Push(mod)
	return 1
}

func (sc *scriptCtx) luaGetImage(L *lua.LState) int {
	ud := L.NewUserData()
	ud.Value = sc.rgba
	L.Push(ud)
	return 1
}

func (sc *scriptCtx) luaSetImage(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		sc.rgba = data.Value.(*image.RGBA)
	}
	return 1
}

func (sc *scriptCtx) luaGrayscale(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		src := effect.Grayscale(data.Value.(image.Image))

		bounds := src.Bounds()
		img := image.NewRGBA(bounds)
		draw.Draw(img, bounds, src, bounds.Min, draw.Src)

		data.Value = img
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaGaussianBlur(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		radius := float64(L.Get(2).(lua.LNumber))
		data.Value = blur.Gaussian(data.Value.(image.Image), radius)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaBoxBlur(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		radius := float64(L.Get(2).(lua.LNumber))
		data.Value = blur.Box(data.Value.(image.Image), radius)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaEdgeDetection(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		radius := float64(L.Get(2).(lua.LNumber))
		data.Value = effect.EdgeDetection(data.Value.(image.Image), radius)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaEmboss(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = effect.Emboss(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaInvert(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = effect.Invert(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaFlipH(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = transform.FlipH(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaFlipV(L *lua.LState) int {
	if L.GetTop() == 1 {
		data := L.Get(1).(*lua.LUserData)
		data.Value = transform.FlipV(data.Value.(image.Image))
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaRotate(L *lua.LState) int {
	if L.GetTop() == 2 {
		data := L.Get(1).(*lua.LUserData)
		val := float64(L.Get(2).(lua.LNumber))
		data.Value = transform.Rotate(data.Value.(image.Image), val, nil)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaResize(L *lua.LState) int {
	if L.GetTop() == 3 {
		data := L.Get(1).(*lua.LUserData)
		w := int(L.Get(2).(lua.LNumber))
		h := int(L.Get(3).(lua.LNumber))
		data.Value = transform.Resize(data.Value.(image.Image), w, h, transform.Linear)
		L.Push(data)
	}
	return 1
}

func (sc *scriptCtx) luaCrop(L *lua.LState) int {
	if L.GetTop() == 5 {
		data := L.Get(1).(*lua.LUserData)
		x1 := int(L.Get(2).(lua.LNumber))
		y1 := int(L.Get(3).(lua.LNumber))
		x2 := int(L.Get(4).(lua.LNumber))
		y2 := int(L.Get(5).(lua.LNumber))
		data.Value = transform.Crop(data.Value.(image.Image), image.Rect(x1, y1, x2, y2))
		L.Push(data)
	}
	return 1
}
<|endoftext|>