<commit_before>package parser

// #include "query_types.h"
// #include <stdlib.h>
import "C"

import (
	"common"
	"fmt"
	"math"
	"reflect"
	"regexp"
	"time"
	"unsafe"
)

type From struct {
	TableName string
}

type Operation int

type ValueType int

const (
	ValueRegex        ValueType = C.VALUE_REGEX
	ValueInt                    = C.VALUE_INT
	ValueFloat                  = C.VALUE_FLOAT
	ValueString                 = C.VALUE_STRING
	ValueTableName              = C.VALUE_TABLE_NAME
	ValueSimpleName             = C.VALUE_SIMPLE_NAME
	ValueDuration               = C.VALUE_DURATION
	ValueWildcard               = C.VALUE_WILDCARD
	ValueFunctionCall           = C.VALUE_FUNCTION_CALL
	ValueExpression             = C.VALUE_EXPRESSION
)

type Value struct {
	Name          string
	Type          ValueType
	Elems         []*Value
	compiledRegex *regexp.Regexp
}

func (self *Value) IsFunctionCall() bool {
	return self.Type == ValueFunctionCall
}

func (self *Value) GetCompiledRegex() (*regexp.Regexp, bool) {
	return self.compiledRegex, self.Type == ValueRegex
}

type FromClauseType int

const (
	FromClauseArray     FromClauseType = C.FROM_ARRAY
	FromClauseMerge     FromClauseType = C.FROM_MERGE
	FromClauseInnerJoin FromClauseType = C.FROM_INNER_JOIN
)

type TableName struct {
	Name  *Value
	Alias string
}

func (self *TableName) GetAlias() string {
	if self.Alias != "" {
		return self.Alias
	}
	return self.Name.Name
}

type FromClause struct {
	Type  FromClauseType
	Names []*TableName
}

type GroupByClause struct {
	FillWithZero bool
	FillValue    *Value
	Elems        []*Value
}

func (self GroupByClause) GetGroupByTime() (*time.Duration, error) {
	for _, groupBy := range self.Elems {
		if groupBy.IsFunctionCall() {
			// TODO: check the number of arguments and return an error
			if len(groupBy.Elems) != 1 {
				return nil, common.NewQueryError(common.WrongNumberOfArguments, "time function only accepts one argument")
			}
			// TODO: check the function name
			// TODO: error checking
			arg := groupBy.Elems[0].Name
			duration, err := time.ParseDuration(arg)
			if err != nil {
				return nil, common.NewQueryError(common.InvalidArgument, fmt.Sprintf("invalid argument %s to the time function", arg))
			}
			return &duration, nil
		}
	}
	return nil, nil
}

type WhereCondition struct {
	isBooleanExpression bool
	Left                interface{}
	Operation           string
	Right               *WhereCondition
}

func (self *WhereCondition) GetBoolExpression() (*Value, bool) {
	if self.isBooleanExpression {
		return self.Left.(*Value), true
	}
	return nil, false
}

func (self *WhereCondition) GetLeftWhereCondition() (*WhereCondition, bool) {
	if !self.isBooleanExpression {
		return self.Left.(*WhereCondition), true
	}
	return nil, false
}

type BasicQuery struct {
	queryString string
	startTime   time.Time
	endTime     time.Time
}

type SelectDeleteCommonQuery struct {
	BasicQuery
	FromClause *FromClause
	Condition  *WhereCondition
}

type SelectQuery struct {
	SelectDeleteCommonQuery
	ColumnNames   []*Value
	groupByClause *GroupByClause
	Limit         int
	Ascending     bool
}

type DeleteQuery struct {
	SelectDeleteCommonQuery
}

type Query struct {
	SelectQuery *SelectQuery
	DeleteQuery *DeleteQuery
}

func (self *Query) GetQueryString() string {
	if self.SelectQuery != nil {
		return self.SelectQuery.GetQueryString()
	}
	return self.DeleteQuery.GetQueryString()
}

func (self *BasicQuery) GetQueryString() string {
	return self.queryString
}

func (self *SelectQuery) GetColumnNames() []*Value {
	return self.ColumnNames
}

func (self *SelectDeleteCommonQuery) GetFromClause() *FromClause {
	return self.FromClause
}

func setupSlice(hdr *reflect.SliceHeader, ptr unsafe.Pointer, size C.size_t) {
	hdr.Cap = int(size)
	hdr.Len = int(size)
	hdr.Data = uintptr(ptr)
}

func GetGroupByClause(groupByClause *C.groupby_clause) (*GroupByClause, error) {
	if groupByClause == nil {
		return &GroupByClause{Elems: nil}, nil
	}

	values, err := GetValueArray(groupByClause.elems)
	if err != nil {
		return nil, err
	}

	fillWithZero := false
	var fillValue *Value

	if groupByClause.fill_function != nil {
		fun, err := GetValue(groupByClause.fill_function)
		if err != nil {
			return nil, err
		}
		if fun.Name != "fill" {
			return nil, fmt.Errorf("You can't use %s with group by", fun.Name)
		}

		if len(fun.Elems) != 1 {
			return nil, fmt.Errorf("`fill` accepts one argument only")
		}

		fillValue = fun.Elems[0]
		fillWithZero = true
	}

	return &GroupByClause{
		Elems:        values,
		FillWithZero: fillWithZero,
		FillValue:    fillValue,
	}, nil
}

func GetValueArray(array *C.value_array) ([]*Value, error) {
	if array == nil {
		return nil, nil
	}

	var values []*C.value
	setupSlice((*reflect.SliceHeader)((unsafe.Pointer(&values))), unsafe.Pointer(array.elems), array.size)

	valuesSlice := make([]*Value, 0, array.size)

	for _, value := range values {
		value, err := GetValue(value)
		if err != nil {
			return nil, err
		}
		valuesSlice = append(valuesSlice, value)
	}
	return valuesSlice, nil
}

func GetStringArray(array *C.array) []string {
	if array == nil {
		return nil
	}

	var values []*C.char
	setupSlice((*reflect.SliceHeader)((unsafe.Pointer(&values))), unsafe.Pointer(array.elems), array.size)

	stringSlice := make([]string, 0, array.size)

	for _, value := range values {
		stringSlice = append(stringSlice, C.GoString(value))
	}
	return stringSlice
}

func GetValue(value *C.value) (*Value, error) {
	v := &Value{}
	v.Name = C.GoString(value.name)
	var err error
	v.Elems, err = GetValueArray(value.args)
	if err != nil {
		return nil, err
	}
	v.Type = ValueType(value.value_type)
	isCaseInsensitive := value.is_case_insensitive != 0
	if v.Type == ValueRegex {
		if isCaseInsensitive {
			v.compiledRegex, err = regexp.Compile("(?i)" + v.Name)
		} else {
			v.compiledRegex, err = regexp.Compile(v.Name)
		}
	}
	return v, err
}

func GetTableName(name *C.table_name) (*TableName, error) {
	value, err := GetValue(name.name)
	if err != nil {
		return nil, err
	}

	table := &TableName{Name: value}
	if name.alias != nil {
		table.Alias = C.GoString(name.alias)
	}

	return table, nil
}

func GetTableNameArray(array *C.table_name_array) ([]*TableName, error) {
	var names []*C.table_name
	setupSlice((*reflect.SliceHeader)((unsafe.Pointer(&names))), unsafe.Pointer(array.elems), array.size)

	tableNamesSlice := make([]*TableName, 0, array.size)
	for _, name := range names {
		tableName, err := GetTableName(name)
		if err != nil {
			return nil, err
		}
		tableNamesSlice = append(tableNamesSlice, tableName)
	}
	return tableNamesSlice, nil
}

func GetFromClause(fromClause *C.from_clause) (*FromClause, error) {
	arr, err := GetTableNameArray(fromClause.names)
	if err != nil {
		return nil, err
	}
	return &FromClause{FromClauseType(fromClause.from_clause_type), arr}, nil
}

// func GetExpression(expr *C.expression) (*Expression, error) {
// 	expression := &Expression{}
// 	if expr.op == 0 {
// 		value, err := GetValue((*C.value)(expr.left))
// 		if err != nil {
// 			return nil, err
// 		}
// 		expression.Left = value
// 		expression.Operation = byte(expr.op)
// 		expression.Right = nil
// 	} else if expr.op == 1 {
// 		value, err := GetValueArray((*C.value_array)(expr.left))
// 		if err != nil {
// 			return nil, err
// 		}
// 		expression.Left = value
// 		expression.Operation = byte(expr.op)
// 		expression.Right = nil
// 	} else {
// 		var err error
// 		expression.Left, err = GetExpression((*C.expression)(expr.left))
// 		if err != nil {
// 			return nil, err
// 		}
// 		expression.Operation = byte(expr.op)
// 		expression.Right, err = GetExpression((*C.expression)(unsafe.Pointer(expr.right)))
// 		if err != nil {
// 			return nil, err
// 		}
// 	}

// 	return expression, nil
// }

// func GetBoolExpression(expr *C.bool_expression) (*BoolExpression, error) {
// 	boolExpression := &BoolExpression{}
// 	var err error
// 	boolExpression.Left, err = GetExpression(expr.left)
// 	if err != nil {
// 		return nil, err
// 	}
// 	if expr.op != nil {
// 		boolExpression.Operation = C.GoString(expr.op)
// 		boolExpression.Right, err = GetExpression(expr.right)
// 	}

// 	return boolExpression, err
// }

func GetWhereCondition(condition *C.condition) (*WhereCondition, error) {
	if condition.is_bool_expression != 0 {
		expr, err := GetValue((*C.value)(condition.left))
		if err != nil {
			return nil, err
		}
		return &WhereCondition{
			isBooleanExpression: true,
			Left:                expr,
			Operation:           "",
			Right:               nil,
		}, nil
	}

	c := &WhereCondition{}
	var err error
	c.Left, err = GetWhereCondition((*C.condition)(condition.left))
	if err != nil {
		return nil, err
	}
	c.Operation = C.GoString(condition.op)
	c.Right, err = GetWhereCondition((*C.condition)(unsafe.Pointer(condition.right)))

	return c, err
}

func (self *SelectDeleteCommonQuery) GetWhereCondition() *WhereCondition {
	return self.Condition
}

func (self *SelectQuery) GetGroupByClause() *GroupByClause {
	return self.groupByClause
}

// This is just for backward compatability so we don't have
// to change all the code.
func ParseSelectQuery(query string) (*SelectQuery, error) {
	queries, err := ParseQuery(query)
	if err != nil {
		return nil, err
	}

	if len(queries) == 0 {
		return nil, fmt.Errorf("No queries found")
	}

	selectQuery := queries[0].SelectQuery
	if selectQuery == nil {
		return nil, fmt.Errorf("Query isn't a select query: '%s'", queries[0].GetQueryString())
	}

	return selectQuery, nil
}

func ParseQuery(query string) ([]*Query, error) {
	queryString := C.CString(query)
	defer C.free(unsafe.Pointer(queryString))
	q := C.parse_query(queryString)
	defer C.close_query(&q)

	if q.error != nil {
		str := C.GoString(q.error.err)
		err := fmt.Errorf("Error at %d:%d. %s", q.error.line, q.error.column, str)
		return nil, err
	}

	if q.select_query != nil {
		selectQuery, err := parseSelectQuery(query, q.select_query)
		if err != nil {
			return nil, err
		}
		return []*Query{&Query{SelectQuery: selectQuery}}, nil
	}
	deleteQuery, err := parseDeleteQuery(query, q.delete_query)
	if err != nil {
		return nil, err
	}
	return []*Query{&Query{DeleteQuery: deleteQuery}}, nil
}

func parseSelectDeleteCommonQuery(queryString string, fromClause *C.from_clause, whereCondition *C.condition) (SelectDeleteCommonQuery, error) {

	goQuery := SelectDeleteCommonQuery{
		BasicQuery: BasicQuery{
			queryString: queryString,
			startTime:   time.Unix(0, 0),
			endTime:     time.Now(),
		},
	}

	var err error

	// get the from clause
	goQuery.FromClause, err = GetFromClause(fromClause)
	if err != nil {
		return goQuery, err
	}

	// get the where condition
	if whereCondition != nil {
		goQuery.Condition, err = GetWhereCondition(whereCondition)
		if err != nil {
			return goQuery, err
		}
	}

	var startTime, endTime time.Time

	goQuery.Condition, endTime, err = getTime(goQuery.GetWhereCondition(), false)
	if err != nil {
		return goQuery, err
	}

	if endTime.Unix() > 0 {
		goQuery.endTime = endTime
	}

	goQuery.Condition, startTime, err = getTime(goQuery.GetWhereCondition(), true)
	if err != nil {
		return goQuery, err
	}

	if startTime.Unix() > 0 {
		goQuery.startTime = startTime
	} else if goQuery.endTime.Unix() > 0 {
		goQuery.startTime = time.Unix(math.MinInt64, 0)
	}

	return goQuery, nil
}

func parseSelectQuery(queryString string, q *C.select_query) (*SelectQuery, error) {
	limit := q.limit
	if limit == -1 {
		// no limit by default
		limit = 0
	}

	basicQurey, err := parseSelectDeleteCommonQuery(queryString, q.from_clause, q.where_condition)
	if err != nil {
		return nil, err
	}
	goQuery := &SelectQuery{
		SelectDeleteCommonQuery: basicQurey,
		Limit:     int(limit),
		Ascending: q.ascending != 0,
	}

	// get the column names
	goQuery.ColumnNames, err = GetValueArray(q.c)
	if err != nil {
		return nil, err
	}

	// get the group by clause
	if q.group_by == nil {
		goQuery.groupByClause = &GroupByClause{}
	} else {
		goQuery.groupByClause, err = GetGroupByClause(q.group_by)
		if err != nil {
			return nil, err
		}
	}

	return goQuery, nil
}

func parseDeleteQuery(queryString string, query *C.delete_query) (*DeleteQuery, error) {
	basicQurey, err := parseSelectDeleteCommonQuery(queryString, query.from_clause, query.where_condition)
	if err != nil {
		return nil, err
	}
	goQuery := &DeleteQuery{
		SelectDeleteCommonQuery: basicQurey,
	}
	if basicQurey.GetWhereCondition() != nil {
		return nil, fmt.Errorf("Delete queries can't have where clause that don't reference time")
	}
	return goQuery, nil
}
<commit_msg>remove some commented code<commit_after>package parser

// #include "query_types.h"
// #include <stdlib.h>
import "C"

import (
	"common"
	"fmt"
	"math"
	"reflect"
	"regexp"
	"time"
	"unsafe"
)

type From struct {
	TableName string
}

type Operation int

type ValueType int

const (
	ValueRegex        ValueType = C.VALUE_REGEX
	ValueInt                    = C.VALUE_INT
	ValueFloat                  = C.VALUE_FLOAT
	ValueString                 = C.VALUE_STRING
	ValueTableName              = C.VALUE_TABLE_NAME
	ValueSimpleName             = C.VALUE_SIMPLE_NAME
	ValueDuration               = C.VALUE_DURATION
	ValueWildcard               = C.VALUE_WILDCARD
	ValueFunctionCall           = C.VALUE_FUNCTION_CALL
	ValueExpression             = C.VALUE_EXPRESSION
)

type Value struct {
	Name          string
	Type          ValueType
	Elems         []*Value
	compiledRegex *regexp.Regexp
}

func (self *Value) IsFunctionCall() bool {
	return self.Type == ValueFunctionCall
}

func (self *Value) GetCompiledRegex() (*regexp.Regexp, bool) {
	return self.compiledRegex, self.Type == ValueRegex
}

type FromClauseType int

const (
	FromClauseArray     FromClauseType = C.FROM_ARRAY
	FromClauseMerge     FromClauseType = C.FROM_MERGE
	FromClauseInnerJoin FromClauseType = C.FROM_INNER_JOIN
)

type TableName struct {
	Name  *Value
	Alias string
}

func (self *TableName) GetAlias() string {
	if self.Alias != "" {
		return self.Alias
	}
	return self.Name.Name
}

type FromClause struct {
	Type  FromClauseType
	Names []*TableName
}

type GroupByClause struct {
	FillWithZero bool
	FillValue    *Value
	Elems        []*Value
}

func (self GroupByClause) GetGroupByTime() (*time.Duration, error) {
	for _, groupBy := range self.Elems {
		if groupBy.IsFunctionCall() {
			// TODO: check the number of arguments and return an error
			if len(groupBy.Elems) != 1 {
				return nil, common.NewQueryError(common.WrongNumberOfArguments, "time function only accepts one argument")
			}
			// TODO: check the function name
			// TODO: error checking
			arg := groupBy.Elems[0].Name
			duration, err := time.ParseDuration(arg)
			if err != nil {
				return nil, common.NewQueryError(common.InvalidArgument, fmt.Sprintf("invalid argument %s to the time function", arg))
			}
			return &duration, nil
		}
	}
	return nil, nil
}

type WhereCondition struct {
	isBooleanExpression bool
	Left                interface{}
	Operation           string
	Right               *WhereCondition
}

func (self *WhereCondition) GetBoolExpression() (*Value, bool) {
	if self.isBooleanExpression {
		return self.Left.(*Value), true
	}
	return nil, false
}

func (self *WhereCondition) GetLeftWhereCondition() (*WhereCondition, bool) {
	if !self.isBooleanExpression {
		return self.Left.(*WhereCondition), true
	}
	return nil, false
}

type BasicQuery struct {
	queryString string
	startTime   time.Time
	endTime     time.Time
}

type SelectDeleteCommonQuery struct {
	BasicQuery
	FromClause *FromClause
	Condition  *WhereCondition
}

type SelectQuery struct {
	SelectDeleteCommonQuery
	ColumnNames   []*Value
	groupByClause *GroupByClause
	Limit         int
	Ascending     bool
}

type DeleteQuery struct {
	SelectDeleteCommonQuery
}

type Query struct {
	SelectQuery *SelectQuery
	DeleteQuery *DeleteQuery
}

func (self *Query) GetQueryString() string {
	if self.SelectQuery != nil {
		return self.SelectQuery.GetQueryString()
	}
	return self.DeleteQuery.GetQueryString()
}

func (self *BasicQuery) GetQueryString() string {
	return self.queryString
}

func (self *SelectQuery) GetColumnNames() []*Value {
	return self.ColumnNames
}

func (self *SelectDeleteCommonQuery) GetFromClause() *FromClause {
	return self.FromClause
}

func setupSlice(hdr *reflect.SliceHeader, ptr unsafe.Pointer, size C.size_t) {
	hdr.Cap = int(size)
	hdr.Len = int(size)
	hdr.Data = uintptr(ptr)
}

func GetGroupByClause(groupByClause *C.groupby_clause) (*GroupByClause, error) {
	if groupByClause == nil {
		return &GroupByClause{Elems: nil}, nil
	}

	values, err := GetValueArray(groupByClause.elems)
	if err != nil {
		return nil, err
	}

	fillWithZero := false
	var fillValue *Value

	if groupByClause.fill_function != nil {
		fun, err := GetValue(groupByClause.fill_function)
		if err != nil {
			return nil, err
		}
		if fun.Name != "fill" {
			return nil, fmt.Errorf("You can't use %s with group by", fun.Name)
		}

		if len(fun.Elems) != 1 {
			return nil, fmt.Errorf("`fill` accepts one argument only")
		}

		fillValue = fun.Elems[0]
		fillWithZero = true
	}

	return &GroupByClause{
		Elems:        values,
		FillWithZero: fillWithZero,
		FillValue:    fillValue,
	}, nil
}

func GetValueArray(array *C.value_array) ([]*Value, error) {
	if array == nil {
		return nil, nil
	}

	var values []*C.value
	setupSlice((*reflect.SliceHeader)((unsafe.Pointer(&values))), unsafe.Pointer(array.elems), array.size)

	valuesSlice := make([]*Value, 0, array.size)

	for _, value := range values {
		value, err := GetValue(value)
		if err != nil {
			return nil, err
		}
		valuesSlice = append(valuesSlice, value)
	}
	return valuesSlice, nil
}

func GetStringArray(array *C.array) []string {
	if array == nil {
		return nil
	}

	var values []*C.char
	setupSlice((*reflect.SliceHeader)((unsafe.Pointer(&values))), unsafe.Pointer(array.elems), array.size)

	stringSlice := make([]string, 0, array.size)

	for _, value := range values {
		stringSlice = append(stringSlice, C.GoString(value))
	}
	return stringSlice
}

func GetValue(value *C.value) (*Value, error) {
	v := &Value{}
	v.Name = C.GoString(value.name)
	var err error
	v.Elems, err = GetValueArray(value.args)
	if err != nil {
		return nil, err
	}
	v.Type = ValueType(value.value_type)
	isCaseInsensitive := value.is_case_insensitive != 0
	if v.Type == ValueRegex {
		if isCaseInsensitive {
			v.compiledRegex, err = regexp.Compile("(?i)" + v.Name)
		} else {
			v.compiledRegex, err = regexp.Compile(v.Name)
		}
	}
	return v, err
}

func GetTableName(name *C.table_name) (*TableName, error) {
	value, err := GetValue(name.name)
	if err != nil {
		return nil, err
	}

	table := &TableName{Name: value}
	if name.alias != nil {
		table.Alias = C.GoString(name.alias)
	}

	return table, nil
}

func GetTableNameArray(array *C.table_name_array) ([]*TableName, error) {
	var names []*C.table_name
	setupSlice((*reflect.SliceHeader)((unsafe.Pointer(&names))), unsafe.Pointer(array.elems), array.size)

	tableNamesSlice := make([]*TableName, 0, array.size)
	for _, name := range names {
		tableName, err := GetTableName(name)
		if err != nil {
			return nil, err
		}
		tableNamesSlice = append(tableNamesSlice, tableName)
	}
	return tableNamesSlice, nil
}

func GetFromClause(fromClause *C.from_clause) (*FromClause, error) {
	arr, err := GetTableNameArray(fromClause.names)
	if err != nil {
		return nil, err
	}
	return &FromClause{FromClauseType(fromClause.from_clause_type), arr}, nil
}

func GetWhereCondition(condition *C.condition) (*WhereCondition, error) {
	if condition.is_bool_expression != 0 {
		expr, err := GetValue((*C.value)(condition.left))
		if err != nil {
			return nil, err
		}
		return &WhereCondition{
			isBooleanExpression: true,
			Left:                expr,
			Operation:           "",
			Right:               nil,
		}, nil
	}

	c := &WhereCondition{}
	var err error
	c.Left, err = GetWhereCondition((*C.condition)(condition.left))
	if err != nil {
		return nil, err
	}
	c.Operation = C.GoString(condition.op)
	c.Right, err = GetWhereCondition((*C.condition)(unsafe.Pointer(condition.right)))

	return c, err
}

func (self *SelectDeleteCommonQuery) GetWhereCondition() *WhereCondition {
	return self.Condition
}

func (self *SelectQuery) GetGroupByClause() *GroupByClause {
	return self.groupByClause
}

// This is just for backward compatability so we don't have
// to change all the code.
func ParseSelectQuery(query string) (*SelectQuery, error) {
	queries, err := ParseQuery(query)
	if err != nil {
		return nil, err
	}

	if len(queries) == 0 {
		return nil, fmt.Errorf("No queries found")
	}

	selectQuery := queries[0].SelectQuery
	if selectQuery == nil {
		return nil, fmt.Errorf("Query isn't a select query: '%s'", queries[0].GetQueryString())
	}

	return selectQuery, nil
}

func ParseQuery(query string) ([]*Query, error) {
	queryString := C.CString(query)
	defer C.free(unsafe.Pointer(queryString))
	q := C.parse_query(queryString)
	defer C.close_query(&q)

	if q.error != nil {
		str := C.GoString(q.error.err)
		err := fmt.Errorf("Error at %d:%d. %s", q.error.line, q.error.column, str)
		return nil, err
	}

	if q.select_query != nil {
		selectQuery, err := parseSelectQuery(query, q.select_query)
		if err != nil {
			return nil, err
		}
		return []*Query{&Query{SelectQuery: selectQuery}}, nil
	}
	deleteQuery, err := parseDeleteQuery(query, q.delete_query)
	if err != nil {
		return nil, err
	}
	return []*Query{&Query{DeleteQuery: deleteQuery}}, nil
}

func parseSelectDeleteCommonQuery(queryString string, fromClause *C.from_clause, whereCondition *C.condition) (SelectDeleteCommonQuery, error) {

	goQuery := SelectDeleteCommonQuery{
		BasicQuery: BasicQuery{
			queryString: queryString,
			startTime:   time.Unix(0, 0),
			endTime:     time.Now(),
		},
	}

	var err error

	// get the from clause
	goQuery.FromClause, err = GetFromClause(fromClause)
	if err != nil {
		return goQuery, err
	}

	// get the where condition
	if whereCondition != nil {
		goQuery.Condition, err = GetWhereCondition(whereCondition)
		if err != nil {
			return goQuery, err
		}
	}

	var startTime, endTime time.Time

	goQuery.Condition, endTime, err = getTime(goQuery.GetWhereCondition(), false)
	if err != nil {
		return goQuery, err
	}

	if endTime.Unix() > 0 {
		goQuery.endTime = endTime
	}

	goQuery.Condition, startTime, err = getTime(goQuery.GetWhereCondition(), true)
	if err != nil {
		return goQuery, err
	}

	if startTime.Unix() > 0 {
		goQuery.startTime = startTime
	} else if goQuery.endTime.Unix() > 0 {
		goQuery.startTime = time.Unix(math.MinInt64, 0)
	}

	return goQuery, nil
}

func parseSelectQuery(queryString string, q *C.select_query) (*SelectQuery, error) {
	limit := q.limit
	if limit == -1 {
		// no limit by default
		limit = 0
	}

	basicQurey, err := parseSelectDeleteCommonQuery(queryString, q.from_clause, q.where_condition)
	if err != nil {
		return nil, err
	}
	goQuery := &SelectQuery{
		SelectDeleteCommonQuery: basicQurey,
		Limit:     int(limit),
		Ascending: q.ascending != 0,
	}

	// get the column names
	goQuery.ColumnNames, err = GetValueArray(q.c)
	if err != nil {
		return nil, err
	}

	// get the group by clause
	if q.group_by == nil {
		goQuery.groupByClause = &GroupByClause{}
	} else {
		goQuery.groupByClause, err = GetGroupByClause(q.group_by)
		if err != nil {
			return nil, err
		}
	}

	return goQuery, nil
}

func parseDeleteQuery(queryString string, query *C.delete_query) (*DeleteQuery, error) {
	basicQurey, err := parseSelectDeleteCommonQuery(queryString, query.from_clause, query.where_condition)
	if err != nil {
		return nil, err
	}
	goQuery := &DeleteQuery{
		SelectDeleteCommonQuery: basicQurey,
	}
	if basicQurey.GetWhereCondition() != nil {
		return nil, fmt.Errorf("Delete queries can't have where clause that don't reference time")
	}
	return goQuery, nil
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 main

import (
	"bytes"
	"expvar"
	"flag"
	"fmt"
	"http"
	"io"
	"log"
	"os"
	"strconv"
)

// hello world, the web server
var helloRequests = expvar.NewInt("hello-requests")

func HelloServer(w http.ResponseWriter, req *http.Request) {
	helloRequests.Add(1)
	io.WriteString(w, "hello, world!\n")
}

// Simple counter server. POSTing to it will set the value.
type Counter struct {
	n int
}

// This makes Counter satisfy the expvar.Var interface, so we can export
// it directly.
func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "GET":
		ctr.n++
	case "POST":
		buf := new(bytes.Buffer)
		io.Copy(buf, req.Body)
		body := buf.String()
		if n, err := strconv.Atoi(body); err != nil {
			fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body)
		} else {
			ctr.n = n
			fmt.Fprint(w, "counter reset\n")
		}
	}
	fmt.Fprintf(w, "counter = %d\n", ctr.n)
}

// simple flag server
var booleanflag = flag.Bool("boolean", true, "another flag for testing")

func FlagServer(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprint(w, "Flags:\n")
	flag.VisitAll(func(f *flag.Flag) {
		if f.Value.String() != f.DefValue {
			fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue)
		} else {
			fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String())
		}
	})
}

// simple argument server
func ArgServer(w http.ResponseWriter, req *http.Request) {
	for _, s := range os.Args {
		fmt.Fprint(w, s, " ")
	}
}

// a channel (just for the fun of it)
type Chan chan int

func ChanCreate() Chan {
	c := make(Chan)
	go func(c Chan) {
		for x := 0; ; x++ {
			c <- x
		}
	}(c)
	return c
}

func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch))
}

// exec a program, redirecting output
func DateServer(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
	r, w, err := os.Pipe()
	if err != nil {
		fmt.Fprintf(rw, "pipe: %s\n", err)
		return
	}

	p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
	defer r.Close()
	w.Close()
	if err != nil {
		fmt.Fprintf(rw, "fork/exec: %s\n", err)
		return
	}
	defer p.Release()
	io.Copy(rw, r)
	wait, err := p.Wait(0)
	if err != nil {
		fmt.Fprintf(rw, "wait: %s\n", err)
		return
	}
	if !wait.Exited() || wait.ExitStatus() != 0 {
		fmt.Fprintf(rw, "date: %v\n", wait)
		return
	}
}

func Logger(w http.ResponseWriter, req *http.Request) {
	log.Print(req.URL.Raw)
	w.WriteHeader(404)
	w.Write([]byte("oops"))
}

var webroot = flag.String("root", "/home/rsc", "web root directory")

func main() {
	flag.Parse()

	// The counter is published as a variable directly.
	ctr := new(Counter)
	http.Handle("/counter", ctr)
	expvar.Publish("counter", ctr)

	http.Handle("/", http.HandlerFunc(Logger))
	http.Handle("/go/", http.FileServer(*webroot, "/go/"))
	http.Handle("/flags", http.HandlerFunc(FlagServer))
	http.Handle("/args", http.HandlerFunc(ArgServer))
	http.Handle("/go/hello", http.HandlerFunc(HelloServer))
	http.Handle("/chan", ChanCreate())
	http.Handle("/date", http.HandlerFunc(DateServer))
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Panicln("ListenAndServe:", err)
	}
}
<commit_msg>http: update triv.go with gofix<commit_after>// Copyright 2009 The Go 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 main

import (
	"bytes"
	"expvar"
	"flag"
	"fmt"
	"http"
	"io"
	"log"
	"os"
	"strconv"
)

// hello world, the web server
var helloRequests = expvar.NewInt("hello-requests")

func HelloServer(w http.ResponseWriter, req *http.Request) {
	helloRequests.Add(1)
	io.WriteString(w, "hello, world!\n")
}

// Simple counter server. POSTing to it will set the value.
type Counter struct {
	n int
}

// This makes Counter satisfy the expvar.Var interface, so we can export
// it directly.
func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "GET":
		ctr.n++
	case "POST":
		buf := new(bytes.Buffer)
		io.Copy(buf, req.Body)
		body := buf.String()
		if n, err := strconv.Atoi(body); err != nil {
			fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body)
		} else {
			ctr.n = n
			fmt.Fprint(w, "counter reset\n")
		}
	}
	fmt.Fprintf(w, "counter = %d\n", ctr.n)
}

// simple flag server
var booleanflag = flag.Bool("boolean", true, "another flag for testing")

func FlagServer(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprint(w, "Flags:\n")
	flag.VisitAll(func(f *flag.Flag) {
		if f.Value.String() != f.DefValue {
			fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue)
		} else {
			fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String())
		}
	})
}

// simple argument server
func ArgServer(w http.ResponseWriter, req *http.Request) {
	for _, s := range os.Args {
		fmt.Fprint(w, s, " ")
	}
}

// a channel (just for the fun of it)
type Chan chan int

func ChanCreate() Chan {
	c := make(Chan)
	go func(c Chan) {
		for x := 0; ; x++ {
			c <- x
		}
	}(c)
	return c
}

func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch))
}

// exec a program, redirecting output
func DateServer(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
	r, w, err := os.Pipe()
	if err != nil {
		fmt.Fprintf(rw, "pipe: %s\n", err)
		return
	}

	p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
	defer r.Close()
	w.Close()
	if err != nil {
		fmt.Fprintf(rw, "fork/exec: %s\n", err)
		return
	}
	defer p.Release()
	io.Copy(rw, r)
	wait, err := p.Wait(0)
	if err != nil {
		fmt.Fprintf(rw, "wait: %s\n", err)
		return
	}
	if !wait.Exited() || wait.ExitStatus() != 0 {
		fmt.Fprintf(rw, "date: %v\n", wait)
		return
	}
}

func Logger(w http.ResponseWriter, req *http.Request) {
	log.Print(req.URL.Raw)
	w.WriteHeader(404)
	w.Write([]byte("oops"))
}

var webroot = flag.String("root", "/home/rsc", "web root directory")

func main() {
	flag.Parse()

	// The counter is published as a variable directly.
	ctr := new(Counter)
	http.Handle("/counter", ctr)
	expvar.Publish("counter", ctr)

	http.Handle("/", http.HandlerFunc(Logger))
	http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
	http.Handle("/flags", http.HandlerFunc(FlagServer))
	http.Handle("/args", http.HandlerFunc(ArgServer))
	http.Handle("/go/hello", http.HandlerFunc(HelloServer))
	http.Handle("/chan", ChanCreate())
	http.Handle("/date", http.HandlerFunc(DateServer))
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Panicln("ListenAndServe:", err)
	}
}
<|endoftext|>


<commit_before>package meter

import (
	"encoding/json"
	"sort"
	"time"

	"github.com/go-redis/redis"
)

type Record struct {
	Name   string
	Time   time.Time
	Key    string
	Field  string
	Labels []string
	Result *redis.StringCmd
}

func (r *Record) Value() int64 {
	if r.Result != nil {
		if n, err := r.Result.Int64(); err == nil {
			return n
		}
	}
	return 0
}

func (r *Record) MarshalJSON() ([]byte, error) {
	obj := make(map[string]interface{})
	for i := 0; i < len(r.Labels); i += 2 {
		if k, v := r.Labels[i], r.Labels[i+1]; v != "*" {
			obj[k] = v
		}
	}
	obj["value"] = r.Value()
	obj["time"] = r.Time.String()
	obj["name"] = r.Name

	return json.Marshal(obj)
}

func ReadRecords(r redis.UniversalClient, records []*Record) error {
	pipeline := r.Pipeline()
	defer pipeline.Close()
	for _, r := range records {
		r.Result = pipeline.HGet(r.Key, r.Field)
	}
	_, err := pipeline.Exec()
	if err == redis.Nil {
		return nil
	}
	return err
}

type RecordSequence []*Record

func (s RecordSequence) Results() []*Result {
	grouped := make(map[string]*Result)
	for _, r := range s {
		key := r.Name + ":" + r.Field
		result, ok := grouped[key]
		if !ok {
			result = &Result{
				Event:  r.Name,
				Labels: Labels(r.Labels).Map(),
				Data:   make([]DataPoint, 0, len(s)),
			}
			grouped[key] = result
		}
		result.Data = append(result.Data, DataPoint{r.Time.Unix(), r.Value()})
	}
	results := make([]*Result, len(grouped))
	i := 0
	for _, r := range grouped {
		sort.Slice(r.Data, func(i, j int) bool {
			return r.Data[i].Value < r.Data[j].Value
		})
		results[i] = r
		i++
	}
	return results
}

func (s RecordSequence) Group() []*Result {
	grouped := make(map[string]*Result)
sloop:
	for _, r := range s {
		key := r.Name
		result, ok := grouped[key]
		if !ok {
			result = &Result{
				Event: r.Name,
				Data:  make([]DataPoint, 0, len(s)),
			}
			grouped[key] = result
		}
		t := r.Time.Unix()
		v := r.Value()
		for i, d := range result.Data {
			if d.Timestamp == t {
				result.Data[i].Value += v
				continue sloop
			}
		}
		result.Data = append(result.Data, DataPoint{t, v})
	}
	results := make([]*Result, len(grouped))
	i := 0
	for _, r := range grouped {
		sort.Slice(r.Data, func(i, j int) bool {
			return r.Data[i].Value < r.Data[j].Value
		})
		results[i] = r
		i++
	}
	return results

}
<commit_msg>Change Record.MarshalJSON() for compatibility<commit_after>package meter

import (
	"encoding/json"
	"sort"
	"time"

	"github.com/go-redis/redis"
)

type Record struct {
	Name   string
	Time   time.Time
	Key    string
	Field  string
	Labels []string
	Result *redis.StringCmd
}

func (r *Record) Value() int64 {
	if r.Result != nil {
		if n, err := r.Result.Int64(); err == nil {
			return n
		}
	}
	return 0
}

func (r *Record) MarshalJSON() ([]byte, error) {
	obj := make(map[string]interface{})
	for i := 0; i < len(r.Labels); i += 2 {
		if k, v := r.Labels[i], r.Labels[i+1]; v != "*" {
			obj[k] = v
		}
	}
	obj["count"] = r.Value()
	obj["time"] = r.Time.String()
	obj["name"] = r.Name

	return json.Marshal(obj)
}

func ReadRecords(r redis.UniversalClient, records []*Record) error {
	pipeline := r.Pipeline()
	defer pipeline.Close()
	for _, r := range records {
		r.Result = pipeline.HGet(r.Key, r.Field)
	}
	_, err := pipeline.Exec()
	if err == redis.Nil {
		return nil
	}
	return err
}

type RecordSequence []*Record

func (s RecordSequence) Results() []*Result {
	grouped := make(map[string]*Result)
	for _, r := range s {
		key := r.Name + ":" + r.Field
		result, ok := grouped[key]
		if !ok {
			result = &Result{
				Event:  r.Name,
				Labels: Labels(r.Labels).Map(),
				Data:   make([]DataPoint, 0, len(s)),
			}
			grouped[key] = result
		}
		result.Data = append(result.Data, DataPoint{r.Time.Unix(), r.Value()})
	}
	results := make([]*Result, len(grouped))
	i := 0
	for _, r := range grouped {
		sort.Slice(r.Data, func(i, j int) bool {
			return r.Data[i].Value < r.Data[j].Value
		})
		results[i] = r
		i++
	}
	return results
}

func (s RecordSequence) Group() []*Result {
	grouped := make(map[string]*Result)
sloop:
	for _, r := range s {
		key := r.Name
		result, ok := grouped[key]
		if !ok {
			result = &Result{
				Event: r.Name,
				Data:  make([]DataPoint, 0, len(s)),
			}
			grouped[key] = result
		}
		t := r.Time.Unix()
		v := r.Value()
		for i, d := range result.Data {
			if d.Timestamp == t {
				result.Data[i].Value += v
				continue sloop
			}
		}
		result.Data = append(result.Data, DataPoint{t, v})
	}
	results := make([]*Result, len(grouped))
	i := 0
	for _, r := range grouped {
		sort.Slice(r.Data, func(i, j int) bool {
			return r.Data[i].Value < r.Data[j].Value
		})
		results[i] = r
		i++
	}
	return results

}
<|endoftext|>


<commit_before>package cloudflare

import (
	"errors"
	"fmt"
	"strings"
)

type RecordsResponse struct {
	Response struct {
		Recs struct {
			Records []Record `json:"objs"`
		} `json:"recs"`
	} `json:"response"`
	Result  string `json:"result"`
	Message string `json:"msg"`
}

func (r *RecordsResponse) FindRecord(id string) (*Record, error) {
	if r.Result == "error" {
		return nil, fmt.Errorf("API Error: %s", r.Message)
	}

	objs := r.Response.Recs.Records
	notFoundErr := errors.New("Record not found")

	// No objects, return nil
	if len(objs) < 0 {
		return nil, notFoundErr
	}

	for _, v := range objs {
		// We have a match, return that
		if v.Id == id {
			return &v, nil
		}
	}

	return nil, notFoundErr
}

func (r *RecordsResponse) FindRecordByName(name string, wildcard bool) ([]Record, error) {
	if r.Result == "error" {
		return nil, fmt.Errorf("API Error: %s", r.Message)
	}

	objs := r.Response.Recs.Records
	notFoundErr := errors.New("Record not found")

	// No objects, return nil
	if len(objs) < 0 {
		return nil, notFoundErr
	}

	var recs []Record
	suffix := "." + name

	for _, v := range objs {
		if v.Name == name {
			recs = append(recs, v)
		} else if wildcard && strings.HasSuffix(v.Name, suffix) {
			recs = append(recs, v)
		}
	}

	return recs, nil
}

type RecordResponse struct {
	Response struct {
		Rec struct {
			Record Record `json:"obj"`
		} `json:"rec"`
	} `json:"response"`
	Result  string `json:"result"`
	Message string `json:"msg"`
}

func (r *RecordResponse) GetRecord() (*Record, error) {
	if r.Result == "error" {
		return nil, fmt.Errorf("API Error: %s", r.Message)
	}

	return &r.Response.Rec.Record, nil
}

// Record is used to represent a retrieved Record. All properties
// are set as strings.
type Record struct {
	Id       string `json:"rec_id"`
	Domain   string `json:"zone_name"`
	Name     string `json:"display_name"`
	FullName string `json:"name"`
	Value    string `json:"content"`
	Type     string `json:"type"`
	Priority string `json:"prio"`
	Ttl      string `json:"ttl"`
}

// CreateRecord contains the request parameters to create a new
// record.
type CreateRecord struct {
	Type     string
	Name     string
	Content  string
	Ttl      string
	Priority string
}

// CreateRecord creates a record from the parameters specified and
// returns an error if it fails. If no error and the name is returned,
// the Record was succesfully created.
func (c *Client) CreateRecord(domain string, opts *CreateRecord) (*Record, error) {
	// Make the request parameters
	params := make(map[string]string)
	params["z"] = domain

	params["type"] = opts.Type

	if opts.Name != "" {
		params["name"] = opts.Name
	}

	if opts.Content != "" {
		params["content"] = opts.Content
	}

	if opts.Priority != "" {
		params["prio"] = opts.Priority
	}

	if opts.Ttl != "" {
		params["ttl"] = opts.Ttl
	} else {
		params["ttl"] = "1"
	}

	req, err := c.NewRequest(params, "POST", "rec_new")
	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))

	if err != nil {
		return nil, fmt.Errorf("Error creating record: %s", err)
	}

	recordResp := new(RecordResponse)

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return nil, fmt.Errorf("Error parsing record response: %s", err)
	}
	record, err := recordResp.GetRecord()
	if err != nil {
		return nil, err
	}

	// The request was successful
	return record, nil
}

// DestroyRecord destroys a record by the ID specified and
// returns an error if it fails. If no error is returned,
// the Record was succesfully destroyed.
func (c *Client) DestroyRecord(domain string, id string) error {
	params := make(map[string]string)

	params["z"] = domain
	params["id"] = id

	req, err := c.NewRequest(params, "POST", "rec_delete")
	if err != nil {
		return err
	}

	resp, err := checkResp(c.Http.Do(req))

	if err != nil {
		return fmt.Errorf("Error deleting record: %s", err)
	}

	recordResp := new(RecordResponse)

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return fmt.Errorf("Error parsing record response: %s", err)
	}
	_, err = recordResp.GetRecord()
	if err != nil {
		return err
	}

	// The request was successful
	return nil
}

// UpdateRecord contains the request parameters to update a
// record.
type UpdateRecord struct {
	Type     string
	Name     string
	Content  string
	Ttl      string
	Priority string
}

// UpdateRecord destroys a record by the ID specified and
// returns an error if it fails. If no error is returned,
// the Record was succesfully updated.
func (c *Client) UpdateRecord(domain string, id string, opts *UpdateRecord) error {
	params := make(map[string]string)
	params["z"] = domain
	params["id"] = id

	params["type"] = opts.Type

	if opts.Name != "" {
		params["name"] = opts.Name
	}

	if opts.Content != "" {
		params["content"] = opts.Content
	}

	if opts.Priority != "" {
		params["prio"] = opts.Priority
	}

	if opts.Ttl != "" {
		params["ttl"] = opts.Ttl
	}

	req, err := c.NewRequest(params, "POST", "rec_edit")
	if err != nil {
		return err
	}

	resp, err := checkResp(c.Http.Do(req))

	if err != nil {
		return fmt.Errorf("Error updating record: %s", err)
	}

	recordResp := new(RecordResponse)

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return fmt.Errorf("Error parsing record response: %s", err)
	}
	_, err = recordResp.GetRecord()
	if err != nil {
		return err
	}

	// The request was successful
	return nil
}

func (c *Client) RetrieveRecordsByName(domain string, name string, wildcard bool) ([]Record, error) {
	params := make(map[string]string)
	// The zone we want
	params["z"] = domain

	req, err := c.NewRequest(params, "GET", "rec_load_all")

	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return nil, fmt.Errorf("Error destroying record: %s", err)
	}

	records := new(RecordsResponse)

	err = decodeBody(resp, records)

	if err != nil {
		return nil, fmt.Errorf("Error decoding record response: %s", err)
	}

	record, err := records.FindRecordByName(name, wildcard)
	if err != nil {
		return nil, err
	}

	// The request was successful
	return record, nil
}

// RetrieveRecord gets  a record by the ID specified and
// returns a Record and an error. An error will be returned for failed
// requests with a nil Record.
func (c *Client) RetrieveRecord(domain string, id string) (*Record, error) {
	params := make(map[string]string)
	// The zone we want
	params["z"] = domain
	params["id"] = id

	req, err := c.NewRequest(params, "GET", "rec_load_all")

	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return nil, fmt.Errorf("Error destroying record: %s", err)
	}

	records := new(RecordsResponse)

	err = decodeBody(resp, records)

	if err != nil {
		return nil, fmt.Errorf("Error decoding record response: %s", err)
	}

	record, err := records.FindRecord(id)
	if err != nil {
		return nil, err
	}

	// The request was successful
	return record, nil
}
<commit_msg>record: fix retrieve logging<commit_after>package cloudflare

import (
	"errors"
	"fmt"
	"strings"
)

type RecordsResponse struct {
	Response struct {
		Recs struct {
			Records []Record `json:"objs"`
		} `json:"recs"`
	} `json:"response"`
	Result  string `json:"result"`
	Message string `json:"msg"`
}

func (r *RecordsResponse) FindRecord(id string) (*Record, error) {
	if r.Result == "error" {
		return nil, fmt.Errorf("API Error: %s", r.Message)
	}

	objs := r.Response.Recs.Records
	notFoundErr := errors.New("Record not found")

	// No objects, return nil
	if len(objs) < 0 {
		return nil, notFoundErr
	}

	for _, v := range objs {
		// We have a match, return that
		if v.Id == id {
			return &v, nil
		}
	}

	return nil, notFoundErr
}

func (r *RecordsResponse) FindRecordByName(name string, wildcard bool) ([]Record, error) {
	if r.Result == "error" {
		return nil, fmt.Errorf("API Error: %s", r.Message)
	}

	objs := r.Response.Recs.Records
	notFoundErr := errors.New("Record not found")

	// No objects, return nil
	if len(objs) < 0 {
		return nil, notFoundErr
	}

	var recs []Record
	suffix := "." + name

	for _, v := range objs {
		if v.Name == name {
			recs = append(recs, v)
		} else if wildcard && strings.HasSuffix(v.Name, suffix) {
			recs = append(recs, v)
		}
	}

	return recs, nil
}

type RecordResponse struct {
	Response struct {
		Rec struct {
			Record Record `json:"obj"`
		} `json:"rec"`
	} `json:"response"`
	Result  string `json:"result"`
	Message string `json:"msg"`
}

func (r *RecordResponse) GetRecord() (*Record, error) {
	if r.Result == "error" {
		return nil, fmt.Errorf("API Error: %s", r.Message)
	}

	return &r.Response.Rec.Record, nil
}

// Record is used to represent a retrieved Record. All properties
// are set as strings.
type Record struct {
	Id       string `json:"rec_id"`
	Domain   string `json:"zone_name"`
	Name     string `json:"display_name"`
	FullName string `json:"name"`
	Value    string `json:"content"`
	Type     string `json:"type"`
	Priority string `json:"prio"`
	Ttl      string `json:"ttl"`
}

// CreateRecord contains the request parameters to create a new
// record.
type CreateRecord struct {
	Type     string
	Name     string
	Content  string
	Ttl      string
	Priority string
}

// CreateRecord creates a record from the parameters specified and
// returns an error if it fails. If no error and the name is returned,
// the Record was succesfully created.
func (c *Client) CreateRecord(domain string, opts *CreateRecord) (*Record, error) {
	// Make the request parameters
	params := make(map[string]string)
	params["z"] = domain

	params["type"] = opts.Type

	if opts.Name != "" {
		params["name"] = opts.Name
	}

	if opts.Content != "" {
		params["content"] = opts.Content
	}

	if opts.Priority != "" {
		params["prio"] = opts.Priority
	}

	if opts.Ttl != "" {
		params["ttl"] = opts.Ttl
	} else {
		params["ttl"] = "1"
	}

	req, err := c.NewRequest(params, "POST", "rec_new")
	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))

	if err != nil {
		return nil, fmt.Errorf("Error creating record: %s", err)
	}

	recordResp := new(RecordResponse)

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return nil, fmt.Errorf("Error parsing record response: %s", err)
	}
	record, err := recordResp.GetRecord()
	if err != nil {
		return nil, err
	}

	// The request was successful
	return record, nil
}

// DestroyRecord destroys a record by the ID specified and
// returns an error if it fails. If no error is returned,
// the Record was succesfully destroyed.
func (c *Client) DestroyRecord(domain string, id string) error {
	params := make(map[string]string)

	params["z"] = domain
	params["id"] = id

	req, err := c.NewRequest(params, "POST", "rec_delete")
	if err != nil {
		return err
	}

	resp, err := checkResp(c.Http.Do(req))

	if err != nil {
		return fmt.Errorf("Error deleting record: %s", err)
	}

	recordResp := new(RecordResponse)

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return fmt.Errorf("Error parsing record response: %s", err)
	}
	_, err = recordResp.GetRecord()
	if err != nil {
		return err
	}

	// The request was successful
	return nil
}

// UpdateRecord contains the request parameters to update a
// record.
type UpdateRecord struct {
	Type     string
	Name     string
	Content  string
	Ttl      string
	Priority string
}

// UpdateRecord destroys a record by the ID specified and
// returns an error if it fails. If no error is returned,
// the Record was succesfully updated.
func (c *Client) UpdateRecord(domain string, id string, opts *UpdateRecord) error {
	params := make(map[string]string)
	params["z"] = domain
	params["id"] = id

	params["type"] = opts.Type

	if opts.Name != "" {
		params["name"] = opts.Name
	}

	if opts.Content != "" {
		params["content"] = opts.Content
	}

	if opts.Priority != "" {
		params["prio"] = opts.Priority
	}

	if opts.Ttl != "" {
		params["ttl"] = opts.Ttl
	}

	req, err := c.NewRequest(params, "POST", "rec_edit")
	if err != nil {
		return err
	}

	resp, err := checkResp(c.Http.Do(req))

	if err != nil {
		return fmt.Errorf("Error updating record: %s", err)
	}

	recordResp := new(RecordResponse)

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return fmt.Errorf("Error parsing record response: %s", err)
	}
	_, err = recordResp.GetRecord()
	if err != nil {
		return err
	}

	// The request was successful
	return nil
}

func (c *Client) RetrieveRecordsByName(domain string, name string, wildcard bool) ([]Record, error) {
	params := make(map[string]string)
	// The zone we want
	params["z"] = domain

	req, err := c.NewRequest(params, "GET", "rec_load_all")

	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return nil, fmt.Errorf("Error destroying record: %s", err)
	}

	records := new(RecordsResponse)

	err = decodeBody(resp, records)

	if err != nil {
		return nil, fmt.Errorf("Error decoding record response: %s", err)
	}

	record, err := records.FindRecordByName(name, wildcard)
	if err != nil {
		return nil, err
	}

	// The request was successful
	return record, nil
}

// RetrieveRecord gets  a record by the ID specified and
// returns a Record and an error. An error will be returned for failed
// requests with a nil Record.
func (c *Client) RetrieveRecord(domain string, id string) (*Record, error) {
	params := make(map[string]string)
	// The zone we want
	params["z"] = domain
	params["id"] = id

	req, err := c.NewRequest(params, "GET", "rec_load_all")

	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return nil, fmt.Errorf("Error retrieving record: %s", err)
	}

	records := new(RecordsResponse)

	err = decodeBody(resp, records)

	if err != nil {
		return nil, fmt.Errorf("Error decoding record response: %s", err)
	}

	record, err := records.FindRecord(id)
	if err != nil {
		return nil, err
	}

	// The request was successful
	return record, nil
}
<|endoftext|>


<commit_before>package vsolver

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"
)

// A remoteRepo represents a potential remote repository resource.
//
// RemoteRepos are based purely on lexical analysis; successfully constructing
// one is not a guarantee that the resource it identifies actually exists or is
// accessible.
type remoteRepo struct {
	Base     string
	RelPkg   string
	CloneURL *url.URL
	Schemes  []string
	VCS      []string
}

//type remoteResult struct {
//r   remoteRepo
//err error
//}

// TODO sync access to this map
//var remoteCache = make(map[string]remoteResult)

// Regexes for the different known import path flavors
var (
	ghRegex      = regexp.MustCompile(`^(?P<root>github\.com/([A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`)
	gpinNewRegex = regexp.MustCompile(`^(?P<root>gopkg\.in/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)(?:\.git))?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`)
	//gpinOldRegex = regexp.MustCompile(`^(?P<root>gopkg\.in/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?))/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
	bbRegex = regexp.MustCompile(`^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`)
	lpRegex = regexp.MustCompile(`^(?P<root>launchpad.net/([A-Za-z0-9-._]+)(/[A-Za-z0-9-._]+)?)(/.+)?`)
	//glpRegex = regexp.MustCompile(`^(?P<root>git\.launchpad\.net/(([A-Za-z0-9_.\-]+)|~[A-Za-z0-9_.\-]+/(\+git|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))$`)
	//gcRegex      = regexp.MustCompile(`^(?P<root>code\.google\.com/[pr]/(?P<project>[a-z0-9\-]+)(\.(?P<subrepo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`)
	jazzRegex    = regexp.MustCompile(`^(?P<root>hub\.jazz\.net/git/[a-z0-9]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`)
	apacheRegex  = regexp.MustCompile(`^(?P<root>git.apache.org/[a-z0-9_.\-]+\.git)(/[A-Za-z0-9_.\-]+)*$`)
	genericRegex = regexp.MustCompile(`^(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/~]*?)\.(?P<vcs>bzr|git|hg|svn))([/A-Za-z0-9_.\-]+)*$`)
)

// Other helper regexes
var (
	scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
	pathvld     = regexp.MustCompile(`^([A-Za-z0-9-]+)(\.[A-Za-z0-9-]+)+(/[A-Za-z0-9-_.~]+)*$`)
)

// deduceRemoteRepo takes a potential import path and returns a RemoteRepo
// representing the remote location of the source of an import path. Remote
// repositories can be bare import paths, or urls including a checkout scheme.
func deduceRemoteRepo(path string) (rr remoteRepo, err error) {
	if m := scpSyntaxRe.FindStringSubmatch(path); m != nil {
		// Match SCP-like syntax and convert it to a URL.
		// Eg, "git@github.com:user/repo" becomes
		// "ssh://git@github.com/user/repo".
		rr.CloneURL = &url.URL{
			Scheme:  "ssh",
			User:    url.User(m[1]),
			Host:    m[2],
			RawPath: m[3],
		}
	} else {
		rr.CloneURL, err = url.Parse(path)
		if err != nil {
			return remoteRepo{}, fmt.Errorf("%q is not a valid import path", path)
		}
	}

	path = rr.CloneURL.Host + rr.CloneURL.Path
	if !pathvld.MatchString(path) {
		return remoteRepo{}, fmt.Errorf("%q is not a valid import path", path)
	}

	if rr.CloneURL.Scheme != "" {
		rr.Schemes = []string{rr.CloneURL.Scheme}
	}

	switch {
	case ghRegex.MatchString(path):
		v := ghRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "github.com"
		rr.CloneURL.Path = v[2]
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[3], "/")
		rr.VCS = []string{"git"}

		return

	case gpinNewRegex.MatchString(path):
		v := gpinNewRegex.FindStringSubmatch(path)

		// Duplicate some logic from the gopkg.in server in order to validate
		// the import path string without having to hit the server
		if strings.Contains(v[4], ".") {
			return remoteRepo{}, fmt.Errorf("%q is not a valid import path; gopkg.in only allows major versions (%q instead of %q)",
				path, v[4][:strings.Index(v[4], ".")], v[4])
		}

		// If the third position is empty, it's the shortened form that expands
		// to the go-pkg github user
		if v[3] != "" {
			rr.CloneURL.Path = "go-pkg/" + v[4]
		} else {
			rr.CloneURL.Path = v[2] + v[4]
		}
		rr.CloneURL.Host = "github.com"
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[6], "/")
		rr.VCS = []string{"git"}

		return
	//case gpinOldRegex.MatchString(path):

	case bbRegex.MatchString(path):
		v := bbRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "bitbucket.org"
		rr.CloneURL.Path = v[2]
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[5], "/")
		rr.VCS = []string{"git", "hg"}

		return

	//case gcRegex.MatchString(path):
	//v := gcRegex.FindStringSubmatch(path)

	//rr.CloneURL.Host = "code.google.com"
	//rr.CloneURL.Path = "p/" + v[2]
	//rr.Base = v[1]
	//rr.RelPkg = strings.TrimPrefix(v[5], "/")
	//rr.VCS = []string{"hg", "git"}

	//return

	case lpRegex.MatchString(path):
		v := lpRegex.FindStringSubmatch(path)
		v = append(v, "", "")

		rr.CloneURL.Host = "launchpad.net"
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[4], "/")
		rr.VCS = []string{"bzr"}

		if v[3] == "" {
			// launchpad.net/project"
			rr.Base = fmt.Sprintf("https://launchpad.net/%v", v[2])
		} else {
			// launchpad.net/project/series"
			rr.Base = fmt.Sprintf("https://launchpad.net/%s/%s", v[2], v[3])
		}
		return

	case jazzRegex.MatchString(path):
		v := jazzRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "hub.jazz.net"
		rr.CloneURL.Path = "git" + v[2]
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[2], "/")
		rr.VCS = []string{"git"}

		return

	case apacheRegex.MatchString(path):
		v := apacheRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "git.apache.org"
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[2], "/")
		rr.VCS = []string{"git"}

		return

	//case glpRegex.MatchString(path):
	//// TODO too many rules for this, commenting out for now
	//v := lpRegex.FindStringSubmatch(path)

	//rr.CloneURL.Host = "launchpad.net"
	//rr.RelPkg = strings.TrimPrefix(v[3], "/")
	//rr.VCS = []string{"git"}

	//v = append(v, "", "")
	//if v[2] == "" {
	//// launchpad.net/project"
	//rr.Base = fmt.Sprintf("https://launchpad.net/%v", v[1])
	//} else {
	//// launchpad.net/project/series"
	//rr.Base = fmt.Sprintf("https://launchpad.net/%s/%s", v[1], v[2])
	//}
	//return

	// try the general syntax
	case genericRegex.MatchString(path):
		v := genericRegex.FindStringSubmatch(path)
		switch v[5] {
		case "git", "hg", "bzr":
			x := strings.SplitN(v[1], "/", 2)
			// TODO is this actually correct for bzr?
			rr.CloneURL.Host = x[0]
			rr.CloneURL.Path = x[1]
			rr.VCS = []string{v[5]}
			rr.Base = v[1]
			rr.RelPkg = strings.TrimPrefix(v[6], "/")
			return
		default:
			return remoteRepo{}, fmt.Errorf("unknown repository type: %q", v[5])
		}
	}

	// TODO use HTTP metadata to resolve vanity imports
	return remoteRepo{}, fmt.Errorf("unable to deduct repository and source type for: %q", path)
}
<commit_msg>Return pointer type from deduceRemoteRepo()<commit_after>package vsolver

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"
)

// A remoteRepo represents a potential remote repository resource.
//
// RemoteRepos are based purely on lexical analysis; successfully constructing
// one is not a guarantee that the resource it identifies actually exists or is
// accessible.
type remoteRepo struct {
	Base     string
	RelPkg   string
	CloneURL *url.URL
	Schemes  []string
	VCS      []string
}

//type remoteResult struct {
//r   remoteRepo
//err error
//}

// TODO sync access to this map
//var remoteCache = make(map[string]remoteResult)

// Regexes for the different known import path flavors
var (
	ghRegex      = regexp.MustCompile(`^(?P<root>github\.com/([A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`)
	gpinNewRegex = regexp.MustCompile(`^(?P<root>gopkg\.in/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)(?:\.git))?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`)
	//gpinOldRegex = regexp.MustCompile(`^(?P<root>gopkg\.in/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?))/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
	bbRegex = regexp.MustCompile(`^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`)
	lpRegex = regexp.MustCompile(`^(?P<root>launchpad.net/([A-Za-z0-9-._]+)(/[A-Za-z0-9-._]+)?)(/.+)?`)
	//glpRegex = regexp.MustCompile(`^(?P<root>git\.launchpad\.net/(([A-Za-z0-9_.\-]+)|~[A-Za-z0-9_.\-]+/(\+git|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))$`)
	//gcRegex      = regexp.MustCompile(`^(?P<root>code\.google\.com/[pr]/(?P<project>[a-z0-9\-]+)(\.(?P<subrepo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`)
	jazzRegex    = regexp.MustCompile(`^(?P<root>hub\.jazz\.net/git/[a-z0-9]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`)
	apacheRegex  = regexp.MustCompile(`^(?P<root>git.apache.org/[a-z0-9_.\-]+\.git)(/[A-Za-z0-9_.\-]+)*$`)
	genericRegex = regexp.MustCompile(`^(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/~]*?)\.(?P<vcs>bzr|git|hg|svn))([/A-Za-z0-9_.\-]+)*$`)
)

// Other helper regexes
var (
	scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
	pathvld     = regexp.MustCompile(`^([A-Za-z0-9-]+)(\.[A-Za-z0-9-]+)+(/[A-Za-z0-9-_.~]+)*$`)
)

// deduceRemoteRepo takes a potential import path and returns a RemoteRepo
// representing the remote location of the source of an import path. Remote
// repositories can be bare import paths, or urls including a checkout scheme.
func deduceRemoteRepo(path string) (rr *remoteRepo, err error) {
	rr = &remoteRepo{}
	if m := scpSyntaxRe.FindStringSubmatch(path); m != nil {
		// Match SCP-like syntax and convert it to a URL.
		// Eg, "git@github.com:user/repo" becomes
		// "ssh://git@github.com/user/repo".
		rr.CloneURL = &url.URL{
			Scheme:  "ssh",
			User:    url.User(m[1]),
			Host:    m[2],
			RawPath: m[3],
		}
	} else {
		rr.CloneURL, err = url.Parse(path)
		if err != nil {
			return nil, fmt.Errorf("%q is not a valid import path", path)
		}
	}

	path = rr.CloneURL.Host + rr.CloneURL.Path
	if !pathvld.MatchString(path) {
		return nil, fmt.Errorf("%q is not a valid import path", path)
	}

	if rr.CloneURL.Scheme != "" {
		rr.Schemes = []string{rr.CloneURL.Scheme}
	}

	switch {
	case ghRegex.MatchString(path):
		v := ghRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "github.com"
		rr.CloneURL.Path = v[2]
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[3], "/")
		rr.VCS = []string{"git"}

		return

	case gpinNewRegex.MatchString(path):
		v := gpinNewRegex.FindStringSubmatch(path)

		// Duplicate some logic from the gopkg.in server in order to validate
		// the import path string without having to hit the server
		if strings.Contains(v[4], ".") {
			return nil, fmt.Errorf("%q is not a valid import path; gopkg.in only allows major versions (%q instead of %q)",
				path, v[4][:strings.Index(v[4], ".")], v[4])
		}

		// If the third position is empty, it's the shortened form that expands
		// to the go-pkg github user
		if v[3] != "" {
			rr.CloneURL.Path = "go-pkg/" + v[4]
		} else {
			rr.CloneURL.Path = v[2] + v[4]
		}
		rr.CloneURL.Host = "github.com"
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[6], "/")
		rr.VCS = []string{"git"}

		return
	//case gpinOldRegex.MatchString(path):

	case bbRegex.MatchString(path):
		v := bbRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "bitbucket.org"
		rr.CloneURL.Path = v[2]
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[5], "/")
		rr.VCS = []string{"git", "hg"}

		return

	//case gcRegex.MatchString(path):
	//v := gcRegex.FindStringSubmatch(path)

	//rr.CloneURL.Host = "code.google.com"
	//rr.CloneURL.Path = "p/" + v[2]
	//rr.Base = v[1]
	//rr.RelPkg = strings.TrimPrefix(v[5], "/")
	//rr.VCS = []string{"hg", "git"}

	//return

	case lpRegex.MatchString(path):
		v := lpRegex.FindStringSubmatch(path)
		v = append(v, "", "")

		rr.CloneURL.Host = "launchpad.net"
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[4], "/")
		rr.VCS = []string{"bzr"}

		if v[3] == "" {
			// launchpad.net/project"
			rr.Base = fmt.Sprintf("https://launchpad.net/%v", v[2])
		} else {
			// launchpad.net/project/series"
			rr.Base = fmt.Sprintf("https://launchpad.net/%s/%s", v[2], v[3])
		}
		return

	case jazzRegex.MatchString(path):
		v := jazzRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "hub.jazz.net"
		rr.CloneURL.Path = "git" + v[2]
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[2], "/")
		rr.VCS = []string{"git"}

		return

	case apacheRegex.MatchString(path):
		v := apacheRegex.FindStringSubmatch(path)

		rr.CloneURL.Host = "git.apache.org"
		rr.Base = v[1]
		rr.RelPkg = strings.TrimPrefix(v[2], "/")
		rr.VCS = []string{"git"}

		return

	//case glpRegex.MatchString(path):
	//// TODO too many rules for this, commenting out for now
	//v := lpRegex.FindStringSubmatch(path)

	//rr.CloneURL.Host = "launchpad.net"
	//rr.RelPkg = strings.TrimPrefix(v[3], "/")
	//rr.VCS = []string{"git"}

	//v = append(v, "", "")
	//if v[2] == "" {
	//// launchpad.net/project"
	//rr.Base = fmt.Sprintf("https://launchpad.net/%v", v[1])
	//} else {
	//// launchpad.net/project/series"
	//rr.Base = fmt.Sprintf("https://launchpad.net/%s/%s", v[1], v[2])
	//}
	//return

	// try the general syntax
	case genericRegex.MatchString(path):
		v := genericRegex.FindStringSubmatch(path)
		switch v[5] {
		case "git", "hg", "bzr":
			x := strings.SplitN(v[1], "/", 2)
			// TODO is this actually correct for bzr?
			rr.CloneURL.Host = x[0]
			rr.CloneURL.Path = x[1]
			rr.VCS = []string{v[5]}
			rr.Base = v[1]
			rr.RelPkg = strings.TrimPrefix(v[6], "/")
			return
		default:
			return nil, fmt.Errorf("unknown repository type: %q", v[5])
		}
	}

	// TODO use HTTP metadata to resolve vanity imports
	return nil, fmt.Errorf("unable to deduct repository and source type for: %q", path)
}
<|endoftext|>


<commit_before>package model

import (
	"io/ioutil"
	"os"
	"time"

	"github.com/tinzenite/shared"
)

/*
localRemove initiates a deletion locally, creating all necessary files and
removing the file from the model.
*/
func (m *Model) localRemove(path *shared.RelativePath) error {
	// get stin for notify
	stin, exists := m.StaticInfos[path.SubPath()]
	if !exists {
		m.log("LocalRemove: stin is missing!")
		return shared.ErrIllegalFileState
	}
	// sanity check
	if m.IsRemoved(stin.Identification) {
		// shouldn't happen but let's be sure; warn at least
		m.warn("LocalRemove: file removal already begun!")
	}
	// direct remove (removes file/dir AND from m.Tracked and m.Static)
	err := m.directRemove(path)
	if err != nil {
		m.log("LocalRemove: failed to directly remove file!")
		return err
	}
	// write peers to check and own peer to done
	err = m.UpdateRemovalDir(stin.Identification, m.SelfID)
	if err != nil {
		m.log("failed to update removal dir for", stin.Identification)
		return err
	}
	// update removal dir here so that creations etc are sent before notify below!
	err = m.updateLocal(m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR + "/" + stin.Identification)
	if err != nil {
		m.warn("partial update on local remove failed!")
		// but continue on because the changes will be synchronized later then anyway
	}
	// update version
	stin.Version.Increase(m.SelfID)
	// send notify
	notifyObj := &shared.ObjectInfo{
		Identification: stin.Identification,
		Name:           path.LastElement(),
		Path:           path.SubPath(),
		Content:        stin.Content,
		Version:        stin.Version,
		Directory:      stin.Directory}
	m.notify(shared.OpRemove, notifyObj)
	return nil
}

/*
remoteRemove handles a remote call of remove.
*/
func (m *Model) remoteRemove(path *shared.RelativePath, remoteObject *shared.ObjectInfo) error {
	// sanity check
	if remoteObject == nil {
		return shared.ErrIllegalParameters
	}
	// get state information
	localFileExists := m.IsTracked(path.FullPath())
	removalExists := m.IsRemoved(remoteObject.Identification)
	// if still exists locally remove it
	if localFileExists {
		// remove file (removedir should already exist, so nothing else to do)
		err := m.directRemove(path)
		if err != nil {
			m.log("couldn't remove file", path.FullPath())
			return err
		}
	}
	// warn if remove dir didn't exist yet
	if !removalExists {
		// we'll create it anyway if it doesn't exist, so all ok but warn
		m.warn("remote file removed but removedir didn't yet exist!")
	}
	// since remote removal --> write own peer to done
	err := m.UpdateRemovalDir(remoteObject.Identification, m.SelfID)
	if err != nil {
		m.log("updating removal dir failed!")
		return err
	}
	// send notify (reuse remoteObject)
	m.notify(shared.OpRemove, remoteObject)
	return nil
}

/*
checkRemove checks whether a remove can be finally applied and purged from the
model dependent on the peers in done and check.
*/
func (m *Model) checkRemove() error {
	removeDir := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR
	allRemovals, err := ioutil.ReadDir(removeDir)
	if err != nil {
		m.log("reading all removals failed")
		return err
	}
	// check for each removal
	for _, stat := range allRemovals {
		// update removal stats and write own peer to them
		err = m.UpdateRemovalDir(stat.Name(), m.SelfID)
		if err != nil {
			return err
		}
		// check if we can complete the removal
		err := m.completeTrackedRemoval(stat.Name())
		if err != nil {
			// notify of error but don't stop, rest can still be checked
			m.log("completeTrackedRemoval:", err.Error())
		}
		// warn of possible orphans
		if time.Since(stat.ModTime()) > removalTimeout {
			m.warn("Removal may be orphaned! ", stat.Name())
			/*TODO this may be called even if it has just been removed... do better logic!
			Also: is there something we can do in this case?*/
		}
		// warn of possibly unapplied removals:
		subPath, err := m.GetSubPath(stat.Name())
		// if err just skip the check (can happen if the file has been removed, so ok)
		if err == nil && m.IsTracked(m.RootPath+"/"+subPath) {
			m.warn("Removal may be unapplied!", subPath)
		}
	}
	// also remove old local remove notifies:
	localDir := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.LOCALDIR + "/" + shared.REMOVESTOREDIR
	allLocals, err := ioutil.ReadDir(localDir)
	if err != nil {
		m.log("reading of local remove notifies failed!")
		return err
	}
	for _, stat := range allLocals {
		if time.Since(stat.ModTime()) > removalLocal {
			// remove notify
			err := os.Remove(localDir + "/" + stat.Name())
			if err != nil {
				m.warn("Failed to remove notify object:", err.Error())
			}
		}
	}
	return nil
}

/*
completeTrackedRemoval checks and if ok, removes the tracked removal dir, replacing
it with a local notify of the removal. This allows the tracked removal to be
purged. After a time out the local notify is also removed.
*/
func (m *Model) completeTrackedRemoval(identification string) error {
	removeDir := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR
	// working directory
	objRemovePath := removeDir + "/" + identification
	// read all peers to check for
	allCheck, err := ioutil.ReadDir(objRemovePath + "/" + shared.REMOVECHECKDIR)
	if err != nil {
		m.log("Failed reading check peer list!")
		return err
	}
	// Test whether we can remove it. This means all peers must have been written
	// AND modify time has reached timeout. Timeout is required to avoid removing
	// removedirs before every peer has a chance of actually noticing they are complete!
	complete := true
	for _, peerStat := range allCheck {
		checkPath := objRemovePath + "/" + shared.REMOVEDONEDIR + "/" + peerStat.Name()
		exists, err := shared.FileExists(checkPath)
		if err != nil {
			// if any error we are done, so break
			m.warn("Failed checking for peer:", err.Error())
			complete = false
			break
		}
		// if a peer doesn't exist yet the removal is NOT yet complete, so break
		if !exists {
			complete = false
			break
		}
	}
	// remove if all peers have written their peer info in REMOVEDONEDIR AND timeout reached (see above)
	if complete {
		// make local note of removal instead of tracked one so that we can remove it
		err := m.makeLocalRemove(identification)
		if err != nil {
			m.log("failed to write local remove note, will not complete removal!")
			return err
		}
		// HARD delete the entire dir: all peers should do the same (soft delete would make removal recursive)
		err = m.directRemove(shared.CreatePathRoot(m.RootPath).Apply(objRemovePath))
		if err != nil {
			m.log("Failed to direct remove!")
			return err
		}
		// note that other peers may not HARD delete it yet, but the isLocalRemoved check ensures that the dir isn't reintroduced
	}
	return nil
}

/*
UpdateRemovalDir is an internal function that writes all known trusted peers to
check. Also, if given, it will add the given peer to the REMOVEDONEDIR.
*/
func (m *Model) UpdateRemovalDir(objIdentification, peerIdentification string) error {
	removeDirectory := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR + "/" + objIdentification
	// make directories if don't exist
	if exists, _ := shared.DirectoryExists(removeDirectory); !exists {
		err := shared.MakeDirectories(removeDirectory, shared.REMOVECHECKDIR, shared.REMOVEDONEDIR)
		if err != nil {
			m.log("making removedir error")
			return err
		}
	}
	// fetch peer objects from disk
	peers, err := shared.LoadPeers(m.RootPath)
	if err != nil {
		m.log("Failed to read peers")
		return err
	}
	// write peer list to check which must all be notified of removal
	for _, peer := range peers {
		// ignore non trusted peers (since they can never write to done dir)
		if !peer.Trusted {
			continue
		}
		path := removeDirectory + "/" + shared.REMOVECHECKDIR + "/" + peer.Identification
		// if already written don't rewrite
		if exists, _ := shared.FileExists(path); exists {
			continue
		}
		err = ioutil.WriteFile(path, []byte(""), shared.FILEPERMISSIONMODE)
		if err != nil {
			m.log("Couldn't write peer file", peer.Name, "to", shared.REMOVECHECKDIR, "!")
			return err
		}
	}
	// if peerIdentification isn't empty, write that peer to DONE
	if peerIdentification != "" {
		path := removeDirectory + "/" + shared.REMOVEDONEDIR + "/" + peerIdentification
		// if already written don't rewrite
		if exists, _ := shared.FileExists(path); !exists {
			// write own peer file also to done dir as removal already applied locally
			err = ioutil.WriteFile(path, []byte(""), shared.FILEPERMISSIONMODE)
			if err != nil {
				m.log("Couldn't write peer file to", shared.REMOVEDONEDIR, "!", err.Error())
				return err
			}
		}
	}
	// update model accordingly and return
	return m.updateLocal(removeDirectory)
}

/*
directRemove removes the given path from the model immediately without notifying
the update. NOTE: Do not use unless this is the behaviour you want! This method
is specifically a part of the normal applyRemove method, do not call outside
of it!
*/
func (m *Model) directRemove(path *shared.RelativePath) error {
	dir, _ := shared.DirectoryExists(path.FullPath())
	// if directory also directRemove all children
	if dir {
		// get all candidates
		children, err := m.partialPopulateMap(path.FullPath())
		if err != nil {
			m.warn("directRemove: failed to retrieve children of directory!")
			return err
		}
		// for each recursively call directRemove
		for subpath := range children {
			// partialPopulateMap returns self too, so ignore
			if subpath == path.SubPath() {
				continue
			}
			// remove all other children
			err := m.directRemove(path.Apply(subpath))
			if err != nil {
				return err
			}
		}
	}
	// remove self if still exists
	if exists, _ := shared.ObjectExists(path.FullPath()); exists {
		err := os.RemoveAll(path.FullPath())
		if err != nil {
			m.log("directRemove failed to remove the file itself!")
			return err
		}
	}
	// remove from model in any case (if no error)
	delete(m.TrackedPaths, path.SubPath())
	delete(m.StaticInfos, path.SubPath())
	return nil
}

/*
IsRemoved checks whether a file is due for deletion or whether it has already
been locally removed completely.
*/
func (m *Model) IsRemoved(identification string) bool {
	path := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR + "/" + identification
	exists, _ := shared.FileExists(path)
	return exists || m.isLocalRemoved(identification)
}

/*
makeLocalRemove is used to locally remember which removals have been applied
already, meaning the shared tracking of a file deletion has been removed.
*/
func (m *Model) makeLocalRemove(identification string) error {
	path := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.LOCALDIR + "/" + shared.REMOVESTOREDIR + "/" + identification
	return ioutil.WriteFile(path, []byte(""), shared.FILEPERMISSIONMODE)
}

/*
isLocalRemoved notes whether a deletion may be being reintroduced even though it
was completely accepted.
*/
func (m *Model) isLocalRemoved(identification string) bool {
	path := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.LOCALDIR + "/" + shared.REMOVESTOREDIR + "/" + identification
	exists, _ := shared.FileExists(path)
	return exists
}
<commit_msg>fixed IsRemoved bug<commit_after>package model

import (
	"io/ioutil"
	"os"
	"time"

	"github.com/tinzenite/shared"
)

/*
localRemove initiates a deletion locally, creating all necessary files and
removing the file from the model.
*/
func (m *Model) localRemove(path *shared.RelativePath) error {
	// get stin for notify
	stin, exists := m.StaticInfos[path.SubPath()]
	if !exists {
		m.log("LocalRemove: stin is missing!")
		return shared.ErrIllegalFileState
	}
	// sanity check
	if m.IsRemoved(stin.Identification) {
		// shouldn't happen but let's be sure; warn at least
		m.warn("LocalRemove: file removal already begun!")
	}
	// direct remove (removes file/dir AND from m.Tracked and m.Static)
	err := m.directRemove(path)
	if err != nil {
		m.log("LocalRemove: failed to directly remove file!")
		return err
	}
	// write peers to check and own peer to done
	err = m.UpdateRemovalDir(stin.Identification, m.SelfID)
	if err != nil {
		m.log("failed to update removal dir for", stin.Identification)
		return err
	}
	// update removal dir here so that creations etc are sent before notify below!
	err = m.updateLocal(m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR + "/" + stin.Identification)
	if err != nil {
		m.warn("partial update on local remove failed!")
		// but continue on because the changes will be synchronized later then anyway
	}
	// update version
	stin.Version.Increase(m.SelfID)
	// send notify
	notifyObj := &shared.ObjectInfo{
		Identification: stin.Identification,
		Name:           path.LastElement(),
		Path:           path.SubPath(),
		Content:        stin.Content,
		Version:        stin.Version,
		Directory:      stin.Directory}
	m.notify(shared.OpRemove, notifyObj)
	return nil
}

/*
remoteRemove handles a remote call of remove.
*/
func (m *Model) remoteRemove(path *shared.RelativePath, remoteObject *shared.ObjectInfo) error {
	// sanity check
	if remoteObject == nil {
		return shared.ErrIllegalParameters
	}
	// get state information
	localFileExists := m.IsTracked(path.FullPath())
	removalExists := m.IsRemoved(remoteObject.Identification)
	// if still exists locally remove it
	if localFileExists {
		// remove file (removedir should already exist, so nothing else to do)
		err := m.directRemove(path)
		if err != nil {
			m.log("couldn't remove file", path.FullPath())
			return err
		}
	}
	// warn if remove dir didn't exist yet
	if !removalExists {
		// we'll create it anyway if it doesn't exist, so all ok but warn
		m.warn("remote file removed but removedir didn't yet exist!")
	}
	// since remote removal --> write own peer to done
	err := m.UpdateRemovalDir(remoteObject.Identification, m.SelfID)
	if err != nil {
		m.log("updating removal dir failed!")
		return err
	}
	// send notify (reuse remoteObject)
	m.notify(shared.OpRemove, remoteObject)
	return nil
}

/*
checkRemove checks whether a remove can be finally applied and purged from the
model dependent on the peers in done and check.
*/
func (m *Model) checkRemove() error {
	removeDir := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR
	allRemovals, err := ioutil.ReadDir(removeDir)
	if err != nil {
		m.log("reading all removals failed")
		return err
	}
	// check for each removal
	for _, stat := range allRemovals {
		// update removal stats and write own peer to them
		err = m.UpdateRemovalDir(stat.Name(), m.SelfID)
		if err != nil {
			return err
		}
		// check if we can complete the removal
		err := m.completeTrackedRemoval(stat.Name())
		if err != nil {
			// notify of error but don't stop, rest can still be checked
			m.log("completeTrackedRemoval:", err.Error())
		}
		// warn of possible orphans
		if time.Since(stat.ModTime()) > removalTimeout {
			m.warn("Removal may be orphaned! ", stat.Name())
			/*TODO this may be called even if it has just been removed... do better logic!
			Also: is there something we can do in this case?*/
		}
		// warn of possibly unapplied removals:
		subPath, err := m.GetSubPath(stat.Name())
		// if err just skip the check (can happen if the file has been removed, so ok)
		if err == nil && m.IsTracked(m.RootPath+"/"+subPath) {
			m.warn("Removal may be unapplied!", subPath)
		}
	}
	// also remove old local remove notifies:
	localDir := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.LOCALDIR + "/" + shared.REMOVESTOREDIR
	allLocals, err := ioutil.ReadDir(localDir)
	if err != nil {
		m.log("reading of local remove notifies failed!")
		return err
	}
	for _, stat := range allLocals {
		if time.Since(stat.ModTime()) > removalLocal {
			// remove notify
			err := os.Remove(localDir + "/" + stat.Name())
			if err != nil {
				m.warn("Failed to remove notify object:", err.Error())
			}
		}
	}
	return nil
}

/*
completeTrackedRemoval checks and if ok, removes the tracked removal dir, replacing
it with a local notify of the removal. This allows the tracked removal to be
purged. After a time out the local notify is also removed.
*/
func (m *Model) completeTrackedRemoval(identification string) error {
	removeDir := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR
	// working directory
	objRemovePath := removeDir + "/" + identification
	// read all peers to check for
	allCheck, err := ioutil.ReadDir(objRemovePath + "/" + shared.REMOVECHECKDIR)
	if err != nil {
		m.log("Failed reading check peer list!")
		return err
	}
	// Test whether we can remove it. This means all peers must have been written
	// AND modify time has reached timeout. Timeout is required to avoid removing
	// removedirs before every peer has a chance of actually noticing they are complete!
	complete := true
	for _, peerStat := range allCheck {
		checkPath := objRemovePath + "/" + shared.REMOVEDONEDIR + "/" + peerStat.Name()
		exists, err := shared.FileExists(checkPath)
		if err != nil {
			// if any error we are done, so break
			m.warn("Failed checking for peer:", err.Error())
			complete = false
			break
		}
		// if a peer doesn't exist yet the removal is NOT yet complete, so break
		if !exists {
			complete = false
			break
		}
	}
	// remove if all peers have written their peer info in REMOVEDONEDIR AND timeout reached (see above)
	if complete {
		// make local note of removal instead of tracked one so that we can remove it
		err := m.makeLocalRemove(identification)
		if err != nil {
			m.log("failed to write local remove note, will not complete removal!")
			return err
		}
		// HARD delete the entire dir: all peers should do the same (soft delete would make removal recursive)
		err = m.directRemove(shared.CreatePathRoot(m.RootPath).Apply(objRemovePath))
		if err != nil {
			m.log("Failed to direct remove!")
			return err
		}
		// note that other peers may not HARD delete it yet, but the isLocalRemoved check ensures that the dir isn't reintroduced
	}
	return nil
}

/*
UpdateRemovalDir is an internal function that writes all known trusted peers to
check. Also, if given, it will add the given peer to the REMOVEDONEDIR.
*/
func (m *Model) UpdateRemovalDir(objIdentification, peerIdentification string) error {
	removeDirectory := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR + "/" + objIdentification
	// make directories if don't exist
	if exists, _ := shared.DirectoryExists(removeDirectory); !exists {
		err := shared.MakeDirectories(removeDirectory, shared.REMOVECHECKDIR, shared.REMOVEDONEDIR)
		if err != nil {
			m.log("making removedir error")
			return err
		}
	}
	// fetch peer objects from disk
	peers, err := shared.LoadPeers(m.RootPath)
	if err != nil {
		m.log("Failed to read peers")
		return err
	}
	// write peer list to check which must all be notified of removal
	for _, peer := range peers {
		// ignore non trusted peers (since they can never write to done dir)
		if !peer.Trusted {
			continue
		}
		path := removeDirectory + "/" + shared.REMOVECHECKDIR + "/" + peer.Identification
		// if already written don't rewrite
		if exists, _ := shared.FileExists(path); exists {
			continue
		}
		err = ioutil.WriteFile(path, []byte(""), shared.FILEPERMISSIONMODE)
		if err != nil {
			m.log("Couldn't write peer file", peer.Name, "to", shared.REMOVECHECKDIR, "!")
			return err
		}
	}
	// if peerIdentification isn't empty, write that peer to DONE
	if peerIdentification != "" {
		path := removeDirectory + "/" + shared.REMOVEDONEDIR + "/" + peerIdentification
		// if already written don't rewrite
		if exists, _ := shared.FileExists(path); !exists {
			// write own peer file also to done dir as removal already applied locally
			err = ioutil.WriteFile(path, []byte(""), shared.FILEPERMISSIONMODE)
			if err != nil {
				m.log("Couldn't write peer file to", shared.REMOVEDONEDIR, "!", err.Error())
				return err
			}
		}
	}
	// update model accordingly and return
	return m.updateLocal(removeDirectory)
}

/*
directRemove removes the given path from the model immediately without notifying
the update. NOTE: Do not use unless this is the behaviour you want! This method
is specifically a part of the normal applyRemove method, do not call outside
of it!
*/
func (m *Model) directRemove(path *shared.RelativePath) error {
	dir, _ := shared.DirectoryExists(path.FullPath())
	// if directory also directRemove all children
	if dir {
		// get all candidates
		children, err := m.partialPopulateMap(path.FullPath())
		if err != nil {
			m.warn("directRemove: failed to retrieve children of directory!")
			return err
		}
		// for each recursively call directRemove
		for subpath := range children {
			// partialPopulateMap returns self too, so ignore
			if subpath == path.SubPath() {
				continue
			}
			// remove all other children
			err := m.directRemove(path.Apply(subpath))
			if err != nil {
				return err
			}
		}
	}
	// remove self if still exists
	if exists, _ := shared.ObjectExists(path.FullPath()); exists {
		err := os.RemoveAll(path.FullPath())
		if err != nil {
			m.log("directRemove failed to remove the file itself!")
			return err
		}
	}
	// remove from model in any case (if no error)
	delete(m.TrackedPaths, path.SubPath())
	delete(m.StaticInfos, path.SubPath())
	return nil
}

/*
IsRemoved checks whether a file is due for deletion or whether it has already
been locally removed completely.
*/
func (m *Model) IsRemoved(identification string) bool {
	path := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR + "/" + identification
	exists, _ := shared.DirectoryExists(path)
	return exists || m.isLocalRemoved(identification)
}

/*
makeLocalRemove is used to locally remember which removals have been applied
already, meaning the shared tracking of a file deletion has been removed.
*/
func (m *Model) makeLocalRemove(identification string) error {
	path := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.LOCALDIR + "/" + shared.REMOVESTOREDIR + "/" + identification
	return ioutil.WriteFile(path, []byte(""), shared.FILEPERMISSIONMODE)
}

/*
isLocalRemoved notes whether a deletion may be being reintroduced even though it
was completely accepted.
*/
func (m *Model) isLocalRemoved(identification string) bool {
	path := m.RootPath + "/" + shared.TINZENITEDIR + "/" + shared.LOCALDIR + "/" + shared.REMOVESTOREDIR + "/" + identification
	exists, _ := shared.FileExists(path)
	return exists
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2015 Ludovic Fauvet
// Licensed under the MIT license

package database

import (
	"errors"
	"fmt"
	. "github.com/etix/mirrorbits/config"
	"github.com/garyburd/redigo/redis"
	"sync"
	"time"
)

const (
	redisConnectionTimeout = 200 * time.Millisecond
	redisReadWriteTimeout  = 300 * time.Second
)

var (
	errUnreachable = errors.New("redis endpoint unreachable")
)

type Redis struct {
	pool         *redis.Pool
	Pubsub       *Pubsub
	failure      bool
	failureState sync.RWMutex
	knownMaster  string
	daemon       bool
}

func NewRedis(daemon bool) *Redis {
	r := &Redis{}
	r.daemon = daemon
	r.pool = &redis.Pool{
		MaxIdle:     10,
		IdleTimeout: 240 * time.Second,
		Dial: func() (redis.Conn, error) {
			return r.Connect()
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	return r
}

func (r *Redis) Get() redis.Conn {
	return r.pool.Get()
}

func (r *Redis) Close() {
	r.pool.Close()
	//TODO close pubsub
}

func (r *Redis) ConnectPubsub() {
	if r.Pubsub == nil {
		r.Pubsub = NewPubsub(r)
	}
}

func (r *Redis) Connect() (redis.Conn, error) {
	sentinels := GetConfig().RedisSentinels

	if len(sentinels) > 0 {

		if len(GetConfig().RedisSentinelMasterName) == 0 {
			r.logError("Config: RedisSentinelMasterName cannot be empty!")
			goto single
		}

		for _, s := range sentinels {
			log.Debug("Connecting to redis sentinel %s", s.Host)
			var master []string
			var masterhost string
			var cm redis.Conn

			c, err := r.connectTo(s.Host)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				continue
			}
			//AUTH?
			role, err := r.askRole(c)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}
			if role != "sentinel" {
				r.logError("Sentinel: %s is not a sentinel but a %s", s.Host, role)
				goto closeSentinel
			}

			master, err = redis.Strings(c.Do("SENTINEL", "get-master-addr-by-name", GetConfig().RedisSentinelMasterName))
			if err == redis.ErrNil {
				r.logError("Sentinel: %s doesn't know the master-name %s", s.Host, GetConfig().RedisSentinelMasterName)
				goto closeSentinel
			} else if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}

			masterhost = fmt.Sprintf("%s:%s", master[0], master[1])

			cm, err = r.connectTo(masterhost)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeSentinel
			}

			if r.auth(cm) != nil {
				r.logError("Redis master: auth failed")
				goto closeMaster
			}

			role, err = r.askRole(cm)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeMaster
			}
			if role != "master" {
				r.logError("Redis master: %s is not a master but a %s", masterhost, role)
				goto closeMaster
			}

			// Close the connection to the sentinel
			c.Close()
			r.setFailureState(false)

			r.printConnectedMaster(masterhost)
			return cm, nil

		closeMaster:
			cm.Close()

		closeSentinel:
			c.Close()
		}
	}

single:

	if len(GetConfig().RedisAddress) == 0 {
		if len(sentinels) == 0 {
			log.Error("No redis master available")
		}
		r.setFailureState(true)
		return nil, errUnreachable
	}

	if len(sentinels) > 0 && r.getFailureState() == false {
		log.Warning("No redis master available, trying using the configured RedisAddress as fallback")
	}

	c, err := r.connectTo(GetConfig().RedisAddress)
	if err != nil {
		return nil, err
	}
	if r.auth(c); err != nil {
		c.Close()
		return nil, err
	}
	role, err := r.askRole(c)
	if err != nil {
		r.logError("Redis master: %s", err.Error())
		r.setFailureState(true)
		return nil, errUnreachable
	}
	if role != "master" {
		r.logError("Redis master: %s is not a master but a %s", GetConfig().RedisAddress, role)
		r.setFailureState(true)
		return nil, errUnreachable
	}
	r.setFailureState(false)
	r.printConnectedMaster(GetConfig().RedisAddress)
	return c, err

}

func (r *Redis) connectTo(address string) (redis.Conn, error) {
	return redis.DialTimeout("tcp", address, redisConnectionTimeout, redisReadWriteTimeout, redisReadWriteTimeout)
}

func (r *Redis) askRole(c redis.Conn) (string, error) {
	roleReply, err := redis.Values(c.Do("ROLE"))
	role, err := redis.String(roleReply[0], err)
	return role, err
}

func (r *Redis) auth(c redis.Conn) (err error) {
	if GetConfig().RedisPassword != "" {
		_, err = c.Do("AUTH", GetConfig().RedisPassword)
	}
	return
}

func (r *Redis) logError(format string, args ...interface{}) {
	if r.getFailureState() == true {
		log.Debug(format, args...)
	} else {
		log.Error(format, args...)
	}
}

func (r *Redis) printConnectedMaster(address string) {
	if address != r.knownMaster && r.daemon {
		r.knownMaster = address
		log.Info("Connected to redis master %s", address)
	} else {
		log.Debug("Connected to redis master %s", address)
	}
}

func (r *Redis) setFailureState(failure bool) {
	r.failureState.Lock()
	r.failure = failure
	r.failureState.Unlock()
}

func (r *Redis) getFailureState() bool {
	r.failureState.RLock()
	defer r.failureState.RUnlock()
	return r.failure
}

// RedisIsLoading returns true if the error is of type LOADING
func RedisIsLoading(err error) bool {
	// PARSING: "LOADING Redis is loading the dataset in memory"
	if err != nil && err.Error()[:7] == "LOADING" {
		return true
	}
	return false
}
<commit_msg>redis: fix unhandled ROLE command error<commit_after>// Copyright (c) 2014-2015 Ludovic Fauvet
// Licensed under the MIT license

package database

import (
	"errors"
	"fmt"
	. "github.com/etix/mirrorbits/config"
	"github.com/garyburd/redigo/redis"
	"sync"
	"time"
)

const (
	redisConnectionTimeout = 200 * time.Millisecond
	redisReadWriteTimeout  = 300 * time.Second
)

var (
	errUnreachable = errors.New("redis endpoint unreachable")
)

type Redis struct {
	pool         *redis.Pool
	Pubsub       *Pubsub
	failure      bool
	failureState sync.RWMutex
	knownMaster  string
	daemon       bool
}

func NewRedis(daemon bool) *Redis {
	r := &Redis{}
	r.daemon = daemon
	r.pool = &redis.Pool{
		MaxIdle:     10,
		IdleTimeout: 240 * time.Second,
		Dial: func() (redis.Conn, error) {
			return r.Connect()
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	return r
}

func (r *Redis) Get() redis.Conn {
	return r.pool.Get()
}

func (r *Redis) Close() {
	r.pool.Close()
	//TODO close pubsub
}

func (r *Redis) ConnectPubsub() {
	if r.Pubsub == nil {
		r.Pubsub = NewPubsub(r)
	}
}

func (r *Redis) Connect() (redis.Conn, error) {
	sentinels := GetConfig().RedisSentinels

	if len(sentinels) > 0 {

		if len(GetConfig().RedisSentinelMasterName) == 0 {
			r.logError("Config: RedisSentinelMasterName cannot be empty!")
			goto single
		}

		for _, s := range sentinels {
			log.Debug("Connecting to redis sentinel %s", s.Host)
			var master []string
			var masterhost string
			var cm redis.Conn

			c, err := r.connectTo(s.Host)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				continue
			}
			//AUTH?
			role, err := r.askRole(c)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}
			if role != "sentinel" {
				r.logError("Sentinel: %s is not a sentinel but a %s", s.Host, role)
				goto closeSentinel
			}

			master, err = redis.Strings(c.Do("SENTINEL", "get-master-addr-by-name", GetConfig().RedisSentinelMasterName))
			if err == redis.ErrNil {
				r.logError("Sentinel: %s doesn't know the master-name %s", s.Host, GetConfig().RedisSentinelMasterName)
				goto closeSentinel
			} else if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}

			masterhost = fmt.Sprintf("%s:%s", master[0], master[1])

			cm, err = r.connectTo(masterhost)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeSentinel
			}

			if r.auth(cm) != nil {
				r.logError("Redis master: auth failed")
				goto closeMaster
			}

			role, err = r.askRole(cm)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeMaster
			}
			if role != "master" {
				r.logError("Redis master: %s is not a master but a %s", masterhost, role)
				goto closeMaster
			}

			// Close the connection to the sentinel
			c.Close()
			r.setFailureState(false)

			r.printConnectedMaster(masterhost)
			return cm, nil

		closeMaster:
			cm.Close()

		closeSentinel:
			c.Close()
		}
	}

single:

	if len(GetConfig().RedisAddress) == 0 {
		if len(sentinels) == 0 {
			log.Error("No redis master available")
		}
		r.setFailureState(true)
		return nil, errUnreachable
	}

	if len(sentinels) > 0 && r.getFailureState() == false {
		log.Warning("No redis master available, trying using the configured RedisAddress as fallback")
	}

	c, err := r.connectTo(GetConfig().RedisAddress)
	if err != nil {
		return nil, err
	}
	if r.auth(c); err != nil {
		c.Close()
		return nil, err
	}
	role, err := r.askRole(c)
	if err != nil {
		r.logError("Redis master: %s", err.Error())
		r.setFailureState(true)
		return nil, errUnreachable
	}
	if role != "master" {
		r.logError("Redis master: %s is not a master but a %s", GetConfig().RedisAddress, role)
		r.setFailureState(true)
		return nil, errUnreachable
	}
	r.setFailureState(false)
	r.printConnectedMaster(GetConfig().RedisAddress)
	return c, err

}

func (r *Redis) connectTo(address string) (redis.Conn, error) {
	return redis.DialTimeout("tcp", address, redisConnectionTimeout, redisReadWriteTimeout, redisReadWriteTimeout)
}

func (r *Redis) askRole(c redis.Conn) (string, error) {
	roleReply, err := redis.Values(c.Do("ROLE"))
	if err != nil {
		return "", err
	}
	role, err := redis.String(roleReply[0], err)
	return role, err
}

func (r *Redis) auth(c redis.Conn) (err error) {
	if GetConfig().RedisPassword != "" {
		_, err = c.Do("AUTH", GetConfig().RedisPassword)
	}
	return
}

func (r *Redis) logError(format string, args ...interface{}) {
	if r.getFailureState() == true {
		log.Debug(format, args...)
	} else {
		log.Error(format, args...)
	}
}

func (r *Redis) printConnectedMaster(address string) {
	if address != r.knownMaster && r.daemon {
		r.knownMaster = address
		log.Info("Connected to redis master %s", address)
	} else {
		log.Debug("Connected to redis master %s", address)
	}
}

func (r *Redis) setFailureState(failure bool) {
	r.failureState.Lock()
	r.failure = failure
	r.failureState.Unlock()
}

func (r *Redis) getFailureState() bool {
	r.failureState.RLock()
	defer r.failureState.RUnlock()
	return r.failure
}

// RedisIsLoading returns true if the error is of type LOADING
func RedisIsLoading(err error) bool {
	// PARSING: "LOADING Redis is loading the dataset in memory"
	if err != nil && err.Error()[:7] == "LOADING" {
		return true
	}
	return false
}
<|endoftext|>