<commit_before>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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 main

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

var customerIndexStr = "_customerindex" //name for the key/value that will store a list of all known customers
var openTradesStr = "_opentrades"     //name for the key/value that will store all open trades

type Customer struct {
	Name        string   `json:"name"` //the fieldtags are needed to keep case from bouncing around
	TelNo       string   `json:"telno"`
	Age         int      `json:"age"`
	Ocupation   string   `json:"ocupation"`
	AllowBroke  []Broker `json:"allowbroke"`
	GauranteeID string   `json:"gauranteeid"`
}

type Broker struct {
	Name     string `json:"name"`
	BrokerNo int    `json:"brokerno"`
}

// ============================================================================================================================
// Main
// ============================================================================================================================
func main() {
	err := shim.Start(new(SimpleChaincode))
	if err != nil {
		fmt.Printf("Error starting Simple chaincode: %s", err)
	}
}

// ============================================================================================================================
// Init - reset all the things
// ============================================================================================================================
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	var Aval int
	var err error

	if len(args) != 1 {
		return nil, errors.New("Incorrect number of arguments. Expecting 1")
	}

	// Initialize the chaincode
	Aval, err = strconv.Atoi(args[0])
	if err != nil {
		return nil, errors.New("Expecting integer value for asset holding")
	}

	// Write the state to the ledger
	err = stub.PutState("kyc", []byte(strconv.Itoa(Aval))) //making a test var "kyc", I find it handy to read/write to it right away to test the network
	if err != nil {
		return nil, err
	}

	var empty []string
	jsonAsBytes, _ := json.Marshal(empty) //marshal an emtpy array of strings to clear the index
	err = stub.PutState(customerIndexStr, jsonAsBytes)
	if err != nil {
		return nil, err
	}

	/*var trades AllTrades
	jsonAsBytes, _ = json.Marshal(trades) //clear the open trade struct
	err = stub.PutState(openTradesStr, jsonAsBytes)
	if err != nil {
		return nil, err
	}
	*/
	return nil, nil
}

// ============================================================================================================================
// Run - Our entry point for Invocations - [LEGACY] obc-peer 4/25/2016
// ============================================================================================================================
func (t *SimpleChaincode) Run(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	fmt.Println("run is running " + function)
	return t.Invoke(stub, function, args)
}

// ============================================================================================================================
// Invoke - Our entry point for Invocations
// ============================================================================================================================
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	fmt.Println("invoke is running " + function)

	// Handle different functions
	if function == "init" { //initialize the chaincode state, used as reset
		return t.Init(stub, "init", args)
		/*} else if function == "delete" { //deletes an entity from its state
		res, err := t.Delete(stub, args)
		cleanTrades(stub) //lets make sure all open trades are still valid
		return res, err*/
	} else if function == "write" { //writes a value to the chaincode state
		return t.Write(stub, args)
	} else if function == "new_customer" { //create a new customer
		return t.new_customer(stub, args)
	} else if function == "set_user" { //change owner of a customer
		res, err := t.set_user(stub, args)
		//cleanTrades(stub) //lets make sure all open trades are still valid
		return res, err
	} else if function == "open_trade" { //create a new trade order
		//return t.open_trade(stub, args)
	} else if function == "perform_trade" { //forfill an open trade order
		// res, err := t.perform_trade(stub, args)
		// cleanTrades(stub) //lets clean just in case
		// return res, err
	} else if function == "remove_trade" { //cancel an open trade order
		// return t.remove_trade(stub, args)
	}
	fmt.Println("invoke did not find func: " + function) //error

	return nil, errors.New("Received unknown function invocation")
}

// ============================================================================================================================
// Query - Our entry point for Queries
// ============================================================================================================================
func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	fmt.Println("query is running " + function)

	// Handle different functions
	if function == "read" { //read a variable
		return t.read(stub, args)
	}
	fmt.Println("query did not find func: " + function) //error

	return nil, errors.New("Received unknown function query")
}

// ============================================================================================================================
// Read - read a variable from chaincode state
// ============================================================================================================================
func (t *SimpleChaincode) read(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var name, jsonResp string
	var err error

	if len(args) != 1 {
		return nil, errors.New("Incorrect number of arguments. Expecting name of the var to query")
	}

	name = args[0]
	valAsbytes, err := stub.GetState(name) //get the var from chaincode state
	if err != nil {
		jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}"
		return nil, errors.New(jsonResp)
	}

	return valAsbytes, nil //send it onward
}

// ============================================================================================================================
// Write - write variable into chaincode state
// ============================================================================================================================
func (t *SimpleChaincode) Write(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var name, value string // Entities
	var err error
	fmt.Println("running write()")

	if len(args) != 2 {
		return nil, errors.New("Incorrect number of arguments. Expecting 2. name of the variable and value to set")
	}

	name = args[0] //rename for funsies
	value = args[1]
	err = stub.PutState(name, []byte(value)) //write the variable into the chaincode state
	if err != nil {
		return nil, err
	}
	return nil, nil
}

// ============================================================================================================================
// Init Customer - create a new customer, store into chaincode state
// ============================================================================================================================
func (t *SimpleChaincode) new_customer(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var err error

	//   0       1       2     3
	// "asdf", "blue", "35", "bob"
	if len(args) != 4 {
		return nil, errors.New("Incorrect number of arguments. Expecting 4")
	}

	//input sanitation
	fmt.Println("- start init customer")
	if len(args[0]) <= 0 {
		return nil, errors.New("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return nil, errors.New("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return nil, errors.New("3rd argument must be a non-empty string")
	}
	if len(args[3]) <= 0 {
		return nil, errors.New("4th argument must be a non-empty string")
	}
	name := args[0]
	telno := strings.ToLower(args[1])
	size, err := strconv.Atoi(args[2])
	if err != nil {
		return nil, errors.New("3rd argument must be a numeric string")
	}
	ocupation := strings.ToLower(args[3])

	//check if customer already exists
	customerAsBytes, err := stub.GetState(name)
	if err != nil {
		return nil, errors.New("Failed to get customer name")
	}
	res := Customer{}
	json.Unmarshal(customerAsBytes, &res)
	if res.Name == name {
		fmt.Println("This customer arleady exists: " + name)
		fmt.Println(res)
		return nil, errors.New("This customer arleady exists") //all stop a customer by this name exists
	}

	res.Name = name
	res.TelNo = telno
	res.Age = size
	res.Ocupation = ocupation
	//build the customer json string manually
	//str := `{"name": "` + name + `", "telno": "` + telno + `", "size": ` + strconv.Itoa(size) + `, "user": "` + user + `"}`
	//err = stub.PutState(name, []byte(str)) //store customer with id as key
	str, err := json.Marshal(res)
	err = stub.PutState(name, str)
	if err != nil {
		return nil, err
	}

	//get the customer index
	customersAsBytes, err := stub.GetState(customerIndexStr)
	if err != nil {
		return nil, errors.New("Failed to get customer index")
	}
	var customerIndex []string
	json.Unmarshal(customersAsBytes, &customerIndex) //un stringify it aka JSON.parse()

	//append
	customerIndex = append(customerIndex, name) //add customer name to index list
	fmt.Println("! customer index: ", customerIndex)
	jsonAsBytes, _ := json.Marshal(customerIndex)
	err = stub.PutState(customerIndexStr, jsonAsBytes) //store name of customer

	fmt.Println("- end init customer")
	return nil, nil
}

// ============================================================================================================================
// Set User Permission on Customer
// ============================================================================================================================
func (t *SimpleChaincode) set_user(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var err error

	//   0       1
	// "name", "bob"
	if len(args) < 2 {
		return nil, errors.New("Incorrect number of arguments. Expecting 2")
	}

	fmt.Println("- start set user")
	fmt.Println(args[0] + " - " + args[1])
	customerAsBytes, err := stub.GetState(args[0])
	if err != nil {
		return nil, errors.New("Failed to get thing")
	}
	res := Customer{}
	json.Unmarshal(customerAsBytes, &res) //un stringify it aka JSON.parse()
	//res.User = args[1]                  //change the user

	jsonAsBytes, _ := json.Marshal(res)
	err = stub.PutState(args[0], jsonAsBytes) //rewrite the customer with id as key
	if err != nil {
		return nil, err
	}

	fmt.Println("- end set user")
	return nil, nil
}
<commit_msg>rename ocupation to occupation<commit_after>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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 main

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

var customerIndexStr = "_customerindex" //name for the key/value that will store a list of all known customers
var openTradesStr = "_opentrades"       //name for the key/value that will store all open trades

type Customer struct {
	Name        string   `json:"name"` //the fieldtags are needed to keep case from bouncing around
	TelNo       string   `json:"telno"`
	Age         int      `json:"age"`
	Occupation  string   `json:"occupation"`
	AllowBroke  []Broker `json:"allowbroke"`
	GauranteeID string   `json:"gauranteeid"`
}

type Broker struct {
	Name          string     `json:"name"`
	BrokerNo      int        `json:"brokerno"`
	AllowCustomer []Customer `json:"allowcustomer"`
}

// ============================================================================================================================
// Main
// ============================================================================================================================
func main() {
	err := shim.Start(new(SimpleChaincode))
	if err != nil {
		fmt.Printf("Error starting Simple chaincode: %s", err)
	}
}

// ============================================================================================================================
// Init - reset all the things
// ============================================================================================================================
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	var Aval int
	var err error

	if len(args) != 1 {
		return nil, errors.New("Incorrect number of arguments. Expecting 1")
	}

	// Initialize the chaincode
	Aval, err = strconv.Atoi(args[0])
	if err != nil {
		return nil, errors.New("Expecting integer value for asset holding")
	}

	// Write the state to the ledger
	err = stub.PutState("kyc", []byte(strconv.Itoa(Aval))) //making a test var "kyc", I find it handy to read/write to it right away to test the network
	if err != nil {
		return nil, err
	}

	var empty []string
	jsonAsBytes, _ := json.Marshal(empty) //marshal an emtpy array of strings to clear the index
	err = stub.PutState(customerIndexStr, jsonAsBytes)
	if err != nil {
		return nil, err
	}

	/*var trades AllTrades
	jsonAsBytes, _ = json.Marshal(trades) //clear the open trade struct
	err = stub.PutState(openTradesStr, jsonAsBytes)
	if err != nil {
		return nil, err
	}
	*/
	return nil, nil
}

// ============================================================================================================================
// Run - Our entry point for Invocations - [LEGACY] obc-peer 4/25/2016
// ============================================================================================================================
func (t *SimpleChaincode) Run(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	fmt.Println("run is running " + function)
	return t.Invoke(stub, function, args)
}

// ============================================================================================================================
// Invoke - Our entry point for Invocations
// ============================================================================================================================
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	fmt.Println("invoke is running " + function)

	// Handle different functions
	if function == "init" { //initialize the chaincode state, used as reset
		return t.Init(stub, "init", args)
		/*} else if function == "delete" { //deletes an entity from its state
		res, err := t.Delete(stub, args)
		cleanTrades(stub) //lets make sure all open trades are still valid
		return res, err*/
	} else if function == "write" { //writes a value to the chaincode state
		return t.Write(stub, args)
	} else if function == "new_customer" { //create a new customer
		return t.new_customer(stub, args)
	} else if function == "set_user" { //change owner of a customer
		res, err := t.set_user(stub, args)
		//cleanTrades(stub) //lets make sure all open trades are still valid
		return res, err
	} else if function == "open_trade" { //create a new trade order
		//return t.open_trade(stub, args)
	} else if function == "perform_trade" { //forfill an open trade order
		// res, err := t.perform_trade(stub, args)
		// cleanTrades(stub) //lets clean just in case
		// return res, err
	} else if function == "remove_trade" { //cancel an open trade order
		// return t.remove_trade(stub, args)
	}
	fmt.Println("invoke did not find func: " + function) //error

	return nil, errors.New("Received unknown function invocation")
}

// ============================================================================================================================
// Query - Our entry point for Queries
// ============================================================================================================================
func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	fmt.Println("query is running " + function)

	// Handle different functions
	if function == "read" { //read a variable
		return t.read(stub, args)
	}
	fmt.Println("query did not find func: " + function) //error

	return nil, errors.New("Received unknown function query")
}

// ============================================================================================================================
// Read - read a variable from chaincode state
// ============================================================================================================================
func (t *SimpleChaincode) read(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var name, jsonResp string
	var err error

	if len(args) != 1 {
		return nil, errors.New("Incorrect number of arguments. Expecting name of the var to query")
	}

	name = args[0]
	valAsbytes, err := stub.GetState(name) //get the var from chaincode state
	if err != nil {
		jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}"
		return nil, errors.New(jsonResp)
	}

	return valAsbytes, nil //send it onward
}

// ============================================================================================================================
// Write - write variable into chaincode state
// ============================================================================================================================
func (t *SimpleChaincode) Write(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var name, value string // Entities
	var err error
	fmt.Println("running write()")

	if len(args) != 2 {
		return nil, errors.New("Incorrect number of arguments. Expecting 2. name of the variable and value to set")
	}

	name = args[0] //rename for funsies
	value = args[1]
	err = stub.PutState(name, []byte(value)) //write the variable into the chaincode state
	if err != nil {
		return nil, err
	}
	return nil, nil
}

// ============================================================================================================================
// Init Customer - create a new customer, store into chaincode state
// ============================================================================================================================
func (t *SimpleChaincode) new_customer(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var err error

	//   0       1       2     3
	// "asdf", "blue", "35", "bob"
	if len(args) != 4 {
		return nil, errors.New("Incorrect number of arguments. Expecting 4")
	}

	//input sanitation
	fmt.Println("- start init customer")
	if len(args[0]) <= 0 {
		return nil, errors.New("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return nil, errors.New("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return nil, errors.New("3rd argument must be a non-empty string")
	}
	if len(args[3]) <= 0 {
		return nil, errors.New("4th argument must be a non-empty string")
	}
	name := args[0]
	telno := strings.ToLower(args[1])
	size, err := strconv.Atoi(args[2])
	if err != nil {
		return nil, errors.New("3rd argument must be a numeric string")
	}
	occupation := strings.ToLower(args[3])

	//check if customer already exists
	customerAsBytes, err := stub.GetState(name)
	if err != nil {
		return nil, errors.New("Failed to get customer name")
	}
	res := Customer{}
	json.Unmarshal(customerAsBytes, &res)
	if res.Name == name {
		fmt.Println("This customer arleady exists: " + name)
		fmt.Println(res)
		return nil, errors.New("This customer arleady exists") //all stop a customer by this name exists
	}

	res.Name = name
	res.TelNo = telno
	res.Age = size
	res.Occupation = occupation
	//build the customer json string manually
	//str := `{"name": "` + name + `", "telno": "` + telno + `", "size": ` + strconv.Itoa(size) + `, "user": "` + user + `"}`
	//err = stub.PutState(name, []byte(str)) //store customer with id as key
	str, err := json.Marshal(res)
	err = stub.PutState(name, str)
	if err != nil {
		return nil, err
	}

	//get the customer index
	customersAsBytes, err := stub.GetState(customerIndexStr)
	if err != nil {
		return nil, errors.New("Failed to get customer index")
	}
	var customerIndex []string
	json.Unmarshal(customersAsBytes, &customerIndex) //un stringify it aka JSON.parse()

	//append
	customerIndex = append(customerIndex, name) //add customer name to index list
	fmt.Println("! customer index: ", customerIndex)
	jsonAsBytes, _ := json.Marshal(customerIndex)
	err = stub.PutState(customerIndexStr, jsonAsBytes) //store name of customer

	fmt.Println("- end init customer")
	return nil, nil
}

// ============================================================================================================================
// Set User Permission on Customer
// ============================================================================================================================
func (t *SimpleChaincode) set_user(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	var err error

	//   0       1
	// "name", "bob"
	if len(args) < 2 {
		return nil, errors.New("Incorrect number of arguments. Expecting 2")
	}

	fmt.Println("- start set user")
	fmt.Println(args[0] + " - " + args[1])
	customerAsBytes, err := stub.GetState(args[0])
	if err != nil {
		return nil, errors.New("Failed to get thing")
	}
	res := Customer{}
	json.Unmarshal(customerAsBytes, &res) //un stringify it aka JSON.parse()
	//res.User = args[1]                  //change the user

	jsonAsBytes, _ := json.Marshal(res)
	err = stub.PutState(args[0], jsonAsBytes) //rewrite the customer with id as key
	if err != nil {
		return nil, err
	}

	fmt.Println("- end set user")
	return nil, nil
}
<|endoftext|>


<commit_before>package cpp

import (
	"fmt"
	"strconv"
)

/*
Implements the expression parsing and evaluation for #if statements

Note that "defined name" and "define(name)" are handled before this part of code.

#if expression
    controlled text
#endif

expression may be:

Integer constants.

Character constants, which are interpreted as they would be in normal code.

Arithmetic operators for most of C

Identifiers that are not macros, which are all considered to be the number zero.

*/

type cppExprCtx struct {
	isDefined func(string) bool
	nextToken func() *Token
	onError   func(error)
}

func (ctx *cppExprCtx) peek() *Token {
	t := ctx.nextToken()
	oldNext := ctx.nextToken
	ctx.nextToken = func() *Token {
		ctx.nextToken = oldNext
		return t
	}
	return t
}

func parseCPPExprAtom(ctx *cppExprCtx) int64 {
	toCheck := ctx.nextToken()
	if toCheck == nil {
		ctx.onError(fmt.Errorf("expected integer, char, or defined but got nothing"))
		return 0
	}
	switch toCheck.Kind {
	case NOT:
		v := parseCPPExprAtom(ctx)
		if v == 0 {
			return 1
		}
		return 0
	case BNOT:
		v := parseCPPExprAtom(ctx)
		return ^v
	case SUB:
		v := parseCPPExprAtom(ctx)
		return -v
	case ADD:
		v := parseCPPExprAtom(ctx)
		return v
	case LPAREN:
		v := parseCPPExpr(ctx)
		rparen := ctx.nextToken()
		if rparen == nil || rparen.Kind != RPAREN {
			ctx.onError(fmt.Errorf("unclosed parenthesis"))
		}
		return v
	case INT_CONSTANT:
		v, err := strconv.ParseInt(toCheck.Val, 0, 64)
		if err != nil {
			ctx.onError(fmt.Errorf("internal error parsing int constant"))
		}
		return v
	case CHAR_CONSTANT:
		ctx.onError(fmt.Errorf("unimplemented char literal in cpp expression"))
		return 0
	case IDENT:
		if toCheck.Val == "defined" {
			toCheck = ctx.nextToken()
			if toCheck == nil {
				ctx.onError(fmt.Errorf("expected ( or an identifier but got nothing"))
				return 0
			}
			switch toCheck.Kind {
			case LPAREN:
				toCheck = ctx.nextToken()
				rparen := ctx.nextToken()
				if rparen == nil || rparen.Kind != RPAREN {
					ctx.onError(fmt.Errorf("malformed defined check, missing )"))
					return 0
				}
			case IDENT:
				//calls isDefined as intended
			default:
				ctx.onError(fmt.Errorf("malformed defined statement at %s", toCheck.Pos))
				return 0
			}
		}
	default:
		ctx.onError(fmt.Errorf("expected integer, char, or defined but got %s", toCheck.Val))
		return 0
	}
	if toCheck == nil {
		ctx.onError(fmt.Errorf("expected identifier but got nothing"))
		return 0
	}
	if ctx.isDefined(toCheck.Val) {
		return 1
	}
	return 0
}

func evalCPPBinop(ctx *cppExprCtx, k TokenKind, l int64, r int64) int64 {
	switch k {
	case LOR:
		if l != 0 || r != 0 {
			return 1
		}
		return 0
	case LAND:
		if l != 0 && r != 0 {
			return 1
		}
		return 0
	case OR:
		return l | r
	case XOR:
		return l ^ r
	case AND:
		return l & r
	case ADD:
		return l + r
	case SUB:
		return l - r
	case MUL:
		return l * r
	case SHR:
		return l >> uint64(r)
	case SHL:
		return l << uint64(r)
	case QUO:
		if r == 0 {
			ctx.onError(fmt.Errorf("divide by zero in expression"))
			return 0
		}
		return l / r
	case REM:
		if r == 0 {
			ctx.onError(fmt.Errorf("divide by zero in expression"))
			return 0
		}
		return l % r
	case EQL:
		if l == r {
			return 1
		}
		return 0
	case LSS:
		if l < r {
			return 1
		}
		return 0
	case GTR:
		if l > r {
			return 1
		}
		return 0
	case LEQ:
		if l <= r {
			return 1
		}
		return 0
	case GEQ:
		if l >= r {
			return 1
		}
		return 0
	case NEQ:
		if l != r {
			return 1
		}
		return 0
	case COMMA:
		return r
	default:
		ctx.onError(fmt.Errorf("internal error %s", k))
	}
	return 0
}

func parseCPPTernary(ctx *cppExprCtx) int64 {
	cond := parseCPPBinop(ctx)
	t := ctx.peek()
	var a, b int64
	if t != nil && t.Kind == QUESTION {
		ctx.nextToken()
		a = parseCPPExpr(ctx)
		colon := ctx.nextToken()
		if colon == nil || colon.Kind != COLON {
			ctx.onError(fmt.Errorf("ternary without :"))
		}
		b = parseCPPExpr(ctx)

		if cond != 0 {
			return a
		}
		return b
	}
	return cond
}

func parseCPPComma(ctx *cppExprCtx) int64 {
	v := parseCPPTernary(ctx)
	for {
		t := ctx.peek()
		if t == nil || t.Kind != COMMA {
			break
		}
		ctx.nextToken()
		v = parseCPPTernary(ctx)
	}
	return v
}

func getPrec(k TokenKind) int {
	switch k {
	case MUL, REM, QUO:
		return 10
	case ADD, SUB:
		return 9
	case SHR, SHL:
		return 8
	case LSS, GTR, GEQ, LEQ:
		return 7
	case EQL, NEQ:
		return 6
	case AND:
		return 5
	case XOR:
		return 4
	case OR:
		return 3
	case LAND:
		return 2
	case LOR:
		return 1
	}
	return -1
}

//This is the precedence climbing algorithm, simplified because
//all the operators are left associative.
func parseCPPBinop_1(ctx *cppExprCtx, prec int) int64 {
	l := parseCPPExprAtom(ctx)
	for {
		t := ctx.peek()
		if t == nil {
			break
		}
		p := getPrec(t.Kind)
		if p == -1 {
			break
		}
		if p < prec {
			break
		}
		ctx.nextToken()
		r := parseCPPBinop_1(ctx, p+1)
		l = evalCPPBinop(ctx, t.Kind, l, r)
	}
	return l
}

func parseCPPBinop(ctx *cppExprCtx) int64 {
	return parseCPPBinop_1(ctx, 0)
}

func parseCPPExpr(ctx *cppExprCtx) int64 {
	result := parseCPPComma(ctx)
	return result
}

func evalIfExpr(isDefined func(string) bool, nextToken func() *Token, onError func(error)) int64 {
	ctx := &cppExprCtx{isDefined: isDefined, nextToken: nextToken, onError: onError}
	ret := parseCPPExpr(ctx)
	t := ctx.nextToken()
	if t != nil {
		ctx.onError(fmt.Errorf("stray token %s", t.Val))
	}
	return ret
}
<commit_msg>comments<commit_after>package cpp

import (
	"fmt"
	"strconv"
)

/*
Implements the expression parsing and evaluation for #if statements

Note that "defined name" and "define(name)" are handled before this part of code.

#if expression
    controlled text
#endif

expression may be:

Integer constants.

Character constants, which are interpreted as they would be in normal code.

Arithmetic operators for most of C

Identifiers that are not macros, which are all considered to be the number zero.

*/

type cppExprCtx struct {
	isDefined func(string) bool
	nextToken func() *Token
	onError   func(error)
}

func (ctx *cppExprCtx) peek() *Token {
	t := ctx.nextToken()
	oldNext := ctx.nextToken
	ctx.nextToken = func() *Token {
		ctx.nextToken = oldNext
		return t
	}
	return t
}

func parseCPPExprAtom(ctx *cppExprCtx) int64 {
	toCheck := ctx.nextToken()
	if toCheck == nil {
		ctx.onError(fmt.Errorf("expected integer, char, or defined but got nothing"))
		return 0
	}
	switch toCheck.Kind {
	case NOT:
		v := parseCPPExprAtom(ctx)
		if v == 0 {
			return 1
		}
		return 0
	case BNOT:
		v := parseCPPExprAtom(ctx)
		return ^v
	case SUB:
		v := parseCPPExprAtom(ctx)
		return -v
	case ADD:
		v := parseCPPExprAtom(ctx)
		return v
	case LPAREN:
		v := parseCPPExpr(ctx)
		rparen := ctx.nextToken()
		if rparen == nil || rparen.Kind != RPAREN {
			ctx.onError(fmt.Errorf("unclosed parenthesis"))
		}
		return v
	case INT_CONSTANT:
		v, err := strconv.ParseInt(toCheck.Val, 0, 64)
		if err != nil {
			ctx.onError(fmt.Errorf("internal error parsing int constant"))
		}
		return v
	case CHAR_CONSTANT:
		ctx.onError(fmt.Errorf("unimplemented char literal in cpp expression"))
		return 0
	case IDENT:
		if toCheck.Val == "defined" {
			toCheck = ctx.nextToken()
			if toCheck == nil {
				ctx.onError(fmt.Errorf("expected ( or an identifier but got nothing"))
				return 0
			}
			switch toCheck.Kind {
			case LPAREN:
				toCheck = ctx.nextToken()
				rparen := ctx.nextToken()
				if rparen == nil || rparen.Kind != RPAREN {
					ctx.onError(fmt.Errorf("malformed defined check, missing )"))
					return 0
				}
			case IDENT:
				//calls isDefined as intended
			default:
				ctx.onError(fmt.Errorf("malformed defined statement at %s", toCheck.Pos))
				return 0
			}
		}
	default:
		ctx.onError(fmt.Errorf("expected integer, char, or defined but got %s", toCheck.Val))
		return 0
	}
	if toCheck == nil {
		ctx.onError(fmt.Errorf("expected identifier but got nothing"))
		return 0
	}
	if ctx.isDefined(toCheck.Val) {
		return 1
	}
	return 0
}

func evalCPPBinop(ctx *cppExprCtx, k TokenKind, l int64, r int64) int64 {
	switch k {
	case LOR:
		if l != 0 || r != 0 {
			return 1
		}
		return 0
	case LAND:
		if l != 0 && r != 0 {
			return 1
		}
		return 0
	case OR:
		return l | r
	case XOR:
		return l ^ r
	case AND:
		return l & r
	case ADD:
		return l + r
	case SUB:
		return l - r
	case MUL:
		return l * r
	case SHR:
		return l >> uint64(r)
	case SHL:
		return l << uint64(r)
	case QUO:
		if r == 0 {
			ctx.onError(fmt.Errorf("divide by zero in expression"))
			return 0
		}
		return l / r
	case REM:
		if r == 0 {
			ctx.onError(fmt.Errorf("divide by zero in expression"))
			return 0
		}
		return l % r
	case EQL:
		if l == r {
			return 1
		}
		return 0
	case LSS:
		if l < r {
			return 1
		}
		return 0
	case GTR:
		if l > r {
			return 1
		}
		return 0
	case LEQ:
		if l <= r {
			return 1
		}
		return 0
	case GEQ:
		if l >= r {
			return 1
		}
		return 0
	case NEQ:
		if l != r {
			return 1
		}
		return 0
	case COMMA:
		return r
	default:
		ctx.onError(fmt.Errorf("internal error %s", k))
	}
	return 0
}

func parseCPPTernary(ctx *cppExprCtx) int64 {
	cond := parseCPPBinop(ctx)
	t := ctx.peek()
	var a, b int64
	if t != nil && t.Kind == QUESTION {
		ctx.nextToken()
		a = parseCPPExpr(ctx)
		colon := ctx.nextToken()
		if colon == nil || colon.Kind != COLON {
			ctx.onError(fmt.Errorf("ternary without :"))
		}
		b = parseCPPExpr(ctx)

		if cond != 0 {
			return a
		}
		return b
	}
	return cond
}

func parseCPPComma(ctx *cppExprCtx) int64 {
	v := parseCPPTernary(ctx)
	for {
		t := ctx.peek()
		if t == nil || t.Kind != COMMA {
			break
		}
		ctx.nextToken()
		v = parseCPPTernary(ctx)
	}
	return v
}

func getPrec(k TokenKind) int {
	switch k {
	case MUL, REM, QUO:
		return 10
	case ADD, SUB:
		return 9
	case SHR, SHL:
		return 8
	case LSS, GTR, GEQ, LEQ:
		return 7
	case EQL, NEQ:
		return 6
	case AND:
		return 5
	case XOR:
		return 4
	case OR:
		return 3
	case LAND:
		return 2
	case LOR:
		return 1
	}
	return -1
}

//This is the precedence climbing algorithm, simplified because
//all the operators are left associative. The CPP doesn't
//deal with assignment operators.
func parseCPPBinop_1(ctx *cppExprCtx, prec int) int64 {
	l := parseCPPExprAtom(ctx)
	for {
		t := ctx.peek()
		if t == nil {
			break
		}
		p := getPrec(t.Kind)
		if p == -1 {
			break
		}
		if p < prec {
			break
		}
		ctx.nextToken()
		r := parseCPPBinop_1(ctx, p+1)
		l = evalCPPBinop(ctx, t.Kind, l, r)
	}
	return l
}

func parseCPPBinop(ctx *cppExprCtx) int64 {
	return parseCPPBinop_1(ctx, 0)
}

func parseCPPExpr(ctx *cppExprCtx) int64 {
	result := parseCPPComma(ctx)
	return result
}

func evalIfExpr(isDefined func(string) bool, nextToken func() *Token, onError func(error)) int64 {
	ctx := &cppExprCtx{isDefined: isDefined, nextToken: nextToken, onError: onError}
	ret := parseCPPExpr(ctx)
	t := ctx.nextToken()
	if t != nil {
		ctx.onError(fmt.Errorf("stray token %s", t.Val))
	}
	return ret
}
<|endoftext|>


<commit_before>package mailcmd

import (
	"errors"
	"fmt"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/HouzuoGuo/laitos/daemon/common"
	"github.com/HouzuoGuo/laitos/inet"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/misc"
	"github.com/HouzuoGuo/laitos/testingstub"
	"github.com/HouzuoGuo/laitos/toolbox"
	"github.com/HouzuoGuo/laitos/toolbox/filter"
)

const CommandTimeoutSec = 120 // CommandTimeoutSec is the default command timeout in seconds

/*
CommandRunner looks for exactly one feature command from an incoming mail, runs it and reply the sender with command
results. Usually used in combination of laitos' own SMTP daemon, but it can also work independently with another MTA
such as the forwarding-mail-to-program mechanism from postfix.
*/
type CommandRunner struct {
	Undocumented1   Undocumented1            `json:"Undocumented1"` // Intentionally undocumented he he he he
	Undocumented2   Undocumented2            `json:"Undocumented2"` // Intentionally undocumented he he he he
	Undocumented3   Undocumented3            `json:"Undocumented3"` // Intentionally undocumented he he he he
	Processor       *common.CommandProcessor `json:"-"`             // Feature configuration
	ReplyMailClient inet.MailClient          `json:"-"`             // To deliver Email replies
	logger          lalog.Logger

	// processTestCaseFunc works along side of command processing routine, it offers execution result to test case for inspection.
	processTestCaseFunc func(*toolbox.Result)
}

// Initialise initialises internal states of command runner. This function must be called before using the command runner.
func (runner *CommandRunner) Initialise() error {
	if runner.Processor == nil || runner.Processor.IsEmpty() {
		return fmt.Errorf("mailcmd.Initialise: command processor and its filters must be configured")
	}
	runner.logger = lalog.Logger{
		ComponentName: "mailcmd",
		ComponentID:   []lalog.LoggerIDField{{Key: "From", Value: runner.ReplyMailClient.MailFrom}},
	}
	runner.Processor.SetLogger(runner.logger)
	runner.Undocumented3.Logger = runner.logger
	runner.Undocumented3.MailClient = runner.ReplyMailClient
	if errs := runner.Processor.IsSaneForInternet(); len(errs) > 0 {
		return fmt.Errorf("mailcmd.Process: %+v", errs)
	}
	return nil
}

// Run a health check on mailer and "undocumented" things.
func (runner *CommandRunner) SelfTest() error {
	ret := make([]string, 0, 0)
	retMutex := &sync.Mutex{}
	wait := &sync.WaitGroup{}
	// One mailer and 3 undocumented
	wait.Add(4)
	go func() {
		err := runner.ReplyMailClient.SelfTest()
		if err != nil {
			retMutex.Lock()
			ret = append(ret, err.Error())
			retMutex.Unlock()
		}
		wait.Done()
	}()
	go func() {
		if runner.Undocumented1.IsConfigured() {
			err := runner.Undocumented1.SelfTest()
			if err != nil {
				retMutex.Lock()
				ret = append(ret, err.Error())
				retMutex.Unlock()
			}
		}
		wait.Done()
	}()
	go func() {
		if runner.Undocumented2.IsConfigured() {
			err := runner.Undocumented2.SelfTest()
			if err != nil {
				retMutex.Lock()
				ret = append(ret, err.Error())
				retMutex.Unlock()
			}
		}
		wait.Done()
	}()
	go func() {
		if runner.Undocumented3.IsConfigured() {
			err := runner.Undocumented3.SelfTest()
			if err != nil {
				retMutex.Lock()
				ret = append(ret, err.Error())
				retMutex.Unlock()
			}
		}
		wait.Done()
	}()
	wait.Wait()
	if len(ret) == 0 {
		return nil
	}
	return errors.New(strings.Join(ret, " | "))
}

/*
Make sure mail processor is sane before processing the incoming mail.
Process only one command (if found) in the incoming mail. If reply addresses are specified, send command result
to the specified addresses. If they are not specified, use the incoming mail sender's address as reply address.
*/
func (runner *CommandRunner) Process(mailContent []byte, replyAddresses ...string) error {
	// Put query duration (including IO time) into statistics
	beginTimeNano := time.Now().UnixNano()
	defer func() {
		common.CommandStats.Trigger(float64(time.Now().UnixNano() - beginTimeNano))
	}()
	if misc.EmergencyLockDown {
		return misc.ErrEmergencyLockDown
	}
	var commandIsProcessed bool
	walkErr := inet.WalkMailMessage(mailContent, func(prop inet.BasicMail, body []byte) (bool, error) {
		// Avoid recursive processing
		if strings.Contains(prop.Subject, inet.OutgoingMailSubjectKeyword) {
			return false, errors.New("ignore email sent by this program itself")
		}
		runner.logger.Info("Process", prop.FromAddress, nil, "process message of type %s, subject \"%s\"", prop.ContentType, prop.Subject)
		// By contract, PIN processor finds command among input lines.
		result := runner.Processor.Process(toolbox.Command{
			Content:    string(body),
			TimeoutSec: CommandTimeoutSec,
		}, true)
		// Offer execution result to test case for inspection
		if runner.processTestCaseFunc != nil {
			runner.processTestCaseFunc(result)
		}
		// If this part does not have a PIN/shortcut match, simply move on to the next part.
		if result.Error == filter.ErrPINAndShortcutNotFound {
			// Move on, do not return error.
			return true, nil
		} else if result.Error != nil {
			// In case of command processing error, do not move on, return the error.
			return false, result.Error
		}
		// A command has been processed, now work on the reply.
		commandIsProcessed = true
		// Normally the result should be sent as Email reply, but there are undocumented scenarios.
		if runner.Undocumented1.MayReplyTo(prop) {
			runner.logger.Info("Process", prop.FromAddress, nil, "invoke Undocumented1")
			if undocErr := runner.Undocumented1.SendMessage(result.CombinedOutput); undocErr == nil {
				return false, nil
			} else {
				return false, undocErr
			}
		}
		if runner.Undocumented2.MayReplyTo(prop) {
			runner.logger.Info("Process", prop.FromAddress, nil, "invoke Undocumented2")
			if undocErr := runner.Undocumented2.SendMessage(result.CombinedOutput); undocErr == nil {
				return false, nil
			} else {
				return false, undocErr
			}
		}
		if runner.Undocumented3.MayReplyTo(prop) {
			runner.logger.Info("Process", prop.FromAddress, nil, "invoke Undocumented3")
			if undocErr := runner.Undocumented3.SendMessage(prop, result.CombinedOutput); undocErr == nil {
				return false, nil
			} else {
				return false, undocErr
			}
		}
		// The Email address suffix did not satisfy undocumented scenario, so send the result as a normal Email reply.
		if !runner.ReplyMailClient.IsConfigured() {
			return false, errors.New("the reply has to be sent via Email but configuration is missing")
		}
		recipients := replyAddresses
		if recipients == nil || len(recipients) == 0 {
			recipients = []string{prop.ReplyAddress}
		}
		return false, runner.ReplyMailClient.Send(inet.OutgoingMailSubjectKeyword+"-reply-"+result.Command.Content, result.CombinedOutput, recipients...)
	})
	if walkErr != nil {
		return walkErr
	}
	// If all parts have been visited but no command is found, return the PIN mismatch error.
	if !commandIsProcessed {
		return filter.ErrPINAndShortcutNotFound
	}
	return nil
}

var (
	// The content of the following variables are set by init_mailcmd_test.go

	TestUndocumented1Message = ""
	TestWolframAlpha         = toolbox.WolframAlpha{}
	TestUndocumented2Message = ""
	TestUndocumented3Message = ""
)

// Run unit tests on mail processor. See TestMailProcessor_Process for processor setup.
func TestCommandRunner(runner *CommandRunner, t testingstub.T) {
	// Real MTA is required to run the tests
	if _, err := net.Dial("tcp", "127.0.0.1:25"); err != nil {
		fmt.Println("skip the test due to no MTA running on 127.0.0.1")
		return
	}
	if err := runner.SelfTest(); err != nil {
		t.Fatal(err)
	}
	var lastResult *toolbox.Result
	runner.processTestCaseFunc = func(result *toolbox.Result) {
		lastResult = result
	}
	// PIN mismatch
	pinMismatch := `From howard@localhost Sun Feb 26 18:17:34 2017
Return-Path: <howard@localhost>
X-Original-To: howard@localhost
Delivered-To: howard@localhost
Received: by localhost (Postfix, from userid 1000)
        id 542EA2421BD; Sun, 26 Feb 2017 18:17:34 +0100 (CET)
Date: Sun, 26 Feb 2017 18:17:34 +0100
To: howard@localhost
Subject: hi howard
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Message-Id: <20170226171734.542EA2421BD@localhost.>
From: howard@localhost (Howard Guo)
Status: R

PIN mismatch`
	lastResult = nil
	if err := runner.Process([]byte(pinMismatch)); err != filter.ErrPINAndShortcutNotFound {
		t.Fatal(err)
	} else if lastResult != nil {
		t.Fatal("should not have executed any command", lastResult)
	}
	// PIN matches
	pinMatch := `From howard@localhost Sun Feb 26 18:17:34 2017
Return-Path: <howard@localhost>
X-Original-To: howard@localhost
Delivered-To: howard@localhost
Received: by localhost (Postfix, from userid 1000)
        id 542EA2421BD; Sun, 26 Feb 2017 18:17:34 +0100 (CET)
Date: Sun, 26 Feb 2017 18:17:34 +0100
To: howard@localhost
Subject: hi howard
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Message-Id: <20170226171734.542EA2421BD@localhost.>
From: howard@localhost (Howard Guo)
Status: R

PIN mismatch
verysecret.s echo hi
`
	lastResult = nil
	if err := runner.Process([]byte(pinMatch)); err != nil {
		t.Fatal(err)
	} else if lastResult == nil || lastResult.Error != nil || strings.TrimSpace(lastResult.CombinedOutput) != "hi" {
		t.Fatalf("%+v", lastResult)
	}
	// PIN matches and override reply addr
	lastResult = nil
	if err := runner.Process([]byte(pinMatch), "root@localhost"); err != nil {
		t.Fatal(err)
	} else if lastResult == nil || lastResult.Error != nil || strings.TrimSpace(lastResult.CombinedOutput) != "hi" {
		t.Fatalf("%+v", lastResult)
	}
}
<commit_msg>Avoid doubling up command execution counter via their execution in incoming emails<commit_after>package mailcmd

import (
	"errors"
	"fmt"
	"github.com/HouzuoGuo/laitos/daemon/common"
	"github.com/HouzuoGuo/laitos/inet"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/misc"
	"github.com/HouzuoGuo/laitos/testingstub"
	"github.com/HouzuoGuo/laitos/toolbox"
	"github.com/HouzuoGuo/laitos/toolbox/filter"
	"net"
	"strings"
	"sync"
)

const CommandTimeoutSec = 120 // CommandTimeoutSec is the default command timeout in seconds

/*
CommandRunner looks for exactly one feature command from an incoming mail, runs it and reply the sender with command
results. Usually used in combination of laitos' own SMTP daemon, but it can also work independently with another MTA
such as the forwarding-mail-to-program mechanism from postfix.
*/
type CommandRunner struct {
	Undocumented1   Undocumented1            `json:"Undocumented1"` // Intentionally undocumented he he he he
	Undocumented2   Undocumented2            `json:"Undocumented2"` // Intentionally undocumented he he he he
	Undocumented3   Undocumented3            `json:"Undocumented3"` // Intentionally undocumented he he he he
	Processor       *common.CommandProcessor `json:"-"`             // Feature configuration
	ReplyMailClient inet.MailClient          `json:"-"`             // To deliver Email replies
	logger          lalog.Logger

	// processTestCaseFunc works along side of command processing routine, it offers execution result to test case for inspection.
	processTestCaseFunc func(*toolbox.Result)
}

// Initialise initialises internal states of command runner. This function must be called before using the command runner.
func (runner *CommandRunner) Initialise() error {
	if runner.Processor == nil || runner.Processor.IsEmpty() {
		return fmt.Errorf("mailcmd.Initialise: command processor and its filters must be configured")
	}
	runner.logger = lalog.Logger{
		ComponentName: "mailcmd",
		ComponentID:   []lalog.LoggerIDField{{Key: "From", Value: runner.ReplyMailClient.MailFrom}},
	}
	runner.Processor.SetLogger(runner.logger)
	runner.Undocumented3.Logger = runner.logger
	runner.Undocumented3.MailClient = runner.ReplyMailClient
	if errs := runner.Processor.IsSaneForInternet(); len(errs) > 0 {
		return fmt.Errorf("mailcmd.Process: %+v", errs)
	}
	return nil
}

// Run a health check on mailer and "undocumented" things.
func (runner *CommandRunner) SelfTest() error {
	ret := make([]string, 0, 0)
	retMutex := &sync.Mutex{}
	wait := &sync.WaitGroup{}
	// One mailer and 3 undocumented
	wait.Add(4)
	go func() {
		err := runner.ReplyMailClient.SelfTest()
		if err != nil {
			retMutex.Lock()
			ret = append(ret, err.Error())
			retMutex.Unlock()
		}
		wait.Done()
	}()
	go func() {
		if runner.Undocumented1.IsConfigured() {
			err := runner.Undocumented1.SelfTest()
			if err != nil {
				retMutex.Lock()
				ret = append(ret, err.Error())
				retMutex.Unlock()
			}
		}
		wait.Done()
	}()
	go func() {
		if runner.Undocumented2.IsConfigured() {
			err := runner.Undocumented2.SelfTest()
			if err != nil {
				retMutex.Lock()
				ret = append(ret, err.Error())
				retMutex.Unlock()
			}
		}
		wait.Done()
	}()
	go func() {
		if runner.Undocumented3.IsConfigured() {
			err := runner.Undocumented3.SelfTest()
			if err != nil {
				retMutex.Lock()
				ret = append(ret, err.Error())
				retMutex.Unlock()
			}
		}
		wait.Done()
	}()
	wait.Wait()
	if len(ret) == 0 {
		return nil
	}
	return errors.New(strings.Join(ret, " | "))
}

/*
Make sure mail processor is sane before processing the incoming mail.
Process only one command (if found) in the incoming mail. If reply addresses are specified, send command result
to the specified addresses. If they are not specified, use the incoming mail sender's address as reply address.
*/
func (runner *CommandRunner) Process(mailContent []byte, replyAddresses ...string) error {
	if misc.EmergencyLockDown {
		return misc.ErrEmergencyLockDown
	}
	var commandIsProcessed bool
	walkErr := inet.WalkMailMessage(mailContent, func(prop inet.BasicMail, body []byte) (bool, error) {
		// Avoid recursive processing
		if strings.Contains(prop.Subject, inet.OutgoingMailSubjectKeyword) {
			return false, errors.New("ignore email sent by this program itself")
		}
		runner.logger.Info("Process", prop.FromAddress, nil, "process message of type %s, subject \"%s\"", prop.ContentType, prop.Subject)
		// By contract, PIN processor finds command among input lines.
		result := runner.Processor.Process(toolbox.Command{
			Content:    string(body),
			TimeoutSec: CommandTimeoutSec,
		}, true)
		// Offer execution result to test case for inspection
		if runner.processTestCaseFunc != nil {
			runner.processTestCaseFunc(result)
		}
		// If this part does not have a PIN/shortcut match, simply move on to the next part.
		if result.Error == filter.ErrPINAndShortcutNotFound {
			// Move on, do not return error.
			return true, nil
		} else if result.Error != nil {
			// In case of command processing error, do not move on, return the error.
			return false, result.Error
		}
		// A command has been processed, now work on the reply.
		commandIsProcessed = true
		// Normally the result should be sent as Email reply, but there are undocumented scenarios.
		if runner.Undocumented1.MayReplyTo(prop) {
			runner.logger.Info("Process", prop.FromAddress, nil, "invoke Undocumented1")
			if undocErr := runner.Undocumented1.SendMessage(result.CombinedOutput); undocErr == nil {
				return false, nil
			} else {
				return false, undocErr
			}
		}
		if runner.Undocumented2.MayReplyTo(prop) {
			runner.logger.Info("Process", prop.FromAddress, nil, "invoke Undocumented2")
			if undocErr := runner.Undocumented2.SendMessage(result.CombinedOutput); undocErr == nil {
				return false, nil
			} else {
				return false, undocErr
			}
		}
		if runner.Undocumented3.MayReplyTo(prop) {
			runner.logger.Info("Process", prop.FromAddress, nil, "invoke Undocumented3")
			if undocErr := runner.Undocumented3.SendMessage(prop, result.CombinedOutput); undocErr == nil {
				return false, nil
			} else {
				return false, undocErr
			}
		}
		// The Email address suffix did not satisfy undocumented scenario, so send the result as a normal Email reply.
		if !runner.ReplyMailClient.IsConfigured() {
			return false, errors.New("the reply has to be sent via Email but configuration is missing")
		}
		recipients := replyAddresses
		if recipients == nil || len(recipients) == 0 {
			recipients = []string{prop.ReplyAddress}
		}
		return false, runner.ReplyMailClient.Send(inet.OutgoingMailSubjectKeyword+"-reply-"+result.Command.Content, result.CombinedOutput, recipients...)
	})
	if walkErr != nil {
		return walkErr
	}
	// If all parts have been visited but no command is found, return the PIN mismatch error.
	if !commandIsProcessed {
		return filter.ErrPINAndShortcutNotFound
	}
	return nil
}

var (
	// The content of the following variables are set by init_mailcmd_test.go

	TestUndocumented1Message = ""
	TestWolframAlpha         = toolbox.WolframAlpha{}
	TestUndocumented2Message = ""
	TestUndocumented3Message = ""
)

// Run unit tests on mail processor. See TestMailProcessor_Process for processor setup.
func TestCommandRunner(runner *CommandRunner, t testingstub.T) {
	// Real MTA is required to run the tests
	if _, err := net.Dial("tcp", "127.0.0.1:25"); err != nil {
		fmt.Println("skip the test due to no MTA running on 127.0.0.1")
		return
	}
	if err := runner.SelfTest(); err != nil {
		t.Fatal(err)
	}
	var lastResult *toolbox.Result
	runner.processTestCaseFunc = func(result *toolbox.Result) {
		lastResult = result
	}
	// PIN mismatch
	pinMismatch := `From howard@localhost Sun Feb 26 18:17:34 2017
Return-Path: <howard@localhost>
X-Original-To: howard@localhost
Delivered-To: howard@localhost
Received: by localhost (Postfix, from userid 1000)
        id 542EA2421BD; Sun, 26 Feb 2017 18:17:34 +0100 (CET)
Date: Sun, 26 Feb 2017 18:17:34 +0100
To: howard@localhost
Subject: hi howard
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Message-Id: <20170226171734.542EA2421BD@localhost.>
From: howard@localhost (Howard Guo)
Status: R

PIN mismatch`
	lastResult = nil
	if err := runner.Process([]byte(pinMismatch)); err != filter.ErrPINAndShortcutNotFound {
		t.Fatal(err)
	} else if lastResult != nil {
		t.Fatal("should not have executed any command", lastResult)
	}
	// PIN matches
	pinMatch := `From howard@localhost Sun Feb 26 18:17:34 2017
Return-Path: <howard@localhost>
X-Original-To: howard@localhost
Delivered-To: howard@localhost
Received: by localhost (Postfix, from userid 1000)
        id 542EA2421BD; Sun, 26 Feb 2017 18:17:34 +0100 (CET)
Date: Sun, 26 Feb 2017 18:17:34 +0100
To: howard@localhost
Subject: hi howard
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Message-Id: <20170226171734.542EA2421BD@localhost.>
From: howard@localhost (Howard Guo)
Status: R

PIN mismatch
verysecret.s echo hi
`
	lastResult = nil
	if err := runner.Process([]byte(pinMatch)); err != nil {
		t.Fatal(err)
	} else if lastResult == nil || lastResult.Error != nil || strings.TrimSpace(lastResult.CombinedOutput) != "hi" {
		t.Fatalf("%+v", lastResult)
	}
	// PIN matches and override reply addr
	lastResult = nil
	if err := runner.Process([]byte(pinMatch), "root@localhost"); err != nil {
		t.Fatal(err)
	} else if lastResult == nil || lastResult.Error != nil || strings.TrimSpace(lastResult.CombinedOutput) != "hi" {
		t.Fatalf("%+v", lastResult)
	}
}
<|endoftext|>


<commit_before>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

//This test is an integration test and need zookeeper which is why it isn't in the zzk/registry package
package elasticsearch

import (
	"github.com/fatih/set"
	"github.com/zenoss/glog"
	"github.com/zenoss/serviced/coordinator/client"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/zzk/registry"
	. "gopkg.in/check.v1"

	"fmt"
	"sort"
	"strings"
	"time"
)

func (dt *DaoTest) TestDao_VhostRegistryCreate(t *C) {

	_, err := registry.VHostRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//test idempotence
	_, err = registry.VHostRegistry(dt.zkConn)
	t.Assert(err, IsNil)
}

func (dt *DaoTest) TestDao_VhostRegistryAdd(t *C) {

	vr, err := registry.VHostRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//	func (vr *VhostRegistry) AddItem(conn client.Connection, key string, node *VhostEndpoint) (string, error) {

	vep := registry.VhostEndpoint{}
	vep.EndpointName = "epn_test"
	vep.ServiceID = "svc_id"
	vep.HostIP = "testip"
	path, err := vr.AddItem(dt.zkConn, "testKey", vep)
	t.Assert(err, IsNil)
	t.Assert(path, Not(Equals), 0)

	var newVep *registry.VhostEndpoint
	newVep, err = vr.GetItem(dt.zkConn, path)
	t.Assert(err, IsNil)
	t.Assert(vep, NotNil)
	//remove version for equals
	newVep.SetVersion(nil)
	t.Assert(vep, Equals, *newVep)

	//test double add
	path, err = vr.AddItem(dt.zkConn, "testKey", vep)
	t.Assert(err, NotNil)
}

func (dt *DaoTest) TestDao_EndpointRegistryCreate(t *C) {

	_, err := registry.CreateEndpointRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//test idempotence
	_, err = registry.CreateEndpointRegistry(dt.zkConn)
	t.Assert(err, IsNil)
}

func (dt *DaoTest) TestDao_EndpointRegistrySet(t *C) {

	epr, err := registry.CreateEndpointRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//test get and set
	verifySetGet := func(expected registry.EndpointNode) {
		glog.V(1).Infof("verifying set/get for expected %+v", expected)
		for ii := 0; ii < 3; ii++ {
			path, err := epr.SetItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID, expected)
			glog.V(1).Infof("expected item[%s]: %+v", path, expected)
			t.Assert(err, IsNil)
			t.Assert(path, Not(Equals), 0)

			var obtained *registry.EndpointNode
			obtained, err = epr.GetItem(dt.zkConn, path)
			glog.V(1).Infof("obtained item[%s]: %+v", path, expected)
			t.Assert(err, IsNil)
			t.Assert(obtained, NotNil)
			//remove version for equals
			expected.SetVersion(nil)
			obtained.SetVersion(nil)
			t.Assert(expected, Equals, *obtained)
		}
	}

	aep := dao.ApplicationEndpoint{
		ServiceID:     "epn_service",
		ContainerIP:   "192.168.0.1",
		ContainerPort: 54321,
		HostIP:        "192.168.0.2",
		HostPort:      12345,
		Protocol:      "epn_tcp",
	}
	epn1 := registry.EndpointNode{
		ApplicationEndpoint: aep,
		TenantID:            "epn_tenant",
		EndpointID:          "epn_endpoint",
		HostID:              "epn_host1",
		ContainerID:         "epn_container",
	}

	verifySetGet(epn1)

	epn2 := epn1
	epn2.EndpointID = "epn_.*"
	epn2.ContainerID = "epn_container2"
	verifySetGet(epn2)

	// test remove
	verifyRemove := func(expected registry.EndpointNode) {
		err = epr.RemoveItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID)
		t.Assert(err, IsNil)
	}
	verifyRemove(epn1)
	verifyRemove(epn2)

	//test watch tenant endpoint
	verifyWatch := func(expected registry.EndpointNode) {
		numEndpoints := 0
		var obtained *registry.EndpointNode
		go func() {
			errorWatcher := func(path string, err error) {}
			countEvents := func(conn client.Connection, parentPath string, nodeIDs ...string) {
				numEndpoints++
				glog.Infof("seeing event %d parentPath:%s nodeIDs:%v", numEndpoints, parentPath, nodeIDs)

				if len(nodeIDs) > 0 {
					obtained, err = epr.GetItem(dt.zkConn, fmt.Sprintf("%s/%s", parentPath, nodeIDs[0]))
					t.Assert(err, IsNil)
					t.Assert(obtained, NotNil)
				}
			}

			tenantEndpointKey := registry.TenantEndpointKey(expected.TenantID, expected.EndpointID)
			glog.Infof("watching tenant endpoint %s", tenantEndpointKey)
			err = epr.WatchTenantEndpoint(dt.zkConn, tenantEndpointKey, countEvents, errorWatcher)
			t.Assert(err, IsNil)
		}()

		time.Sleep(2 * time.Second)

		const numEndpointsExpected int = 3
		for i := 0; i < numEndpointsExpected; i++ {
			glog.Infof("SetItem %+v", expected)
			expected.ContainerID = fmt.Sprintf("epn_container_%d", i)
			_, err = epr.SetItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID, expected)
			t.Assert(err, IsNil)
			time.Sleep(1 * time.Second)
		}

		time.Sleep(2 * time.Second)
		//remove version for equals
		expected.SetVersion(nil)
		obtained.SetVersion(nil)
		t.Assert(expected, Equals, *obtained)

		// remove items
		for i := 0; i < numEndpointsExpected; i++ {
			glog.Infof("RemoveItem %+v", expected)
			expected.ContainerID = fmt.Sprintf("epn_container_%d", i)
			err = epr.RemoveItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID)
			t.Assert(err, IsNil)
			time.Sleep(1 * time.Second)
		}

		err = epr.RemoveTenantEndpointKey(dt.zkConn, expected.TenantID, expected.EndpointID)
		t.Assert(err, IsNil)

		glog.Warning("TODO - deficiency of epr.WatchTenantEndpoint - notice how countEvents is not called after RemoveTenantEndpointKey")
		for i := 0; i < numEndpointsExpected; i++ {
			glog.Infof("SetItem %+v", expected)
			expected.ContainerID = fmt.Sprintf("epn_container_%d", i)
			_, err = epr.SetItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID, expected)
			t.Assert(err, IsNil)
			time.Sleep(1 * time.Second)
		}
	}

	epn3 := epn1
	verifyWatch(epn3)

	// make sure that watching non-existent path does not result in panic
	epn4 := registry.EndpointNode{
		ApplicationEndpoint: aep,
		TenantID:            "epn_tenant4",
		EndpointID:          "epn_endpoint4",
		HostID:              "epn_host4",
		ContainerID:         "epn_container4",
	}
	verifyWatchNonExistentKey := func(expected registry.EndpointNode) {
		var obtained *registry.EndpointNode
		errorWatcher := func(path string, err error) {}
		showEvents := func(conn client.Connection, parentPath string, nodeIDs ...string) {
			glog.Infof("seeing event parentPath:%s nodeIDs:%v", parentPath, nodeIDs)

			if len(nodeIDs) > 0 {
				obtained, err = epr.GetItem(dt.zkConn, fmt.Sprintf("%s/%s", parentPath, nodeIDs[0]))
				t.Assert(err, IsNil)
				t.Assert(obtained, NotNil)
			}
		}

		tenantEndpointKey := registry.TenantEndpointKey(expected.TenantID, expected.EndpointID)
		glog.Infof("watching tenant endpoint %s", tenantEndpointKey)
		err = epr.WatchTenantEndpoint(dt.zkConn, tenantEndpointKey, showEvents, errorWatcher)
		t.Assert(err, NotNil)
		t.Assert(err, Equals, client.ErrNoNode)
	}
	verifyWatchNonExistentKey(epn4)
}

func (dt *DaoTest) TestDao_FatihSet(t *C) {
	// test sets
	verifySetDifference := func() {
		setOld := set.New(set.ThreadSafe) // thread safe version
		setOld.Add("item3", "item4", "item2", "item1")

		setNew := set.New(set.ThreadSafe) // thread safe version
		setNew.Add("item3", "item4", "item6", "item5")

		separator := "/"

		deletions := set.Difference(setOld, setNew)
		deletionsObtained := set.StringSlice(deletions)
		sort.Strings(deletionsObtained)
		deletionsExpected := []string{"item1", "item2"}
		t.Assert(strings.Join(deletionsObtained, separator), Equals, strings.Join(deletionsExpected, separator))

		additions := set.Difference(setNew, setOld)
		additionsObtained := set.StringSlice(additions)
		sort.Strings(additionsObtained)
		additionsExpected := []string{"item5", "item6"}
		t.Assert(strings.Join(additionsObtained, separator), Equals, strings.Join(additionsExpected, separator))

	}
	verifySetDifference()
}
<commit_msg>removed unused AddItem; replaced AddItem with SetItem in vhost unit tests<commit_after>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

//This test is an integration test and need zookeeper which is why it isn't in the zzk/registry package
package elasticsearch

import (
	"github.com/fatih/set"
	"github.com/zenoss/glog"
	"github.com/zenoss/serviced/coordinator/client"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/zzk/registry"
	. "gopkg.in/check.v1"

	"fmt"
	"sort"
	"strings"
	"time"
)

func (dt *DaoTest) TestDao_VhostRegistryCreate(t *C) {

	_, err := registry.VHostRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//test idempotence
	_, err = registry.VHostRegistry(dt.zkConn)
	t.Assert(err, IsNil)
}

func (dt *DaoTest) TestDao_VhostRegistrySet(t *C) {

	vr, err := registry.VHostRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	vep := registry.VhostEndpoint{}
	vep.EndpointName = "epn_test"
	vep.ServiceID = "svc_id"
	vep.HostIP = "testip"
	path, err := vr.SetItem(dt.zkConn, "testKey", vep)
	t.Assert(err, IsNil)
	t.Assert(path, Not(Equals), 0)

	var newVep *registry.VhostEndpoint
	newVep, err = vr.GetItem(dt.zkConn, path)
	t.Assert(err, IsNil)
	t.Assert(vep, NotNil)
	//remove version for equals
	newVep.SetVersion(nil)
	t.Assert(vep, Equals, *newVep)

	//test double add
	path, err = vr.SetItem(dt.zkConn, "testKey", vep)
	t.Assert(err, IsNil)
}

func (dt *DaoTest) TestDao_EndpointRegistryCreate(t *C) {

	_, err := registry.CreateEndpointRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//test idempotence
	_, err = registry.CreateEndpointRegistry(dt.zkConn)
	t.Assert(err, IsNil)
}

func (dt *DaoTest) TestDao_EndpointRegistrySet(t *C) {

	epr, err := registry.CreateEndpointRegistry(dt.zkConn)
	t.Assert(err, IsNil)

	//test get and set
	verifySetGet := func(expected registry.EndpointNode) {
		glog.V(1).Infof("verifying set/get for expected %+v", expected)
		for ii := 0; ii < 3; ii++ {
			path, err := epr.SetItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID, expected)
			glog.V(1).Infof("expected item[%s]: %+v", path, expected)
			t.Assert(err, IsNil)
			t.Assert(path, Not(Equals), 0)

			var obtained *registry.EndpointNode
			obtained, err = epr.GetItem(dt.zkConn, path)
			glog.V(1).Infof("obtained item[%s]: %+v", path, expected)
			t.Assert(err, IsNil)
			t.Assert(obtained, NotNil)
			//remove version for equals
			expected.SetVersion(nil)
			obtained.SetVersion(nil)
			t.Assert(expected, Equals, *obtained)
		}
	}

	aep := dao.ApplicationEndpoint{
		ServiceID:     "epn_service",
		ContainerIP:   "192.168.0.1",
		ContainerPort: 54321,
		HostIP:        "192.168.0.2",
		HostPort:      12345,
		Protocol:      "epn_tcp",
	}
	epn1 := registry.EndpointNode{
		ApplicationEndpoint: aep,
		TenantID:            "epn_tenant",
		EndpointID:          "epn_endpoint",
		HostID:              "epn_host1",
		ContainerID:         "epn_container",
	}

	verifySetGet(epn1)

	epn2 := epn1
	epn2.EndpointID = "epn_.*"
	epn2.ContainerID = "epn_container2"
	verifySetGet(epn2)

	// test remove
	verifyRemove := func(expected registry.EndpointNode) {
		err = epr.RemoveItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID)
		t.Assert(err, IsNil)
	}
	verifyRemove(epn1)
	verifyRemove(epn2)

	//test watch tenant endpoint
	verifyWatch := func(expected registry.EndpointNode) {
		numEndpoints := 0
		var obtained *registry.EndpointNode
		go func() {
			errorWatcher := func(path string, err error) {}
			countEvents := func(conn client.Connection, parentPath string, nodeIDs ...string) {
				numEndpoints++
				glog.Infof("seeing event %d parentPath:%s nodeIDs:%v", numEndpoints, parentPath, nodeIDs)

				if len(nodeIDs) > 0 {
					obtained, err = epr.GetItem(dt.zkConn, fmt.Sprintf("%s/%s", parentPath, nodeIDs[0]))
					t.Assert(err, IsNil)
					t.Assert(obtained, NotNil)
				}
			}

			tenantEndpointKey := registry.TenantEndpointKey(expected.TenantID, expected.EndpointID)
			glog.Infof("watching tenant endpoint %s", tenantEndpointKey)
			err = epr.WatchTenantEndpoint(dt.zkConn, tenantEndpointKey, countEvents, errorWatcher)
			t.Assert(err, IsNil)
		}()

		time.Sleep(2 * time.Second)

		const numEndpointsExpected int = 3
		for i := 0; i < numEndpointsExpected; i++ {
			glog.Infof("SetItem %+v", expected)
			expected.ContainerID = fmt.Sprintf("epn_container_%d", i)
			_, err = epr.SetItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID, expected)
			t.Assert(err, IsNil)
			time.Sleep(1 * time.Second)
		}

		time.Sleep(2 * time.Second)
		//remove version for equals
		expected.SetVersion(nil)
		obtained.SetVersion(nil)
		t.Assert(expected, Equals, *obtained)

		// remove items
		for i := 0; i < numEndpointsExpected; i++ {
			glog.Infof("RemoveItem %+v", expected)
			expected.ContainerID = fmt.Sprintf("epn_container_%d", i)
			err = epr.RemoveItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID)
			t.Assert(err, IsNil)
			time.Sleep(1 * time.Second)
		}

		err = epr.RemoveTenantEndpointKey(dt.zkConn, expected.TenantID, expected.EndpointID)
		t.Assert(err, IsNil)

		glog.Warning("TODO - deficiency of epr.WatchTenantEndpoint - notice how countEvents is not called after RemoveTenantEndpointKey")
		for i := 0; i < numEndpointsExpected; i++ {
			glog.Infof("SetItem %+v", expected)
			expected.ContainerID = fmt.Sprintf("epn_container_%d", i)
			_, err = epr.SetItem(dt.zkConn, expected.TenantID, expected.EndpointID, expected.HostID, expected.ContainerID, expected)
			t.Assert(err, IsNil)
			time.Sleep(1 * time.Second)
		}
	}

	epn3 := epn1
	verifyWatch(epn3)

	// make sure that watching non-existent path does not result in panic
	epn4 := registry.EndpointNode{
		ApplicationEndpoint: aep,
		TenantID:            "epn_tenant4",
		EndpointID:          "epn_endpoint4",
		HostID:              "epn_host4",
		ContainerID:         "epn_container4",
	}
	verifyWatchNonExistentKey := func(expected registry.EndpointNode) {
		var obtained *registry.EndpointNode
		errorWatcher := func(path string, err error) {}
		showEvents := func(conn client.Connection, parentPath string, nodeIDs ...string) {
			glog.Infof("seeing event parentPath:%s nodeIDs:%v", parentPath, nodeIDs)

			if len(nodeIDs) > 0 {
				obtained, err = epr.GetItem(dt.zkConn, fmt.Sprintf("%s/%s", parentPath, nodeIDs[0]))
				t.Assert(err, IsNil)
				t.Assert(obtained, NotNil)
			}
		}

		tenantEndpointKey := registry.TenantEndpointKey(expected.TenantID, expected.EndpointID)
		glog.Infof("watching tenant endpoint %s", tenantEndpointKey)
		err = epr.WatchTenantEndpoint(dt.zkConn, tenantEndpointKey, showEvents, errorWatcher)
		t.Assert(err, NotNil)
		t.Assert(err, Equals, client.ErrNoNode)
	}
	verifyWatchNonExistentKey(epn4)
}

func (dt *DaoTest) TestDao_FatihSet(t *C) {
	// test sets
	verifySetDifference := func() {
		setOld := set.New(set.ThreadSafe) // thread safe version
		setOld.Add("item3", "item4", "item2", "item1")

		setNew := set.New(set.ThreadSafe) // thread safe version
		setNew.Add("item3", "item4", "item6", "item5")

		separator := "/"

		deletions := set.Difference(setOld, setNew)
		deletionsObtained := set.StringSlice(deletions)
		sort.Strings(deletionsObtained)
		deletionsExpected := []string{"item1", "item2"}
		t.Assert(strings.Join(deletionsObtained, separator), Equals, strings.Join(deletionsExpected, separator))

		additions := set.Difference(setNew, setOld)
		additionsObtained := set.StringSlice(additions)
		sort.Strings(additionsObtained)
		additionsExpected := []string{"item5", "item6"}
		t.Assert(strings.Join(additionsObtained, separator), Equals, strings.Join(additionsExpected, separator))

	}
	verifySetDifference()
}
<|endoftext|>


<commit_before>// Copyright ©2012 The bíogo.bam Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bgzf

import (
	"code.google.com/p/biogo.bam/bgzf/egzip"

	"bytes"
	"compress/gzip"
	"errors"
	"io"
	"io/ioutil"
	"os"
	"sync"
)

const (
	BlockSize    = 0x0ff00 // Size of input data block.
	MaxBlockSize = 0x10000 // Maximum size of output block.
)

const (
	bgzfExtra = "BC\x02\x00\x00\x00"
	minFrame  = 20 + len(bgzfExtra) // Minimum bgzf header+footer length.

	// Magic EOF block.
	magicBlock = "\x1f\x8b\x08\x04\x00\x00\x00\x00\x00\xff\x06\x00\x42\x43\x02\x00\x1b\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00"
)

var bgzfExtraPrefix = []byte(bgzfExtra[:4])

func compressBound(srcLen int) int {
	return srcLen + srcLen>>12 + srcLen>>14 + srcLen>>25 + 13 + minFrame
}

func init() {
	if compressBound(BlockSize) > MaxBlockSize {
		panic("bam: BlockSize too large")
	}
}

var (
	NewBlock         = egzip.NewBlock
	ErrClosed        = errors.New("bgzf: use of closed writer")
	ErrBlockOverflow = errors.New("bgzf: block overflow")
	ErrWrongFileType = errors.New("bgzf: file is a directory")
)

type Reader struct {
	gzip.Header
	gz  *egzip.Reader
	err error
}

func CheckEOF(f *os.File) (bool, error) {
	fi, err := f.Stat()
	if err != nil || fi.IsDir() {
		return false, ErrWrongFileType
	}

	b := make([]byte, len(magicBlock))
	_, err = f.ReadAt(b, fi.Size()-int64(len(magicBlock)))
	if err != nil {
		return false, err
	}
	for i := range b {
		if b[i] != magicBlock[i] {
			return false, nil
		}
	}
	return true, nil
}

func NewReader(r io.Reader, limited bool) (*Reader, error) {
	bg := &Reader{}
	gz, err := egzip.NewReader(r, &bg.Header)
	if err != nil {
		return nil, err
	}
	gz.BlockLimited = limited
	bg.gz = gz
	return bg, nil
}

type Offset struct {
	File  int64
	Block uint16
}

func (bg *Reader) Seek(off Offset, whence int) error {
	bg.err = bg.gz.Seek(off.File, whence)
	if bg.err != nil {
		return bg.err
	}
	if off.Block > 0 {
		_, bg.err = io.CopyN(ioutil.Discard, bg.gz, int64(off.Block))
	}
	return bg.err
}

func (bg *Reader) Close() error {
	return bg.gz.Close()
}

func (bg *Reader) Read(p []byte) (int, error) {
	if bg.err != nil {
		return 0, bg.err
	}
	n, err := bg.gz.Read(p)
	if n < len(p) && err == nil {
		var pn int
		pn, err = bg.Read(p[n:])
		n += pn
	}
	if n > 0 && err == io.EOF {
		err = nil
	}
	return n, err
}

func (bg *Reader) CurrBlockSize() (int, error) {
	if bg.err != nil {
		return -1, bg.err
	}
	i := bytes.Index(bg.Extra, bgzfExtraPrefix)
	if i+5 >= len(bg.Extra) {
		return -1, gzip.ErrHeader
	}
	return (int(bg.Extra[i+4]) | int(bg.Extra[i+5])<<8) + 1, nil
}

type Writer struct {
	gzip.Header
	w io.Writer

	queue chan *worker
	qwg   sync.WaitGroup

	pool

	wg sync.WaitGroup

	closed bool

	m   sync.Mutex
	err error
}

type pool struct {
	active  chan *worker
	waiting chan *worker
}

func NewWriter(w io.Writer, wc int) *Writer {
	return NewWriterLevel(w, gzip.DefaultCompression, wc)
}

func NewWriterLevel(w io.Writer, level, wc int) *Writer {
	if wc < 2 {
		wc = 2
	}
	bg := &Writer{
		w: w,
		pool: pool{
			active:  make(chan *worker, 1),
			waiting: make(chan *worker, wc),
		},
		queue: make(chan *worker, wc),
	}

	wp := make([]worker, wc)
	for i := range wp {
		wp[i].Header = &bg.Header
		wp[i].level = level
		wp[i].pool = bg.pool
		wp[i].flush = make(chan *worker, 1)
		wp[i].qwg = &bg.qwg
		bg.waiting <- &wp[i]
	}
	bg.active <- <-bg.waiting

	bg.wg.Add(1)
	go func() {
		defer bg.wg.Done()
		for qw := range bg.queue {
			if !writeOK(bg, <-qw.flush) {
				break
			}
		}
		if bg.err == nil {
			writeOK(bg, <-bg.active)
		}
	}()

	return bg
}

func writeOK(bg *Writer, wk *worker) bool {
	defer func() { bg.waiting <- wk }()

	if wk.err != nil {
		bg.setErr(wk.err)
		return false
	}
	if wk.buf.Len() == 0 {
		return true
	}

	_, err := io.Copy(bg.w, &wk.buf)
	bg.qwg.Done()
	if err != nil {
		bg.setErr(err)
		return false
	}
	wk.next = 0

	return true
}

type worker struct {
	*gzip.Header
	gz    *egzip.Writer
	level int

	next  int
	block [BlockSize]byte
	buf   bytes.Buffer

	flush chan *worker
	qwg   *sync.WaitGroup

	pool

	err error
}

func (wk *worker) writeBlock() {
	wk.active <- <-wk.waiting
	defer func() { wk.flush <- wk }()

	if wk.gz == nil {
		wk.gz, wk.err = egzip.NewWriterLevel(&wk.buf, wk.level)
		if wk.err != nil {
			return
		}
	} else {
		wk.gz.Reset(&wk.buf)
	}
	wk.gz.Header = gzip.Header{
		Comment: wk.Comment,
		Extra:   append([]byte(bgzfExtra), wk.Extra...),
		ModTime: wk.ModTime,
		Name:    wk.Name,
		OS:      wk.OS,
	}

	_, wk.err = wk.gz.Write(wk.block[:wk.next])
	if wk.err != nil {
		return
	}
	wk.err = wk.gz.Close()
	if wk.err != nil {
		return
	}
	wk.next = 0

	b := wk.buf.Bytes()
	i := bytes.Index(b, bgzfExtraPrefix)
	if i < 0 {
		wk.err = gzip.ErrHeader
		return
	}
	size := len(b) - 1
	if size >= MaxBlockSize {
		wk.err = ErrBlockOverflow
		return
	}
	b[i+4], b[i+5] = byte(size), byte(size>>8)
}

func (bg *Writer) Next() (int, error) {
	if bg.closed {
		return 0, ErrClosed
	}
	if err := bg.Error(); err != nil {
		return 0, err
	}

	wk := <-bg.active
	bg.active <- wk

	return wk.next, nil
}

func (bg *Writer) Write(b []byte) (int, error) {
	if bg.closed {
		return 0, ErrClosed
	}
	err := bg.Error()
	if err != nil {
		return 0, err
	}

	wk := <-bg.active
	var n int
	for ; len(b) > 0 && err == nil; err = bg.Error() {
		var _n int
		if wk.next == 0 || wk.next+len(b) <= len(wk.block) {
			_n = copy(wk.block[wk.next:], b)
			b = b[_n:]
			wk.next += _n
		}

		if wk.next == len(wk.block) || _n == 0 {
			n += wk.buf.Len()
			bg.queue <- wk
			bg.qwg.Add(1)
			go wk.writeBlock()
			wk = <-bg.active
		}
	}
	bg.active <- wk

	return n, bg.Error()
}

func (bg *Writer) Flush() error {
	if bg.closed {
		return ErrClosed
	}
	if err := bg.Error(); err != nil {
		return err
	}

	wk := <-bg.active
	if wk.next == 0 {
		bg.active <- wk
		return nil
	}

	bg.queue <- wk
	bg.qwg.Add(1)
	go wk.writeBlock()

	return bg.Error()
}

func (bg *Writer) Wait() error {
	if err := bg.Error(); err != nil {
		return err
	}
	bg.qwg.Wait()
	return bg.Error()
}

func (bg *Writer) Error() error {
	bg.m.Lock()
	defer bg.m.Unlock()
	return bg.err
}

func (bg *Writer) setErr(err error) {
	bg.m.Lock()
	defer bg.m.Unlock()
	if bg.err == nil {
		bg.err = err
	}
}

func (bg *Writer) Close() error {
	if !bg.closed {
		wk := <-bg.active
		bg.queue <- wk
		bg.qwg.Add(1)
		wk.writeBlock()
		bg.closed = true
		close(bg.queue)
		bg.wg.Wait()
		if bg.err == nil {
			_, bg.err = bg.w.Write([]byte(magicBlock))
		}
	}
	return bg.err
}
<commit_msg>Rename worker to compressor<commit_after>// Copyright ©2012 The bíogo.bam Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bgzf

import (
	"code.google.com/p/biogo.bam/bgzf/egzip"

	"bytes"
	"compress/gzip"
	"errors"
	"io"
	"io/ioutil"
	"os"
	"sync"
)

const (
	BlockSize    = 0x0ff00 // Size of input data block.
	MaxBlockSize = 0x10000 // Maximum size of output block.
)

const (
	bgzfExtra = "BC\x02\x00\x00\x00"
	minFrame  = 20 + len(bgzfExtra) // Minimum bgzf header+footer length.

	// Magic EOF block.
	magicBlock = "\x1f\x8b\x08\x04\x00\x00\x00\x00\x00\xff\x06\x00\x42\x43\x02\x00\x1b\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00"
)

var bgzfExtraPrefix = []byte(bgzfExtra[:4])

func compressBound(srcLen int) int {
	return srcLen + srcLen>>12 + srcLen>>14 + srcLen>>25 + 13 + minFrame
}

func init() {
	if compressBound(BlockSize) > MaxBlockSize {
		panic("bam: BlockSize too large")
	}
}

var (
	NewBlock         = egzip.NewBlock
	ErrClosed        = errors.New("bgzf: use of closed writer")
	ErrBlockOverflow = errors.New("bgzf: block overflow")
	ErrWrongFileType = errors.New("bgzf: file is a directory")
)

type Reader struct {
	gzip.Header
	gz  *egzip.Reader
	err error
}

func CheckEOF(f *os.File) (bool, error) {
	fi, err := f.Stat()
	if err != nil || fi.IsDir() {
		return false, ErrWrongFileType
	}

	b := make([]byte, len(magicBlock))
	_, err = f.ReadAt(b, fi.Size()-int64(len(magicBlock)))
	if err != nil {
		return false, err
	}
	for i := range b {
		if b[i] != magicBlock[i] {
			return false, nil
		}
	}
	return true, nil
}

func NewReader(r io.Reader, limited bool) (*Reader, error) {
	bg := &Reader{}
	gz, err := egzip.NewReader(r, &bg.Header)
	if err != nil {
		return nil, err
	}
	gz.BlockLimited = limited
	bg.gz = gz
	return bg, nil
}

type Offset struct {
	File  int64
	Block uint16
}

func (bg *Reader) Seek(off Offset, whence int) error {
	bg.err = bg.gz.Seek(off.File, whence)
	if bg.err != nil {
		return bg.err
	}
	if off.Block > 0 {
		_, bg.err = io.CopyN(ioutil.Discard, bg.gz, int64(off.Block))
	}
	return bg.err
}

func (bg *Reader) Close() error {
	return bg.gz.Close()
}

func (bg *Reader) Read(p []byte) (int, error) {
	if bg.err != nil {
		return 0, bg.err
	}
	n, err := bg.gz.Read(p)
	if n < len(p) && err == nil {
		var pn int
		pn, err = bg.Read(p[n:])
		n += pn
	}
	if n > 0 && err == io.EOF {
		err = nil
	}
	return n, err
}

func (bg *Reader) CurrBlockSize() (int, error) {
	if bg.err != nil {
		return -1, bg.err
	}
	i := bytes.Index(bg.Extra, bgzfExtraPrefix)
	if i+5 >= len(bg.Extra) {
		return -1, gzip.ErrHeader
	}
	return (int(bg.Extra[i+4]) | int(bg.Extra[i+5])<<8) + 1, nil
}

type Writer struct {
	gzip.Header
	w io.Writer

	queue chan *compressor
	qwg   sync.WaitGroup

	pool

	wg sync.WaitGroup

	closed bool

	m   sync.Mutex
	err error
}

type pool struct {
	active  chan *compressor
	waiting chan *compressor
}

func NewWriter(w io.Writer, wc int) *Writer {
	return NewWriterLevel(w, gzip.DefaultCompression, wc)
}

func NewWriterLevel(w io.Writer, level, wc int) *Writer {
	if wc < 2 {
		wc = 2
	}
	bg := &Writer{
		w: w,
		pool: pool{
			active:  make(chan *compressor, 1),
			waiting: make(chan *compressor, wc),
		},
		queue: make(chan *compressor, wc),
	}

	c := make([]compressor, wc)
	for i := range c {
		c[i].Header = &bg.Header
		c[i].level = level
		c[i].pool = bg.pool
		c[i].flush = make(chan *compressor, 1)
		c[i].qwg = &bg.qwg
		bg.waiting <- &c[i]
	}
	bg.active <- <-bg.waiting

	bg.wg.Add(1)
	go func() {
		defer bg.wg.Done()
		for qw := range bg.queue {
			if !writeOK(bg, <-qw.flush) {
				break
			}
		}
		if bg.err == nil {
			writeOK(bg, <-bg.active)
		}
	}()

	return bg
}

func writeOK(bg *Writer, c *compressor) bool {
	defer func() { bg.waiting <- c }()

	if c.err != nil {
		bg.setErr(c.err)
		return false
	}
	if c.buf.Len() == 0 {
		return true
	}

	_, err := io.Copy(bg.w, &c.buf)
	bg.qwg.Done()
	if err != nil {
		bg.setErr(err)
		return false
	}
	c.next = 0

	return true
}

type compressor struct {
	*gzip.Header
	gz    *egzip.Writer
	level int

	next  int
	block [BlockSize]byte
	buf   bytes.Buffer

	flush chan *compressor
	qwg   *sync.WaitGroup

	pool

	err error
}

func (c *compressor) writeBlock() {
	c.active <- <-c.waiting
	defer func() { c.flush <- c }()

	if c.gz == nil {
		c.gz, c.err = egzip.NewWriterLevel(&c.buf, c.level)
		if c.err != nil {
			return
		}
	} else {
		c.gz.Reset(&c.buf)
	}
	c.gz.Header = gzip.Header{
		Comment: c.Comment,
		Extra:   append([]byte(bgzfExtra), c.Extra...),
		ModTime: c.ModTime,
		Name:    c.Name,
		OS:      c.OS,
	}

	_, c.err = c.gz.Write(c.block[:c.next])
	if c.err != nil {
		return
	}
	c.err = c.gz.Close()
	if c.err != nil {
		return
	}
	c.next = 0

	b := c.buf.Bytes()
	i := bytes.Index(b, bgzfExtraPrefix)
	if i < 0 {
		c.err = gzip.ErrHeader
		return
	}
	size := len(b) - 1
	if size >= MaxBlockSize {
		c.err = ErrBlockOverflow
		return
	}
	b[i+4], b[i+5] = byte(size), byte(size>>8)
}

func (bg *Writer) Next() (int, error) {
	if bg.closed {
		return 0, ErrClosed
	}
	if err := bg.Error(); err != nil {
		return 0, err
	}

	c := <-bg.active
	bg.active <- c

	return c.next, nil
}

func (bg *Writer) Write(b []byte) (int, error) {
	if bg.closed {
		return 0, ErrClosed
	}
	err := bg.Error()
	if err != nil {
		return 0, err
	}

	c := <-bg.active
	var n int
	for ; len(b) > 0 && err == nil; err = bg.Error() {
		var _n int
		if c.next == 0 || c.next+len(b) <= len(c.block) {
			_n = copy(c.block[c.next:], b)
			b = b[_n:]
			c.next += _n
		}

		if c.next == len(c.block) || _n == 0 {
			n += c.buf.Len()
			bg.queue <- c
			bg.qwg.Add(1)
			go c.writeBlock()
			c = <-bg.active
		}
	}
	bg.active <- c

	return n, bg.Error()
}

func (bg *Writer) Flush() error {
	if bg.closed {
		return ErrClosed
	}
	if err := bg.Error(); err != nil {
		return err
	}

	c := <-bg.active
	if c.next == 0 {
		bg.active <- c
		return nil
	}

	bg.queue <- c
	bg.qwg.Add(1)
	go c.writeBlock()

	return bg.Error()
}

func (bg *Writer) Wait() error {
	if err := bg.Error(); err != nil {
		return err
	}
	bg.qwg.Wait()
	return bg.Error()
}

func (bg *Writer) Error() error {
	bg.m.Lock()
	defer bg.m.Unlock()
	return bg.err
}

func (bg *Writer) setErr(err error) {
	bg.m.Lock()
	defer bg.m.Unlock()
	if bg.err == nil {
		bg.err = err
	}
}

func (bg *Writer) Close() error {
	if !bg.closed {
		c := <-bg.active
		bg.queue <- c
		bg.qwg.Add(1)
		c.writeBlock()
		bg.closed = true
		close(bg.queue)
		bg.wg.Wait()
		if bg.err == nil {
			_, bg.err = bg.w.Write([]byte(magicBlock))
		}
	}
	return bg.err
}
<|endoftext|>


<commit_before>package telegram

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

func onReaction(b *Bot, callbackQuery *tgbotapi.CallbackQuery) {
	var callbackText string

	_type, _id, reaction, err := saveReaction(b.redis, callbackQuery.Data, callbackQuery.From.ID)
	if err == nil {
		diss := b.redis.SCard(buildReactionKey(_type, _id, "diss")).Val()
		like := b.redis.SCard(buildReactionKey(_type, _id, "like")).Val()
		if diss-like < 2 {
			msg := tgbotapi.NewEditMessageReplyMarkup(
				callbackQuery.Message.Chat.ID,
				callbackQuery.Message.MessageID,
				buildLikeButton(b.redis, _type, _id),
			)
			_, err = b.Client.Send(msg)
		} else {
			delMsg := tgbotapi.DeleteMessageConfig{
				ChatID:    callbackQuery.Message.Chat.ID,
				MessageID: callbackQuery.Message.MessageID,
			}
			_, err = b.Client.DeleteMessage(delMsg)
			if err == nil {
				err = b.probate(_type, _id)
			}
		}
	}

	if err != nil {
		b.logger.Debugf("%s", err)
		callbackText = err.Error()
	} else {
		callbackText = reaction + " " + _id + "!"
	}

	callbackMsg := tgbotapi.NewCallback(callbackQuery.ID, callbackText)
	_, err = b.Client.AnswerCallbackQuery(callbackMsg)
	if err != nil {
		b.logger.Errorf("%s", err)
	}
}

func onReactionCandidate(b *Bot, callbackQuery *tgbotapi.CallbackQuery) {
	token := strings.Split(callbackQuery.Data, ":")
	if len(token) != 3 {
		b.logger.Errorf("react data error: %s", callbackQuery.Data)
		return
	}

	_id := token[1]
	id, err := strconv.ParseUint(_id, 10, 0)
	if err != nil {
		b.logger.Errorf("failed parsing pixiv id (%s): %s", err, callbackQuery.Data)
		return
	}
	reaction := token[2]

	var newText, callbackText string
	switch reaction {
	case "like":
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%s", err)
			callbackText = "get btd error: " + err.Error()
			break
		}
		newText = fmt.Sprintf("%s recruited:\n%s",
			callbackQuery.From,
			callbackQuery.Message.Text,
		)
		data, err := json.Marshal(DownloadPixiv{
			ChatID:    callbackQuery.Message.Chat.ID,
			MessageID: callbackQuery.Message.MessageID,
			PixivID:   id,
			Text:      newText,
		})
		if err != nil {
			b.logger.Errorf("%s", err)
			callbackText = "marshal message error: " + err.Error()
			break
		}
		err = conn.Use(tgPixivTube)
		if err != nil {
			b.logger.Errorf("%s", err)
			callbackText = "use tube error: " + err.Error()
			break
		}
		_, err = conn.Put(data, 1, 0, 10*time.Minute)
		if err != nil {
			callbackText = fmt.Sprintf("queue pixiv error: %s", err)
		} else {
			callbackText = fmt.Sprintf("queued: %d", id)
		}

		newText = fmt.Sprintf("%s recruited:\n%s",
			callbackQuery.From,
			"⏳ "+callbackQuery.Message.Text,
		)

	case "diss":
		newText = fmt.Sprintf("%s expelled pixiv %d", callbackQuery.From, id)

	default:
		b.logger.Errorf("react type error: %s", reaction)
		return
	}

	callbackMsg := tgbotapi.NewCallback(callbackQuery.ID, callbackText)
	_, err = b.Client.AnswerCallbackQuery(callbackMsg)
	if err != nil {
		b.logger.Errorf("%s", err)
	}

	updateTextMsg := tgbotapi.NewEditMessageText(
		callbackQuery.Message.Chat.ID,
		callbackQuery.Message.MessageID,
		newText,
	)
	updateTextMsg.DisableWebPagePreview = true
	_, err = b.Client.Send(updateTextMsg)
	if err != nil {
		b.logger.Errorf("error update message text %s", err)
	}
}

func buildReactionData(_type, _id, reaction string) string {
	return _type + ":" + _id + ":" + reaction
}
func buildReactionKey(_type, _id, reaction string) string {
	return "reaction_" + buildReactionData(_type, _id, reaction)
}

func buildLikeButton(rds *redis.Client, _type, _id string) tgbotapi.InlineKeyboardMarkup {

	likeCount, _ := rds.SCard(buildReactionKey(_type, _id, "like")).Result()
	dissCount, _ := rds.SCard(buildReactionKey(_type, _id, "diss")).Result()

	likeText := "❤"
	if likeCount > 0 {
		likeText = likeText + " " + strconv.FormatInt(likeCount, 10)
	}
	dissText := "💔"
	if dissCount > 0 {
		dissText = dissText + " " + strconv.FormatInt(dissCount, 10)
	}

	row := tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData(likeText, buildReactionData(_type, _id, "like")),
		tgbotapi.NewInlineKeyboardButtonData("♻", buildReactionData(_type, _id, "reset")),
		tgbotapi.NewInlineKeyboardButtonData(dissText, buildReactionData(_type, _id, "diss")),
	)
	return tgbotapi.NewInlineKeyboardMarkup(row)
}

func saveReaction(rds *redis.Client, key string, user int) (_type, _id, reaction string, err error) {
	token := strings.Split(key, ":")
	if len(token) != 3 {
		err = fmt.Errorf("react data error: %s", key)
		return
	}
	_type = token[0]
	_id = token[1]
	reaction = token[2]

	pipe := rds.Pipeline()
	switch reaction {
	case "like":
		likeCount := pipe.SAdd(buildReactionKey(_type, _id, "like"), strconv.Itoa(user))
		dissCount := pipe.SRem(buildReactionKey(_type, _id, "diss"), strconv.Itoa(user))
		_, err = pipe.Exec()
		if err == nil {
			if likeCount.Val()+dissCount.Val() == 0 {
				err = fmt.Errorf("not modified")
			}
		}
	case "diss":
		dissCount := pipe.SAdd(buildReactionKey(_type, _id, "diss"), strconv.Itoa(user))
		likeCount := pipe.SRem(buildReactionKey(_type, _id, "like"), strconv.Itoa(user))
		_, err = pipe.Exec()
		if err == nil {
			if likeCount.Val()+dissCount.Val() == 0 {
				err = fmt.Errorf("not modified")
			}
		}
	case "reset":
		dissCount := pipe.SRem(buildReactionKey(_type, _id, "diss"), strconv.Itoa(user))
		likeCount := pipe.SRem(buildReactionKey(_type, _id, "like"), strconv.Itoa(user))
		_, err = pipe.Exec()
		if err == nil {
			if likeCount.Val()+dissCount.Val() == 0 {
				err = fmt.Errorf("not modified")
			}
		}
	default:
		err = fmt.Errorf("react type error: %s", key)
	}
	return
}
<commit_msg>chore: wait 10s before download<commit_after>package telegram

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

func onReaction(b *Bot, callbackQuery *tgbotapi.CallbackQuery) {
	var callbackText string

	_type, _id, reaction, err := saveReaction(b.redis, callbackQuery.Data, callbackQuery.From.ID)
	if err == nil {
		diss := b.redis.SCard(buildReactionKey(_type, _id, "diss")).Val()
		like := b.redis.SCard(buildReactionKey(_type, _id, "like")).Val()
		if diss-like < 2 {
			msg := tgbotapi.NewEditMessageReplyMarkup(
				callbackQuery.Message.Chat.ID,
				callbackQuery.Message.MessageID,
				buildLikeButton(b.redis, _type, _id),
			)
			_, err = b.Client.Send(msg)
		} else {
			delMsg := tgbotapi.DeleteMessageConfig{
				ChatID:    callbackQuery.Message.Chat.ID,
				MessageID: callbackQuery.Message.MessageID,
			}
			_, err = b.Client.DeleteMessage(delMsg)
			if err == nil {
				err = b.probate(_type, _id)
			}
		}
	}

	if err != nil {
		b.logger.Debugf("%s", err)
		callbackText = err.Error()
	} else {
		callbackText = reaction + " " + _id + "!"
	}

	callbackMsg := tgbotapi.NewCallback(callbackQuery.ID, callbackText)
	_, err = b.Client.AnswerCallbackQuery(callbackMsg)
	if err != nil {
		b.logger.Errorf("%s", err)
	}
}

func onReactionCandidate(b *Bot, callbackQuery *tgbotapi.CallbackQuery) {
	token := strings.Split(callbackQuery.Data, ":")
	if len(token) != 3 {
		b.logger.Errorf("react data error: %s", callbackQuery.Data)
		return
	}

	_id := token[1]
	id, err := strconv.ParseUint(_id, 10, 0)
	if err != nil {
		b.logger.Errorf("failed parsing pixiv id (%s): %s", err, callbackQuery.Data)
		return
	}
	reaction := token[2]

	var newText, callbackText string
	switch reaction {
	case "like":
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%s", err)
			callbackText = "get btd error: " + err.Error()
			break
		}
		newText = fmt.Sprintf("%s recruited:\n%s",
			callbackQuery.From,
			callbackQuery.Message.Text,
		)
		data, err := json.Marshal(DownloadPixiv{
			ChatID:    callbackQuery.Message.Chat.ID,
			MessageID: callbackQuery.Message.MessageID,
			PixivID:   id,
			Text:      newText,
		})
		if err != nil {
			b.logger.Errorf("%s", err)
			callbackText = "marshal message error: " + err.Error()
			break
		}
		err = conn.Use(tgPixivTube)
		if err != nil {
			b.logger.Errorf("%s", err)
			callbackText = "use tube error: " + err.Error()
			break
		}
		_, err = conn.Put(data, 1, 10*time.Second, 10*time.Minute)
		if err != nil {
			callbackText = fmt.Sprintf("queue pixiv error: %s", err)
		} else {
			callbackText = fmt.Sprintf("queued: %d", id)
		}

		newText = fmt.Sprintf("%s recruited:\n%s",
			callbackQuery.From,
			"⏳ "+callbackQuery.Message.Text,
		)

	case "diss":
		newText = fmt.Sprintf("%s expelled pixiv %d", callbackQuery.From, id)

	default:
		b.logger.Errorf("react type error: %s", reaction)
		return
	}

	callbackMsg := tgbotapi.NewCallback(callbackQuery.ID, callbackText)
	_, err = b.Client.AnswerCallbackQuery(callbackMsg)
	if err != nil {
		b.logger.Errorf("%s", err)
	}

	updateTextMsg := tgbotapi.NewEditMessageText(
		callbackQuery.Message.Chat.ID,
		callbackQuery.Message.MessageID,
		newText,
	)
	updateTextMsg.DisableWebPagePreview = true
	_, err = b.Client.Send(updateTextMsg)
	if err != nil {
		b.logger.Errorf("error update message text %s", err)
	}
}

func buildReactionData(_type, _id, reaction string) string {
	return _type + ":" + _id + ":" + reaction
}
func buildReactionKey(_type, _id, reaction string) string {
	return "reaction_" + buildReactionData(_type, _id, reaction)
}

func buildLikeButton(rds *redis.Client, _type, _id string) tgbotapi.InlineKeyboardMarkup {

	likeCount, _ := rds.SCard(buildReactionKey(_type, _id, "like")).Result()
	dissCount, _ := rds.SCard(buildReactionKey(_type, _id, "diss")).Result()

	likeText := "❤"
	if likeCount > 0 {
		likeText = likeText + " " + strconv.FormatInt(likeCount, 10)
	}
	dissText := "💔"
	if dissCount > 0 {
		dissText = dissText + " " + strconv.FormatInt(dissCount, 10)
	}

	row := tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData(likeText, buildReactionData(_type, _id, "like")),
		tgbotapi.NewInlineKeyboardButtonData("♻", buildReactionData(_type, _id, "reset")),
		tgbotapi.NewInlineKeyboardButtonData(dissText, buildReactionData(_type, _id, "diss")),
	)
	return tgbotapi.NewInlineKeyboardMarkup(row)
}

func saveReaction(rds *redis.Client, key string, user int) (_type, _id, reaction string, err error) {
	token := strings.Split(key, ":")
	if len(token) != 3 {
		err = fmt.Errorf("react data error: %s", key)
		return
	}
	_type = token[0]
	_id = token[1]
	reaction = token[2]

	pipe := rds.Pipeline()
	switch reaction {
	case "like":
		likeCount := pipe.SAdd(buildReactionKey(_type, _id, "like"), strconv.Itoa(user))
		dissCount := pipe.SRem(buildReactionKey(_type, _id, "diss"), strconv.Itoa(user))
		_, err = pipe.Exec()
		if err == nil {
			if likeCount.Val()+dissCount.Val() == 0 {
				err = fmt.Errorf("not modified")
			}
		}
	case "diss":
		dissCount := pipe.SAdd(buildReactionKey(_type, _id, "diss"), strconv.Itoa(user))
		likeCount := pipe.SRem(buildReactionKey(_type, _id, "like"), strconv.Itoa(user))
		_, err = pipe.Exec()
		if err == nil {
			if likeCount.Val()+dissCount.Val() == 0 {
				err = fmt.Errorf("not modified")
			}
		}
	case "reset":
		dissCount := pipe.SRem(buildReactionKey(_type, _id, "diss"), strconv.Itoa(user))
		likeCount := pipe.SRem(buildReactionKey(_type, _id, "like"), strconv.Itoa(user))
		_, err = pipe.Exec()
		if err == nil {
			if likeCount.Val()+dissCount.Val() == 0 {
				err = fmt.Errorf("not modified")
			}
		}
	default:
		err = fmt.Errorf("react type error: %s", key)
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"log"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/resolver"
)

func main() {
	log.SetFlags(log.Lshortfile)

	// TODO configurable path?  perhaps allow for an optional *.dsc instead?
	con, err := control.ParseControlFile("debian/control")
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	// TODO configurable or something
	suite := "unstable"
	arch := "amd64"
	index, err := resolver.GetBinaryIndex(
		"http://httpredir.debian.org/debian",
		suite,
		"main",
		arch,
	)
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	allCan := true
	allPossi := append(
		con.Source.BuildDepends.GetAllPossibilities(),
		con.Source.BuildDependsIndep.GetAllPossibilities()...,
	)

	depArch, err := dependency.ParseArch("any")
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	allBins := []control.BinaryIndex{}
	for _, possi := range allPossi {
		can, why, bins := index.ExplainSatisfies(*depArch, possi)
		if !can {
			log.Printf("%s: %s\n", possi.Name, why)
			allCan = false
		} else {
			// TODO more smarts for which dep out of bins to use
			allBins = append(allBins, bins[0])
		}
	}

	if !allCan {
		log.Fatalf("Unsatisfied possi; exiting.\n")
	}
}
<commit_msg>Check incoming too<commit_after>package main

import (
	"log"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/resolver"
)

func main() {
	log.SetFlags(log.Lshortfile)

	// TODO configurable path?  perhaps allow for an optional *.dsc instead?
	con, err := control.ParseControlFile("debian/control")
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	// TODO configurable or something
	suite := "unstable"
	arch := "amd64"
	index, err := resolver.GetBinaryIndex(
		"http://httpredir.debian.org/debian",
		suite,
		"main",
		arch,
	)
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	incoming, err := resolver.GetBinaryIndex(
		"http://incoming.debian.org/debian-buildd",
		"buildd-"+suite,
		"main",
		arch,
	)
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	allPossi := append(
		con.Source.BuildDepends.GetAllPossibilities(),
		con.Source.BuildDependsIndep.GetAllPossibilities()...,
	)

	depArch, err := dependency.ParseArch("any")
	if err != nil {
		log.Fatalf("error: %v\n", err)
	}

	for _, possi := range allPossi {
		can, why, _ := index.ExplainSatisfies(*depArch, possi)
		if !can {
			inCan, _, _ := incoming.ExplainSatisfies(*depArch, possi)
			if !inCan {
				log.Printf("%s: %s\n", possi.Name, why)
			} else {
				log.Printf("%s: in incoming!\n", possi.Name)
			}
		}
	}
}
<|endoftext|>