<commit_before>// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
	"fmt"
	"time"

	api "code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/timeutil"
)

// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
	ID          int64              `xorm:"pk autoincr"`
	IssueID     int64              `xorm:"INDEX"`
	UserID      int64              `xorm:"INDEX"`
	CreatedUnix timeutil.TimeStamp `xorm:"created"`
}

// Stopwatches is a List ful of Stopwatch
type Stopwatches []Stopwatch

func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
	sw = new(Stopwatch)
	exists, err = e.
		Where("user_id = ?", userID).
		And("issue_id = ?", issueID).
		Get(sw)
	return
}

// GetUserStopwatches return list of all stopwatches of a user
func GetUserStopwatches(userID int64, listOptions ListOptions) (*Stopwatches, error) {
	sws := new(Stopwatches)
	sess := x.Where("stopwatch.user_id = ?", userID)
	if listOptions.Page != 0 {
		sess = listOptions.setSessionPagination(sess)
	}

	err := sess.Find(sws)
	if err != nil {
		return nil, err
	}
	return sws, nil
}

// StopwatchExists returns true if the stopwatch exists
func StopwatchExists(userID int64, issueID int64) bool {
	_, exists, _ := getStopwatch(x, userID, issueID)
	return exists
}

// HasUserStopwatch returns true if the user has a stopwatch
func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
	sw = new(Stopwatch)
	exists, err = x.
		Where("user_id = ?", userID).
		Get(sw)
	return
}

// CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline.
func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
	sw, exists, err := getStopwatch(x, user.ID, issue.ID)
	if err != nil {
		return err
	}
	if err := issue.loadRepo(x); err != nil {
		return err
	}

	if exists {
		// Create tracked time out of the time difference between start date and actual date
		timediff := time.Now().Unix() - int64(sw.CreatedUnix)

		// Create TrackedTime
		tt := &TrackedTime{
			Created: time.Now(),
			IssueID: issue.ID,
			UserID:  user.ID,
			Time:    timediff,
		}

		if _, err := x.Insert(tt); err != nil {
			return err
		}

		if _, err := CreateComment(&CreateCommentOptions{
			Doer:    user,
			Issue:   issue,
			Repo:    issue.Repo,
			Content: SecToTime(timediff),
			Type:    CommentTypeStopTracking,
		}); err != nil {
			return err
		}
		if _, err := x.Delete(sw); err != nil {
			return err
		}
	} else {
		//if another stopwatch is running: stop it
		exists, sw, err := HasUserStopwatch(user.ID)
		if err != nil {
			return err
		}
		if exists {
			issue, err := getIssueByID(x, sw.IssueID)
			if err != nil {
				return err
			}
			if err := CreateOrStopIssueStopwatch(user, issue); err != nil {
				return err
			}
		}

		// Create stopwatch
		sw = &Stopwatch{
			UserID:  user.ID,
			IssueID: issue.ID,
		}

		if _, err := x.Insert(sw); err != nil {
			return err
		}

		if _, err := CreateComment(&CreateCommentOptions{
			Doer:  user,
			Issue: issue,
			Repo:  issue.Repo,
			Type:  CommentTypeStartTracking,
		}); err != nil {
			return err
		}
	}
	return nil
}

// CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
func CancelStopwatch(user *User, issue *Issue) error {
	sw, exists, err := getStopwatch(x, user.ID, issue.ID)
	if err != nil {
		return err
	}

	if exists {
		if _, err := x.Delete(sw); err != nil {
			return err
		}

		if err := issue.loadRepo(x); err != nil {
			return err
		}

		if _, err := CreateComment(&CreateCommentOptions{
			Doer:  user,
			Issue: issue,
			Repo:  issue.Repo,
			Type:  CommentTypeCancelTracking,
		}); err != nil {
			return err
		}
	}
	return nil
}

// SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s)
func SecToTime(duration int64) string {
	seconds := duration % 60
	minutes := (duration / (60)) % 60
	hours := duration / (60 * 60)

	var hrs string

	if hours > 0 {
		hrs = fmt.Sprintf("%dh", hours)
	}
	if minutes > 0 {
		if hours == 0 {
			hrs = fmt.Sprintf("%dmin", minutes)
		} else {
			hrs = fmt.Sprintf("%s %dmin", hrs, minutes)
		}
	}
	if seconds > 0 {
		if hours == 0 && minutes == 0 {
			hrs = fmt.Sprintf("%ds", seconds)
		} else {
			hrs = fmt.Sprintf("%s %ds", hrs, seconds)
		}
	}

	return hrs
}

// APIFormat convert Stopwatch type to api.StopWatch type
func (sw *Stopwatch) APIFormat() (api.StopWatch, error) {
	issue, err := getIssueByID(x, sw.IssueID)
	if err != nil {
		return api.StopWatch{}, err
	}
	if err := issue.LoadRepo(); err != nil {
		return api.StopWatch{}, err
	}
	return api.StopWatch{
		Created:       sw.CreatedUnix.AsTime(),
		IssueIndex:    issue.Index,
		IssueTitle:    issue.Title,
		RepoOwnerName: issue.Repo.OwnerName,
		RepoName:      issue.Repo.Name,
	}, nil
}

// APIFormat convert Stopwatches type to api.StopWatches type
func (sws Stopwatches) APIFormat() (api.StopWatches, error) {
	result := api.StopWatches(make([]api.StopWatch, 0, len(sws)))
	for _, sw := range sws {
		apiSW, err := sw.APIFormat()
		if err != nil {
			return nil, err
		}
		result = append(result, apiSW)
	}
	return result, nil
}
<commit_msg>implemet Cache for Stopwatches.APIFormat() (#12730)<commit_after>// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
	"fmt"
	"time"

	api "code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/timeutil"
)

// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
	ID          int64              `xorm:"pk autoincr"`
	IssueID     int64              `xorm:"INDEX"`
	UserID      int64              `xorm:"INDEX"`
	CreatedUnix timeutil.TimeStamp `xorm:"created"`
}

// Stopwatches is a List ful of Stopwatch
type Stopwatches []Stopwatch

func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
	sw = new(Stopwatch)
	exists, err = e.
		Where("user_id = ?", userID).
		And("issue_id = ?", issueID).
		Get(sw)
	return
}

// GetUserStopwatches return list of all stopwatches of a user
func GetUserStopwatches(userID int64, listOptions ListOptions) (*Stopwatches, error) {
	sws := new(Stopwatches)
	sess := x.Where("stopwatch.user_id = ?", userID)
	if listOptions.Page != 0 {
		sess = listOptions.setSessionPagination(sess)
	}

	err := sess.Find(sws)
	if err != nil {
		return nil, err
	}
	return sws, nil
}

// StopwatchExists returns true if the stopwatch exists
func StopwatchExists(userID int64, issueID int64) bool {
	_, exists, _ := getStopwatch(x, userID, issueID)
	return exists
}

// HasUserStopwatch returns true if the user has a stopwatch
func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
	sw = new(Stopwatch)
	exists, err = x.
		Where("user_id = ?", userID).
		Get(sw)
	return
}

// CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline.
func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
	sw, exists, err := getStopwatch(x, user.ID, issue.ID)
	if err != nil {
		return err
	}
	if err := issue.loadRepo(x); err != nil {
		return err
	}

	if exists {
		// Create tracked time out of the time difference between start date and actual date
		timediff := time.Now().Unix() - int64(sw.CreatedUnix)

		// Create TrackedTime
		tt := &TrackedTime{
			Created: time.Now(),
			IssueID: issue.ID,
			UserID:  user.ID,
			Time:    timediff,
		}

		if _, err := x.Insert(tt); err != nil {
			return err
		}

		if _, err := CreateComment(&CreateCommentOptions{
			Doer:    user,
			Issue:   issue,
			Repo:    issue.Repo,
			Content: SecToTime(timediff),
			Type:    CommentTypeStopTracking,
		}); err != nil {
			return err
		}
		if _, err := x.Delete(sw); err != nil {
			return err
		}
	} else {
		//if another stopwatch is running: stop it
		exists, sw, err := HasUserStopwatch(user.ID)
		if err != nil {
			return err
		}
		if exists {
			issue, err := getIssueByID(x, sw.IssueID)
			if err != nil {
				return err
			}
			if err := CreateOrStopIssueStopwatch(user, issue); err != nil {
				return err
			}
		}

		// Create stopwatch
		sw = &Stopwatch{
			UserID:  user.ID,
			IssueID: issue.ID,
		}

		if _, err := x.Insert(sw); err != nil {
			return err
		}

		if _, err := CreateComment(&CreateCommentOptions{
			Doer:  user,
			Issue: issue,
			Repo:  issue.Repo,
			Type:  CommentTypeStartTracking,
		}); err != nil {
			return err
		}
	}
	return nil
}

// CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
func CancelStopwatch(user *User, issue *Issue) error {
	sw, exists, err := getStopwatch(x, user.ID, issue.ID)
	if err != nil {
		return err
	}

	if exists {
		if _, err := x.Delete(sw); err != nil {
			return err
		}

		if err := issue.loadRepo(x); err != nil {
			return err
		}

		if _, err := CreateComment(&CreateCommentOptions{
			Doer:  user,
			Issue: issue,
			Repo:  issue.Repo,
			Type:  CommentTypeCancelTracking,
		}); err != nil {
			return err
		}
	}
	return nil
}

// SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s)
func SecToTime(duration int64) string {
	seconds := duration % 60
	minutes := (duration / (60)) % 60
	hours := duration / (60 * 60)

	var hrs string

	if hours > 0 {
		hrs = fmt.Sprintf("%dh", hours)
	}
	if minutes > 0 {
		if hours == 0 {
			hrs = fmt.Sprintf("%dmin", minutes)
		} else {
			hrs = fmt.Sprintf("%s %dmin", hrs, minutes)
		}
	}
	if seconds > 0 {
		if hours == 0 && minutes == 0 {
			hrs = fmt.Sprintf("%ds", seconds)
		} else {
			hrs = fmt.Sprintf("%s %ds", hrs, seconds)
		}
	}

	return hrs
}

// APIFormat convert Stopwatch type to api.StopWatch type
func (sw *Stopwatch) APIFormat() (api.StopWatch, error) {
	issue, err := getIssueByID(x, sw.IssueID)
	if err != nil {
		return api.StopWatch{}, err
	}
	if err := issue.LoadRepo(); err != nil {
		return api.StopWatch{}, err
	}
	return api.StopWatch{
		Created:       sw.CreatedUnix.AsTime(),
		IssueIndex:    issue.Index,
		IssueTitle:    issue.Title,
		RepoOwnerName: issue.Repo.OwnerName,
		RepoName:      issue.Repo.Name,
	}, nil
}

// APIFormat convert Stopwatches type to api.StopWatches type
func (sws Stopwatches) APIFormat() (api.StopWatches, error) {
	result := api.StopWatches(make([]api.StopWatch, 0, len(sws)))

	issueCache := make(map[int64]*Issue)
	repoCache := make(map[int64]*Repository)
	var (
		issue *Issue
		repo  *Repository
		ok    bool
		err   error
	)

	for _, sw := range sws {
		issue, ok = issueCache[sw.IssueID]
		if !ok {
			issue, err = GetIssueByID(sw.IssueID)
			if err != nil {
				return nil, err
			}
		}
		repo, ok = repoCache[issue.RepoID]
		if !ok {
			repo, err = GetRepositoryByID(issue.RepoID)
			if err != nil {
				return nil, err
			}
		}

		result = append(result, api.StopWatch{
			Created:       sw.CreatedUnix.AsTime(),
			IssueIndex:    issue.Index,
			IssueTitle:    issue.Title,
			RepoOwnerName: repo.OwnerName,
			RepoName:      repo.Name,
		})
	}
	return result, nil
}
<|endoftext|>


<commit_before>package flagx

import (
	"encoding"
	"reflect"
)

// Value is like flag.Getter (which is a superset of flag.Value)
type Value interface {
	String() string
	Set(string) error
	Get() interface{}
}

// Dummy is a flag.Value that does nothing.
type Dummy struct{}

func (Dummy) String() string { return "" }

func (Dummy) Set(s string) error { return nil }

func (Dummy) Get() interface{} { return nil }

// stringSetter is the subset of flag.Value for setting a value from a string
type stringSetter interface {
	// See flag.Value
	Set(string) error
}

var (
	textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
	stringSetterType    = reflect.TypeOf(new(stringSetter)).Elem()
)

func setterFor(typ reflect.Type) func(target reflect.Value, value string) error {
	switch {
	case reflect.PtrTo(typ).Implements(stringSetterType):
		return func(target reflect.Value, value string) error {
			return target.Addr().Interface().(stringSetter).Set(value)
		}
	case reflect.PtrTo(typ).Implements(textUnmarshalerType):
		return func(target reflect.Value, value string) error {
			return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
		}
	case typ.Implements(stringSetterType):
		return func(target reflect.Value, value string) error {
			return target.Interface().(stringSetter).Set(value)
		}
	case typ.Implements(textUnmarshalerType):
		return func(target reflect.Value, value string) error {
			return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
		}
	case typ.Kind() == reflect.String:
		return func(target reflect.Value, value string) error {
			target.SetString(value)
			return nil
		}
	default:
		return nil
	}
}

func isBoolFlag(f interface {
	String() string
	Set(string) error
}) bool {
	if bf, ok := f.(interface {
		IsBoolFlag() bool
	}); ok {
		return bf.IsBoolFlag()
	}
	return false
}
<commit_msg>doc: add more doc links<commit_after>package flagx

import (
	"encoding"
	"reflect"
)

// Value is like [flag.Getter] (which is a superset of [flag.Value]).
type Value interface {
	String() string
	Set(string) error
	Get() interface{}
}

// Dummy is a [flag.Value] that does nothing.
type Dummy struct{}

func (Dummy) String() string { return "" }

func (Dummy) Set(s string) error { return nil }

func (Dummy) Get() interface{} { return nil }

// stringSetter is the subset of [flag.Value] for setting a value from a string
type stringSetter interface {
	// See flag.Value
	Set(string) error
}

var (
	textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
	stringSetterType    = reflect.TypeOf(new(stringSetter)).Elem()
)

func setterFor(typ reflect.Type) func(target reflect.Value, value string) error {
	switch {
	case reflect.PtrTo(typ).Implements(stringSetterType):
		return func(target reflect.Value, value string) error {
			return target.Addr().Interface().(stringSetter).Set(value)
		}
	case reflect.PtrTo(typ).Implements(textUnmarshalerType):
		return func(target reflect.Value, value string) error {
			return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
		}
	case typ.Implements(stringSetterType):
		return func(target reflect.Value, value string) error {
			return target.Interface().(stringSetter).Set(value)
		}
	case typ.Implements(textUnmarshalerType):
		return func(target reflect.Value, value string) error {
			return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
		}
	case typ.Kind() == reflect.String:
		return func(target reflect.Value, value string) error {
			target.SetString(value)
			return nil
		}
	default:
		return nil
	}
}

func isBoolFlag(f interface {
	String() string
	Set(string) error
}) bool {
	if bf, ok := f.(interface {
		IsBoolFlag() bool
	}); ok {
		return bf.IsBoolFlag()
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2011 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 csv reads and writes comma-separated values (CSV) files.
//
// A csv file contains zero or more records of one or more fields per record.
// Each record is separated by the newline character. The final record may
// optionally be followed by a newline character.
//
//	field1,field2,field3
//
// White space is considered part of a field.
//
// Carriage returns before newline characters are silently removed.
//
// Blank lines are ignored.  A line with only whitespace characters (excluding
// the ending newline character) is not considered a blank line.
//
// Fields which start and stop with the quote character " are called
// quoted-fields.  The beginning and ending quote are not part of the
// field.
//
// The source:
//
//	normal string,"quoted-field"
//
// results in the fields
//
//	{`normal string`, `quoted-field`}
//
// Within a quoted-field a quote character followed by a second quote
// character is considered a single quote.
//
//	"the ""word"" is true","a ""quoted-field"""
//
// results in
//
//	{`the "word" is true`, `a "quoted-field"`}
//
// Newlines and commas may be included in a quoted-field
//
//	"Multi-line
//	field","comma is ,"
//
// results in
//
//	{`Multi-line
//	field`, `comma is ,`}
package csv

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"unicode"
)

// A ParseError is returned for parsing errors.
// The first line is 1.  The first column is 0.
type ParseError struct {
	Line   int   // Line where the error occurred
	Column int   // Column (rune index) where the error occurred
	Err    error // The actual error
}

func (e *ParseError) Error() string {
	return fmt.Sprintf("line %d, column %d: %s", e.Line, e.Column, e.Err)
}

// These are the errors that can be returned in ParseError.Error
var (
	ErrTrailingComma = errors.New("extra delimiter at end of line")
	ErrBareQuote     = errors.New("bare \" in non-quoted-field")
	ErrQuote         = errors.New("extraneous \" in field")
	ErrFieldCount    = errors.New("wrong number of fields in line")
)

// A Reader reads records from a CSV-encoded file.
//
// As returned by NewReader, a Reader expects input conforming to RFC 4180.
// The exported fields can be changed to customize the details before the
// first call to Read or ReadAll.
//
// Comma is the field delimiter.  It defaults to ','.
//
// Comment, if not 0, is the comment character. Lines beginning with the
// Comment character are ignored.
//
// If FieldsPerRecord is positive, Read requires each record to
// have the given number of fields.  If FieldsPerRecord is 0, Read sets it to
// the number of fields in the first record, so that future records must
// have the same field count.
//
// If LazyQuotes is true, a quote may appear in an unquoted field and a
// non-doubled quote may appear in a quoted field.
//
// If TrailingComma is true, the last field may be an unquoted empty field.
//
// If TrimLeadingSpace is true, leading white space in a field is ignored.
type Reader struct {
	Comma            rune // Field delimiter (set to ',' by NewReader)
	Comment          rune // Comment character for start of line
	FieldsPerRecord  int  // Number of expected fields per record
	LazyQuotes       bool // Allow lazy quotes
	TrailingComma    bool // Allow trailing comma
	TrimLeadingSpace bool // Trim leading space
	line             int
	column           int
	r                *bufio.Reader
	field            bytes.Buffer
}

// NewReader returns a new Reader that reads from r.
func NewReader(r io.Reader) *Reader {
	return &Reader{
		Comma: ',',
		r:     bufio.NewReader(r),
	}
}

// error creates a new ParseError based on err.
func (r *Reader) error(err error) error {
	return &ParseError{
		Line:   r.line,
		Column: r.column,
		Err:    err,
	}
}

// Read reads one record from r.  The record is a slice of strings with each
// string representing one field.
func (r *Reader) Read() (record []string, err error) {
	for {
		record, err = r.parseRecord()
		if record != nil {
			break
		}
		if err != nil {
			return nil, err
		}
	}

	if r.FieldsPerRecord > 0 {
		if len(record) != r.FieldsPerRecord {
			r.column = 0 // report at start of record
			return record, r.error(ErrFieldCount)
		}
	} else if r.FieldsPerRecord == 0 {
		r.FieldsPerRecord = len(record)
	}
	return record, nil
}

// ReadAll reads all the remaining records from r.
// Each record is a slice of fields.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read until EOF, it does not treat end of file as an error to be
// reported.
func (r *Reader) ReadAll() (records [][]string, err error) {
	for {
		record, err := r.Read()
		if err == io.EOF {
			return records, nil
		}
		if err != nil {
			return nil, err
		}
		records = append(records, record)
	}
	panic("unreachable")
}

// readRune reads one rune from r, folding \r\n to \n and keeping track
// of how far into the line we have read.  r.column will point to the start
// of this rune, not the end of this rune.
func (r *Reader) readRune() (rune, error) {
	r1, _, err := r.r.ReadRune()

	// Handle \r\n here.  We make the simplifying assumption that
	// anytime \r is followed by \n that it can be folded to \n.
	// We will not detect files which contain both \r\n and bare \n.
	if r1 == '\r' {
		r1, _, err = r.r.ReadRune()
		if err == nil {
			if r1 != '\n' {
				r.r.UnreadRune()
				r1 = '\r'
			}
		}
	}
	r.column++
	return r1, err
}

// unreadRune puts the last rune read from r back.
func (r *Reader) unreadRune() {
	r.r.UnreadRune()
	r.column--
}

// skip reads runes up to and including the rune delim or until error.
func (r *Reader) skip(delim rune) error {
	for {
		r1, err := r.readRune()
		if err != nil {
			return err
		}
		if r1 == delim {
			return nil
		}
	}
	panic("unreachable")
}

// parseRecord reads and parses a single csv record from r.
func (r *Reader) parseRecord() (fields []string, err error) {
	// Each record starts on a new line.  We increment our line
	// number (lines start at 1, not 0) and set column to -1
	// so as we increment in readRune it points to the character we read.
	r.line++
	r.column = -1

	// Peek at the first rune.  If it is an error we are done.
	// If we are support comments and it is the comment character
	// then skip to the end of line.

	r1, _, err := r.r.ReadRune()
	if err != nil {
		return nil, err
	}

	if r.Comment != 0 && r1 == r.Comment {
		return nil, r.skip('\n')
	}
	r.r.UnreadRune()

	// At this point we have at least one field.
	for {
		haveField, delim, err := r.parseField()
		if haveField {
			fields = append(fields, r.field.String())
		}
		if delim == '\n' || err == io.EOF {
			return fields, err
		} else if err != nil {
			return nil, err
		}
	}
	panic("unreachable")
}

// parseField parses the next field in the record.  The read field is
// located in r.field.  Delim is the first character not part of the field
// (r.Comma or '\n').
func (r *Reader) parseField() (haveField bool, delim rune, err error) {
	r.field.Reset()

	r1, err := r.readRune()
	if err != nil {
		// If we have EOF and are not at the start of a line
		// then we return the empty field.  We have already
		// checked for trailing commas if needed.
		if err == io.EOF && r.column != 0 {
			return true, 0, err
		}
		return false, 0, err
	}

	if r.TrimLeadingSpace {
		for r1 != '\n' && unicode.IsSpace(r1) {
			r1, err = r.readRune()
			if err != nil {
				return false, 0, err
			}
		}
	}

	switch r1 {
	case r.Comma:
		// will check below

	case '\n':
		// We are a trailing empty field or a blank line
		if r.column == 0 {
			return false, r1, nil
		}
		return true, r1, nil

	case '"':
		// quoted field
	Quoted:
		for {
			r1, err = r.readRune()
			if err != nil {
				if err == io.EOF {
					if r.LazyQuotes {
						return true, 0, err
					}
					return false, 0, r.error(ErrQuote)
				}
				return false, 0, err
			}
			switch r1 {
			case '"':
				r1, err = r.readRune()
				if err != nil || r1 == r.Comma {
					break Quoted
				}
				if r1 == '\n' {
					return true, r1, nil
				}
				if r1 != '"' {
					if !r.LazyQuotes {
						r.column--
						return false, 0, r.error(ErrQuote)
					}
					// accept the bare quote
					r.field.WriteRune('"')
				}
			case '\n':
				r.line++
				r.column = -1
			}
			r.field.WriteRune(r1)
		}

	default:
		// unquoted field
		for {
			r.field.WriteRune(r1)
			r1, err = r.readRune()
			if err != nil || r1 == r.Comma {
				break
			}
			if r1 == '\n' {
				return true, r1, nil
			}
			if !r.LazyQuotes && r1 == '"' {
				return false, 0, r.error(ErrBareQuote)
			}
		}
	}

	if err != nil {
		if err == io.EOF {
			return true, 0, err
		}
		return false, 0, err
	}

	if !r.TrailingComma {
		// We don't allow trailing commas.  See if we
		// are at the end of the line (being mindful
		// of trimming spaces).
		c := r.column
		r1, err = r.readRune()
		if r.TrimLeadingSpace {
			for r1 != '\n' && unicode.IsSpace(r1) {
				r1, err = r.readRune()
				if err != nil {
					break
				}
			}
		}
		if err == io.EOF || r1 == '\n' {
			r.column = c // report the comma
			return false, 0, r.error(ErrTrailingComma)
		}
		r.unreadRune()
	}
	return true, r1, nil
}
<commit_msg>csv: clarify what a negative FieldsPerRecord means<commit_after>// Copyright 2011 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 csv reads and writes comma-separated values (CSV) files.
//
// A csv file contains zero or more records of one or more fields per record.
// Each record is separated by the newline character. The final record may
// optionally be followed by a newline character.
//
//	field1,field2,field3
//
// White space is considered part of a field.
//
// Carriage returns before newline characters are silently removed.
//
// Blank lines are ignored.  A line with only whitespace characters (excluding
// the ending newline character) is not considered a blank line.
//
// Fields which start and stop with the quote character " are called
// quoted-fields.  The beginning and ending quote are not part of the
// field.
//
// The source:
//
//	normal string,"quoted-field"
//
// results in the fields
//
//	{`normal string`, `quoted-field`}
//
// Within a quoted-field a quote character followed by a second quote
// character is considered a single quote.
//
//	"the ""word"" is true","a ""quoted-field"""
//
// results in
//
//	{`the "word" is true`, `a "quoted-field"`}
//
// Newlines and commas may be included in a quoted-field
//
//	"Multi-line
//	field","comma is ,"
//
// results in
//
//	{`Multi-line
//	field`, `comma is ,`}
package csv

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"unicode"
)

// A ParseError is returned for parsing errors.
// The first line is 1.  The first column is 0.
type ParseError struct {
	Line   int   // Line where the error occurred
	Column int   // Column (rune index) where the error occurred
	Err    error // The actual error
}

func (e *ParseError) Error() string {
	return fmt.Sprintf("line %d, column %d: %s", e.Line, e.Column, e.Err)
}

// These are the errors that can be returned in ParseError.Error
var (
	ErrTrailingComma = errors.New("extra delimiter at end of line")
	ErrBareQuote     = errors.New("bare \" in non-quoted-field")
	ErrQuote         = errors.New("extraneous \" in field")
	ErrFieldCount    = errors.New("wrong number of fields in line")
)

// A Reader reads records from a CSV-encoded file.
//
// As returned by NewReader, a Reader expects input conforming to RFC 4180.
// The exported fields can be changed to customize the details before the
// first call to Read or ReadAll.
//
// Comma is the field delimiter.  It defaults to ','.
//
// Comment, if not 0, is the comment character. Lines beginning with the
// Comment character are ignored.
//
// If FieldsPerRecord is positive, Read requires each record to
// have the given number of fields.  If FieldsPerRecord is 0, Read sets it to
// the number of fields in the first record, so that future records must
// have the same field count.  If FieldsPerRecord is negative, no check is
// made and records may have a variable number of fields.
//
// If LazyQuotes is true, a quote may appear in an unquoted field and a
// non-doubled quote may appear in a quoted field.
//
// If TrailingComma is true, the last field may be an unquoted empty field.
//
// If TrimLeadingSpace is true, leading white space in a field is ignored.
type Reader struct {
	Comma            rune // Field delimiter (set to ',' by NewReader)
	Comment          rune // Comment character for start of line
	FieldsPerRecord  int  // Number of expected fields per record
	LazyQuotes       bool // Allow lazy quotes
	TrailingComma    bool // Allow trailing comma
	TrimLeadingSpace bool // Trim leading space
	line             int
	column           int
	r                *bufio.Reader
	field            bytes.Buffer
}

// NewReader returns a new Reader that reads from r.
func NewReader(r io.Reader) *Reader {
	return &Reader{
		Comma: ',',
		r:     bufio.NewReader(r),
	}
}

// error creates a new ParseError based on err.
func (r *Reader) error(err error) error {
	return &ParseError{
		Line:   r.line,
		Column: r.column,
		Err:    err,
	}
}

// Read reads one record from r.  The record is a slice of strings with each
// string representing one field.
func (r *Reader) Read() (record []string, err error) {
	for {
		record, err = r.parseRecord()
		if record != nil {
			break
		}
		if err != nil {
			return nil, err
		}
	}

	if r.FieldsPerRecord > 0 {
		if len(record) != r.FieldsPerRecord {
			r.column = 0 // report at start of record
			return record, r.error(ErrFieldCount)
		}
	} else if r.FieldsPerRecord == 0 {
		r.FieldsPerRecord = len(record)
	}
	return record, nil
}

// ReadAll reads all the remaining records from r.
// Each record is a slice of fields.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read until EOF, it does not treat end of file as an error to be
// reported.
func (r *Reader) ReadAll() (records [][]string, err error) {
	for {
		record, err := r.Read()
		if err == io.EOF {
			return records, nil
		}
		if err != nil {
			return nil, err
		}
		records = append(records, record)
	}
	panic("unreachable")
}

// readRune reads one rune from r, folding \r\n to \n and keeping track
// of how far into the line we have read.  r.column will point to the start
// of this rune, not the end of this rune.
func (r *Reader) readRune() (rune, error) {
	r1, _, err := r.r.ReadRune()

	// Handle \r\n here.  We make the simplifying assumption that
	// anytime \r is followed by \n that it can be folded to \n.
	// We will not detect files which contain both \r\n and bare \n.
	if r1 == '\r' {
		r1, _, err = r.r.ReadRune()
		if err == nil {
			if r1 != '\n' {
				r.r.UnreadRune()
				r1 = '\r'
			}
		}
	}
	r.column++
	return r1, err
}

// unreadRune puts the last rune read from r back.
func (r *Reader) unreadRune() {
	r.r.UnreadRune()
	r.column--
}

// skip reads runes up to and including the rune delim or until error.
func (r *Reader) skip(delim rune) error {
	for {
		r1, err := r.readRune()
		if err != nil {
			return err
		}
		if r1 == delim {
			return nil
		}
	}
	panic("unreachable")
}

// parseRecord reads and parses a single csv record from r.
func (r *Reader) parseRecord() (fields []string, err error) {
	// Each record starts on a new line.  We increment our line
	// number (lines start at 1, not 0) and set column to -1
	// so as we increment in readRune it points to the character we read.
	r.line++
	r.column = -1

	// Peek at the first rune.  If it is an error we are done.
	// If we are support comments and it is the comment character
	// then skip to the end of line.

	r1, _, err := r.r.ReadRune()
	if err != nil {
		return nil, err
	}

	if r.Comment != 0 && r1 == r.Comment {
		return nil, r.skip('\n')
	}
	r.r.UnreadRune()

	// At this point we have at least one field.
	for {
		haveField, delim, err := r.parseField()
		if haveField {
			fields = append(fields, r.field.String())
		}
		if delim == '\n' || err == io.EOF {
			return fields, err
		} else if err != nil {
			return nil, err
		}
	}
	panic("unreachable")
}

// parseField parses the next field in the record.  The read field is
// located in r.field.  Delim is the first character not part of the field
// (r.Comma or '\n').
func (r *Reader) parseField() (haveField bool, delim rune, err error) {
	r.field.Reset()

	r1, err := r.readRune()
	if err != nil {
		// If we have EOF and are not at the start of a line
		// then we return the empty field.  We have already
		// checked for trailing commas if needed.
		if err == io.EOF && r.column != 0 {
			return true, 0, err
		}
		return false, 0, err
	}

	if r.TrimLeadingSpace {
		for r1 != '\n' && unicode.IsSpace(r1) {
			r1, err = r.readRune()
			if err != nil {
				return false, 0, err
			}
		}
	}

	switch r1 {
	case r.Comma:
		// will check below

	case '\n':
		// We are a trailing empty field or a blank line
		if r.column == 0 {
			return false, r1, nil
		}
		return true, r1, nil

	case '"':
		// quoted field
	Quoted:
		for {
			r1, err = r.readRune()
			if err != nil {
				if err == io.EOF {
					if r.LazyQuotes {
						return true, 0, err
					}
					return false, 0, r.error(ErrQuote)
				}
				return false, 0, err
			}
			switch r1 {
			case '"':
				r1, err = r.readRune()
				if err != nil || r1 == r.Comma {
					break Quoted
				}
				if r1 == '\n' {
					return true, r1, nil
				}
				if r1 != '"' {
					if !r.LazyQuotes {
						r.column--
						return false, 0, r.error(ErrQuote)
					}
					// accept the bare quote
					r.field.WriteRune('"')
				}
			case '\n':
				r.line++
				r.column = -1
			}
			r.field.WriteRune(r1)
		}

	default:
		// unquoted field
		for {
			r.field.WriteRune(r1)
			r1, err = r.readRune()
			if err != nil || r1 == r.Comma {
				break
			}
			if r1 == '\n' {
				return true, r1, nil
			}
			if !r.LazyQuotes && r1 == '"' {
				return false, 0, r.error(ErrBareQuote)
			}
		}
	}

	if err != nil {
		if err == io.EOF {
			return true, 0, err
		}
		return false, 0, err
	}

	if !r.TrailingComma {
		// We don't allow trailing commas.  See if we
		// are at the end of the line (being mindful
		// of trimming spaces).
		c := r.column
		r1, err = r.readRune()
		if r.TrimLeadingSpace {
			for r1 != '\n' && unicode.IsSpace(r1) {
				r1, err = r.readRune()
				if err != nil {
					break
				}
			}
		}
		if err == io.EOF || r1 == '\n' {
			r.column = c // report the comma
			return false, 0, r.error(ErrTrailingComma)
		}
		r.unreadRune()
	}
	return true, r1, nil
}
<|endoftext|>


<commit_before>package builder

import (
	"fmt"
	"os"

	"github.com/fsouza/go-dockerclient"
)

func Build() error {
	endpoint := "unix:///var/run/docker.sock"
	client, err := docker.NewClient(endpoint)
	if err != nil {
		return fmt.Errorf("docker.Newclient: %s", err)
		return err
	}

	//dockerfile, err := os.Open("./Dockerfile")
	//if err != nil {
	//  return fmt.Errorf("os.Open: %s", err)
	//}
	//defer dockerfile.Close()

	var builder docker.BuildImageOptions

	builder.Name = "testapp"
	builder.RmTmpContainer = true
	builder.OutputStream = os.Stderr
	builder.Remote = "foo/bar/baz"

	err = client.BuildImage(builder)
	if err != nil {
		return fmt.Errorf("client.BuildImage: %s", err)
	}

	return err
}
<commit_msg>Working docker build of current directory<commit_after>package builder

import (
	"fmt"
	"os"

	"github.com/fsouza/go-dockerclient"
)

func Build() error {
	endpoint := "unix:///var/run/docker.sock"
	client, err := docker.NewClient(endpoint)
	if err != nil {
		return fmt.Errorf("docker.Newclient: %s", err)
		return err
	}

	var builder docker.BuildImageOptions

	builder.Name = "testapp"
	builder.RmTmpContainer = true
	builder.ContextDir = "."
	builder.OutputStream = os.Stderr

	err = client.BuildImage(builder)
	if err != nil {
		return fmt.Errorf("client.BuildImage: %s", err)
	}

	return err
}
<|endoftext|>


<commit_before>// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package buildkite

import "fmt"

// TeamService handles communication with the teams related
// methods of the buildkite API.
//
// buildkite API docs: https://buildkite.com/docs/api
type TeamsService struct {
	client *Client
}

// User represents a buildkite user.
type Team struct {
	ID          *string    `json:"id,omitempty" yaml:"id,omitempty"`
	Name        *string    `json:"name,omitempty" yaml:"name,omitempty"`
	Slug        *string    `json:"slug,omitempty" yaml:"slug,omitempty"`
	Description *string    `json:"description,omitempty" yaml:"description,omitempty"`
	Privacy     *string    `json:"privacy,omitempty" yaml:"privacy,omitempty"`
	Default     *bool      `json:"default,omitempty" yaml:"default,omitempty"`
	CreatedAt   *Timestamp `json:"created_at,omitempty" yaml:"created_at,omitempty"`
	CreatedBy   *User      `json:"created_by,omitempty" yaml:"created_by,omitempty"`
}

// Get the teams for an org.
//
// buildkite API docs: https://buildkite.com/docs/api
func (ts *TeamsService) List(org string) ([]Team, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/teams", org)

	req, err := ts.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	teams := new([]Team)
	resp, err := ts.client.Do(req, teams)
	if err != nil {
		return nil, resp, err
	}

	return *teams, resp, err
}
<commit_msg>fix pagination for teams<commit_after>// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package buildkite

import "fmt"

// TeamService handles communication with the teams related
// methods of the buildkite API.
//
// buildkite API docs: https://buildkite.com/docs/api
type TeamsService struct {
	client *Client
}

// User represents a buildkite user.
type Team struct {
	ID          *string    `json:"id,omitempty" yaml:"id,omitempty"`
	Name        *string    `json:"name,omitempty" yaml:"name,omitempty"`
	Slug        *string    `json:"slug,omitempty" yaml:"slug,omitempty"`
	Description *string    `json:"description,omitempty" yaml:"description,omitempty"`
	Privacy     *string    `json:"privacy,omitempty" yaml:"privacy,omitempty"`
	Default     *bool      `json:"default,omitempty" yaml:"default,omitempty"`
	CreatedAt   *Timestamp `json:"created_at,omitempty" yaml:"created_at,omitempty"`
	CreatedBy   *User      `json:"created_by,omitempty" yaml:"created_by,omitempty"`
}

// TeamsListOptions specifies the optional parameters to the
// TeamsService.List method.
type TeamsListOptions struct {
	ListOptions
}

// Get the teams for an org.
//
// buildkite API docs: https://buildkite.com/docs/api
func (ts *TeamsService) List(org string, opt *TeamsListOptions) ([]Team, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/teams", org)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := ts.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	teams := new([]Team)
	resp, err := ts.client.Do(req, teams)
	if err != nil {
		return nil, resp, err
	}

	return *teams, resp, err
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"github.com/workfit/tester/assert"
	"testing"
)

func TestUnmarshalGameNode(t *testing.T) {

	tests := []struct {
		description string
		in          string
		expected    *GameNode
	}{
		{
			"No nesting",
			`
				[
					"checkers",
					"blackjack"
				]
			`,
			&GameNode{
				Leafs: []string{
					"checkers",
					"blackjack",
				},
			},
		},
		{
			"One level nesting",
			`
				{
					"github.com/jkomoros":[
						"checkers",
						"blackjack"
					]
				}
			`,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"checkers",
							"blackjack",
						},
					},
				},
			},
		},
		{
			"two layer nesting",
			`
				{
					"github.com/jkomoros":{
						"boardgame": [
							"checkers",
							"blackjack"
						],
						"other-repo": [
							"pass"
						]
					}
				}
			`,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"boardgame": {
								Leafs: []string{
									"checkers",
									"blackjack",
								},
							},
							"other-repo": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
				},
			},
		},
		{
			"mixed mid and leaf",
			`
				{
					"github.com/jkomoros":{
						"boardgame": {
							"checkers": [
								""
							],
							"subdir": [
								"blackjack",
								"memory"
							]
						}
					}
				}
			`,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"boardgame": {
								Mids: map[string]*GameNode{
									"checkers": {
										Leafs: []string{
											"",
										},
									},
									"subdir": {
										Leafs: []string{
											"blackjack",
											"memory",
										},
									},
								},
							},
						},
					},
				},
			},
		},
	}

	for i, test := range tests {
		var gameNode *GameNode
		err := json.Unmarshal([]byte(test.in), &gameNode)
		if test.expected == nil {
			assert.For(t, i, test.description).ThatActual(err).IsNotNil()
		} else {
			assert.For(t, i, test.description).ThatActual(err).IsNil()
		}
		assert.For(t, i, test.description).ThatActual(gameNode).Equals(test.expected).ThenDiffOnFail()
	}

}

func TestGameNodeExtend(t *testing.T) {
	tests := []struct {
		description string
		base        *GameNode
		other       *GameNode
		expected    *GameNode
	}{
		{
			"No other",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
			nil,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
		},
		{
			"No base",
			nil,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
		},
		{
			"Simple no nesting one duplicate",
			&GameNode{
				Leafs: []string{
					"blackjack",
					"checkers",
				},
			},
			&GameNode{
				Leafs: []string{
					"checkers",
					"pass",
				},
			},
			&GameNode{
				Leafs: []string{
					"blackjack",
					"checkers",
					"pass",
				},
			},
		},
		{
			"One level nesting no overlap",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/bob": {
						Leafs: []string{
							"pass",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com": {
						Mids: map[string]*GameNode{
							"jkomoros": {
								Leafs: []string{
									"blackjack",
									"checkers",
								},
							},
							"bob": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
				},
			},
		},
		{
			"One level nesting partial overlap",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"checkers",
							"blackjack",
						},
					},
					"github.com/a": {
						Leafs: []string{
							"checkers",
							"blackjack",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"checkers",
							"memory",
						},
					},
					"github.com/b": {
						Leafs: []string{
							"pass",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com": {
						Mids: map[string]*GameNode{
							"jkomoros": {
								Leafs: []string{
									"blackjack",
									"checkers",
									"memory",
								},
							},
							"a": {
								Leafs: []string{
									"blackjack",
									"checkers",
								},
							},
							"b": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
				},
			},
		},
		{
			"overlap at second level",
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Mids: map[string]*GameNode{
							"github.com/jkomoros": {
								Leafs: []string{
									"checkers",
									"blackjack",
								},
							},
							"github.com/a": {
								Leafs: []string{
									"checkers",
									"blackjack",
								},
							},
						},
					},
					"two": {
						Leafs: []string{
							"a",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Mids: map[string]*GameNode{
							"github.com/jkomoros": {
								Leafs: []string{
									"checkers",
									"memory",
								},
							},
							"github.com/b": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
					"three": {
						Leafs: []string{
							"b",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Mids: map[string]*GameNode{
							"github.com": {
								Mids: map[string]*GameNode{
									"jkomoros": {
										Leafs: []string{
											"blackjack",
											"checkers",
											"memory",
										},
									},
									"a": {
										Leafs: []string{
											"blackjack",
											"checkers",
										},
									},
									"b": {
										Leafs: []string{
											"pass",
										},
									},
								},
							},
						},
					},
					"two": {
						Leafs: []string{
							"a",
						},
					},
					"three": {
						Leafs: []string{
							"b",
						},
					},
				},
			},
		},
		{
			"overlap leaf + mid",
			&GameNode{
				Mids: map[string]*GameNode{
					"toplevelgame": {
						Leafs: []string{
							"",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"toplevelgame": {
						Leafs: []string{
							"blackjack",
							"memory",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"toplevelgame": {
						Leafs: []string{
							"",
							"blackjack",
							"memory",
						},
					},
				},
			},
		},
		{
			"key is mid and leafs (test normalize is called)",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"other-dir/bar",
							"other-dir/baz",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
							"other-dir": {
								Leafs: []string{
									"bar",
									"baz",
								},
							},
						},
					},
				},
			},
		},
	}

	for i, test := range tests {

		result := test.base.extend(test.other)
		assert.For(t, i, test.description).ThatActual(result).Equals(test.expected).ThenDiffOnFail()

	}
}

func TestGameNodeList(t *testing.T) {
	tests := []struct {
		description string
		in          *GameNode
		expected    []string
	}{
		{
			"No nest",
			&GameNode{
				Leafs: []string{
					"b",
					"a",
					"c",
				},
			},
			[]string{
				"a",
				"b",
				"c",
			},
		},
		{
			"Single nest",
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Leafs: []string{
							"a",
							"b",
						},
					},
					"two": {
						Leafs: []string{
							"c",
							"d",
						},
					},
				},
			},
			[]string{
				"one/a",
				"one/b",
				"two/c",
				"two/d",
			},
		},
		{
			"Double nest with path separator in key",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"one": {
								Leafs: []string{
									"a",
									"b",
								},
							},
							"two": {
								Leafs: []string{
									"c",
									"d",
								},
							},
						},
					},
				},
			},
			[]string{
				"github.com/jkomoros/one/a",
				"github.com/jkomoros/one/b",
				"github.com/jkomoros/two/c",
				"github.com/jkomoros/two/d",
			},
		},
		{
			"Mixed leaf and min",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"toplevelgame": {
								Leafs: []string{
									"",
								},
							},
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
						},
					},
				},
			},
			[]string{
				"github.com/jkomoros/examples/blackjack",
				"github.com/jkomoros/examples/memory",
				"github.com/jkomoros/toplevelgame",
			},
		},
		{
			"Mixed leaf and min with extra",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"toplevelgame": {
								Leafs: []string{
									"",
									"another",
								},
							},
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
						},
					},
				},
			},
			[]string{
				"github.com/jkomoros/examples/blackjack",
				"github.com/jkomoros/examples/memory",
				"github.com/jkomoros/toplevelgame",
				"github.com/jkomoros/toplevelgame/another",
			},
		},
	}

	for i, test := range tests {
		result := test.in.List()
		assert.For(t, i, test.description).ThatActual(result).Equals(test.expected).ThenDiffOnFail()
	}
}
<commit_msg>Add a (commented out) test that shiould be supported for gamenode.normalize that currently doesn't work. Part of #655.<commit_after>package config

import (
	"encoding/json"
	"github.com/davecgh/go-spew/spew"
	"github.com/workfit/tester/assert"
	"testing"
)

func TestUnmarshalGameNode(t *testing.T) {

	tests := []struct {
		description string
		in          string
		expected    *GameNode
	}{
		{
			"No nesting",
			`
				[
					"checkers",
					"blackjack"
				]
			`,
			&GameNode{
				Leafs: []string{
					"checkers",
					"blackjack",
				},
			},
		},
		{
			"One level nesting",
			`
				{
					"github.com/jkomoros":[
						"checkers",
						"blackjack"
					]
				}
			`,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"checkers",
							"blackjack",
						},
					},
				},
			},
		},
		{
			"two layer nesting",
			`
				{
					"github.com/jkomoros":{
						"boardgame": [
							"checkers",
							"blackjack"
						],
						"other-repo": [
							"pass"
						]
					}
				}
			`,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"boardgame": {
								Leafs: []string{
									"checkers",
									"blackjack",
								},
							},
							"other-repo": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
				},
			},
		},
		{
			"mixed mid and leaf",
			`
				{
					"github.com/jkomoros":{
						"boardgame": {
							"checkers": [
								""
							],
							"subdir": [
								"blackjack",
								"memory"
							]
						}
					}
				}
			`,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"boardgame": {
								Mids: map[string]*GameNode{
									"checkers": {
										Leafs: []string{
											"",
										},
									},
									"subdir": {
										Leafs: []string{
											"blackjack",
											"memory",
										},
									},
								},
							},
						},
					},
				},
			},
		},
	}

	for i, test := range tests {
		var gameNode *GameNode
		err := json.Unmarshal([]byte(test.in), &gameNode)
		if test.expected == nil {
			assert.For(t, i, test.description).ThatActual(err).IsNotNil()
		} else {
			assert.For(t, i, test.description).ThatActual(err).IsNil()
		}
		assert.For(t, i, test.description).ThatActual(gameNode).Equals(test.expected).ThenDiffOnFail()
	}

}

func TestGameNodeExtend(t *testing.T) {
	tests := []struct {
		description string
		base        *GameNode
		other       *GameNode
		expected    *GameNode
	}{
		{
			"No other",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
			nil,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
		},
		{
			"No base",
			nil,
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
		},
		{
			"Simple no nesting one duplicate",
			&GameNode{
				Leafs: []string{
					"blackjack",
					"checkers",
				},
			},
			&GameNode{
				Leafs: []string{
					"checkers",
					"pass",
				},
			},
			&GameNode{
				Leafs: []string{
					"blackjack",
					"checkers",
					"pass",
				},
			},
		},
		{
			"One level nesting no overlap",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"blackjack",
							"checkers",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/bob": {
						Leafs: []string{
							"pass",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com": {
						Mids: map[string]*GameNode{
							"jkomoros": {
								Leafs: []string{
									"blackjack",
									"checkers",
								},
							},
							"bob": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
				},
			},
		},
		{
			"One level nesting partial overlap",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"checkers",
							"blackjack",
						},
					},
					"github.com/a": {
						Leafs: []string{
							"checkers",
							"blackjack",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"checkers",
							"memory",
						},
					},
					"github.com/b": {
						Leafs: []string{
							"pass",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com": {
						Mids: map[string]*GameNode{
							"jkomoros": {
								Leafs: []string{
									"blackjack",
									"checkers",
									"memory",
								},
							},
							"a": {
								Leafs: []string{
									"blackjack",
									"checkers",
								},
							},
							"b": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
				},
			},
		},
		{
			"overlap at second level",
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Mids: map[string]*GameNode{
							"github.com/jkomoros": {
								Leafs: []string{
									"checkers",
									"blackjack",
								},
							},
							"github.com/a": {
								Leafs: []string{
									"checkers",
									"blackjack",
								},
							},
						},
					},
					"two": {
						Leafs: []string{
							"a",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Mids: map[string]*GameNode{
							"github.com/jkomoros": {
								Leafs: []string{
									"checkers",
									"memory",
								},
							},
							"github.com/b": {
								Leafs: []string{
									"pass",
								},
							},
						},
					},
					"three": {
						Leafs: []string{
							"b",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Mids: map[string]*GameNode{
							"github.com": {
								Mids: map[string]*GameNode{
									"jkomoros": {
										Leafs: []string{
											"blackjack",
											"checkers",
											"memory",
										},
									},
									"a": {
										Leafs: []string{
											"blackjack",
											"checkers",
										},
									},
									"b": {
										Leafs: []string{
											"pass",
										},
									},
								},
							},
						},
					},
					"two": {
						Leafs: []string{
							"a",
						},
					},
					"three": {
						Leafs: []string{
							"b",
						},
					},
				},
			},
		},
		{
			"overlap leaf + mid",
			&GameNode{
				Mids: map[string]*GameNode{
					"toplevelgame": {
						Leafs: []string{
							"",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"toplevelgame": {
						Leafs: []string{
							"blackjack",
							"memory",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"toplevelgame": {
						Leafs: []string{
							"",
							"blackjack",
							"memory",
						},
					},
				},
			},
		},
		{
			"key is mid and leafs (test normalize is called)",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Leafs: []string{
							"other-dir/bar",
							"other-dir/baz",
						},
					},
				},
			},
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
							"other-dir": {
								Leafs: []string{
									"bar",
									"baz",
								},
							},
						},
					},
				},
			},
		},
		/*
		   //TODO: make this test work and uncomment it
		   		{
		   			"inner key should be merged to normalize",
		   			&GameNode{
		   				Mids: map[string]*GameNode{
		   					"github.com/jkomoros/boardgame/examples": {
		   						Leafs: []string{
		   							"blackjack",
		   							"checkers",
		   							"memory",
		   						},
		   					},
		   				},
		   			},
		   			&GameNode{
		   				Mids: map[string]*GameNode{
		   					"github.com/jkomoros/games": {
		   						Leafs: []string{
		   							"darwin",
		   						},
		   					},
		   				},
		   			},
		   			&GameNode{
		   				Mids: map[string]*GameNode{
		   					"github.com/jkomoros": {
		   						Mids: map[string]*GameNode{
		   							"boardgame/examples": {
		   								Leafs: []string{
		   									"blackjack",
		   									"checkers",
		   									"memory",
		   								},
		   							},
		   							"games": {
		   								Leafs: []string{
		   									"darwin",
		   								},
		   							},
		   						},
		   					},
		   				},
		   			},
		   		},
		*/
	}

	for i, test := range tests {

		result := test.base.extend(test.other)
		if !assert.For(t, i, test.description).ThatActual(result).Equals(test.expected).ThenDiffOnFail().Passed() {
			spew.Dump(result)
		}

	}
}

func TestGameNodeList(t *testing.T) {
	tests := []struct {
		description string
		in          *GameNode
		expected    []string
	}{
		{
			"No nest",
			&GameNode{
				Leafs: []string{
					"b",
					"a",
					"c",
				},
			},
			[]string{
				"a",
				"b",
				"c",
			},
		},
		{
			"Single nest",
			&GameNode{
				Mids: map[string]*GameNode{
					"one": {
						Leafs: []string{
							"a",
							"b",
						},
					},
					"two": {
						Leafs: []string{
							"c",
							"d",
						},
					},
				},
			},
			[]string{
				"one/a",
				"one/b",
				"two/c",
				"two/d",
			},
		},
		{
			"Double nest with path separator in key",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"one": {
								Leafs: []string{
									"a",
									"b",
								},
							},
							"two": {
								Leafs: []string{
									"c",
									"d",
								},
							},
						},
					},
				},
			},
			[]string{
				"github.com/jkomoros/one/a",
				"github.com/jkomoros/one/b",
				"github.com/jkomoros/two/c",
				"github.com/jkomoros/two/d",
			},
		},
		{
			"Mixed leaf and min",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"toplevelgame": {
								Leafs: []string{
									"",
								},
							},
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
						},
					},
				},
			},
			[]string{
				"github.com/jkomoros/examples/blackjack",
				"github.com/jkomoros/examples/memory",
				"github.com/jkomoros/toplevelgame",
			},
		},
		{
			"Mixed leaf and min with extra",
			&GameNode{
				Mids: map[string]*GameNode{
					"github.com/jkomoros": {
						Mids: map[string]*GameNode{
							"toplevelgame": {
								Leafs: []string{
									"",
									"another",
								},
							},
							"examples": {
								Leafs: []string{
									"blackjack",
									"memory",
								},
							},
						},
					},
				},
			},
			[]string{
				"github.com/jkomoros/examples/blackjack",
				"github.com/jkomoros/examples/memory",
				"github.com/jkomoros/toplevelgame",
				"github.com/jkomoros/toplevelgame/another",
			},
		},
	}

	for i, test := range tests {
		result := test.in.List()
		assert.For(t, i, test.description).ThatActual(result).Equals(test.expected).ThenDiffOnFail()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"math"
	"net/http"
	"strconv"

	"github.com/gorilla/Schema"
	"github.com/gorilla/mux"
	"github.com/mt2d2/forum/model"
)

func numberOfForumPages(forum *model.Forum) int {
	return int(math.Ceil(float64(forum.TopicCount) / float64(limitTopics)))
}

func (app *app) handleIndex(w http.ResponseWriter, req *http.Request) {
	forums, err := model.FindForums(app.db)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	results := make(map[string]interface{})
	results["forums"] = forums

	app.renderTemplate(w, req, "index", results)
}

func (app *app) handleForum(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	pageOffset := 0
	if page, ok := vars["page"]; ok {
		if val, err := strconv.Atoi(page); err == nil {
			pageOffset = val - 1
		}
	}

	forum, err := model.FindOneForum(app.db, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	numberOfPages := numberOfForumPages(forum)
	pageIndicies := make([]int, numberOfPages)
	for i := 0; i < numberOfPages; i++ {
		pageIndicies[i] = i + 1
	}
	currentPage := int(pageOffset + 1)

	topics, err := model.FindTopics(app.db, id, limitTopics, pageOffset*limitTopics)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	app.addBreadCrumb("/forum/"+strconv.Itoa(forum.Id), forum.Title)
	if currentPage > 1 {
		app.addBreadCrumb("forum/"+strconv.Itoa(forum.Id)+"/page/"+strconv.Itoa(currentPage), "page "+strconv.Itoa(currentPage))
	}

	results := make(map[string]interface{})
	results["forum"] = forum
	results["topics"] = topics
	results["pageIndicies"] = pageIndicies
	results["currentPage"] = currentPage

	app.renderTemplate(w, req, "forum", results)
}

func (app *app) handleAddTopic(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]

	results := make(map[string]interface{})
	results["ForumId"] = id
	app.renderTemplate(w, req, "addTopic", results)
}

func (app *app) handleSaveTopic(w http.ResponseWriter, req *http.Request) {
	req.ParseForm()

	topic := model.NewTopic()
	decoder := schema.NewDecoder()
	err := decoder.Decode(topic, req.PostForm)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	ok, errors := model.ValidateTopic(app.db, topic)
	if !ok {
		app.addErrorFlashes(w, req, errors)
		http.Redirect(w, req, "/forum/"+req.PostFormValue("ForumId")+"/add", http.StatusFound)
		return
	}

	err = model.SaveTopic(app.db, topic)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	http.Redirect(w, req, "/forum/"+req.PostFormValue("ForumId"), http.StatusFound)
}
<commit_msg>add breadcrumb to new topic<commit_after>package main

import (
	"math"
	"net/http"
	"strconv"

	"github.com/gorilla/Schema"
	"github.com/gorilla/mux"
	"github.com/mt2d2/forum/model"
)

func numberOfForumPages(forum *model.Forum) int {
	return int(math.Ceil(float64(forum.TopicCount) / float64(limitTopics)))
}

func (app *app) handleIndex(w http.ResponseWriter, req *http.Request) {
	forums, err := model.FindForums(app.db)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	results := make(map[string]interface{})
	results["forums"] = forums

	app.renderTemplate(w, req, "index", results)
}

func (app *app) handleForum(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	pageOffset := 0
	if page, ok := vars["page"]; ok {
		if val, err := strconv.Atoi(page); err == nil {
			pageOffset = val - 1
		}
	}

	forum, err := model.FindOneForum(app.db, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	numberOfPages := numberOfForumPages(forum)
	pageIndicies := make([]int, numberOfPages)
	for i := 0; i < numberOfPages; i++ {
		pageIndicies[i] = i + 1
	}
	currentPage := int(pageOffset + 1)

	topics, err := model.FindTopics(app.db, id, limitTopics, pageOffset*limitTopics)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	app.addBreadCrumb("/forum/"+strconv.Itoa(forum.Id), forum.Title)
	if currentPage > 1 {
		app.addBreadCrumb("forum/"+strconv.Itoa(forum.Id)+"/page/"+strconv.Itoa(currentPage), "page "+strconv.Itoa(currentPage))
	}

	results := make(map[string]interface{})
	results["forum"] = forum
	results["topics"] = topics
	results["pageIndicies"] = pageIndicies
	results["currentPage"] = currentPage

	app.renderTemplate(w, req, "forum", results)
}

func (app *app) handleAddTopic(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]

	forum, err := model.FindOneForum(app.db, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	app.addBreadCrumb("/forum/"+strconv.Itoa(forum.Id), forum.Title)

	results := make(map[string]interface{})
	results["ForumId"] = id
	app.renderTemplate(w, req, "addTopic", results)
}

func (app *app) handleSaveTopic(w http.ResponseWriter, req *http.Request) {
	req.ParseForm()

	topic := model.NewTopic()
	decoder := schema.NewDecoder()
	err := decoder.Decode(topic, req.PostForm)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	ok, errors := model.ValidateTopic(app.db, topic)
	if !ok {
		app.addErrorFlashes(w, req, errors)
		http.Redirect(w, req, "/forum/"+req.PostFormValue("ForumId")+"/add", http.StatusFound)
		return
	}

	err = model.SaveTopic(app.db, topic)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	http.Redirect(w, req, "/forum/"+req.PostFormValue("ForumId"), http.StatusFound)
}
<|endoftext|>


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

import (
    "sippy/conf"
    "sippy/headers"
    "sippy/time"
    "sippy/types"
)

type UaStateConnected struct {
    *uaStateGeneric
    ka_tr       sippy_types.ClientTransaction
    rtime       *sippy_time.MonoTime
    origin      string
}

func NewUaStateConnected(ua sippy_types.UA, rtime *sippy_time.MonoTime, origin string, config sippy_conf.Config) *UaStateConnected {
    ua.SetBranch("")
    self := &UaStateConnected{
        uaStateGeneric : newUaStateGeneric(ua, config),
        ka_tr       : nil,
        rtime       : rtime,
        origin      : origin,
    }
    newKeepaliveController(ua, config)
    self.connected = true
    return self
}

func (self *UaStateConnected) OnActivation() {
    if self.rtime != nil {
        self.ua.ConnCb(self.rtime, self.origin)
    }
}

func (self *UaStateConnected) String() string {
    return "Connected"
}

func (self *UaStateConnected) RecvRequest(req sippy_types.SipRequest, t sippy_types.ServerTransaction) sippy_types.UaState {
    if req.GetMethod() == "REFER" {
        if req.GetReferTo() == nil {
            t.SendResponse(req.GenResponse(400, "Bad Request", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
            return nil
        }
        t.SendResponse(req.GenResponse(202, "Accepted", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        refer_to, err := req.GetReferTo().GetBody(self.config)
        if err != nil {
            self.config.ErrorLogger().Error("UaStateConnected::RecvRequest: #1: " + err.Error())
            return nil
        }
        self.ua.Enqueue(NewCCEventDisconnect(refer_to.GetCopy(), req.GetRtime(), self.ua.GetOrigin()))
        self.ua.RecvEvent(NewCCEventDisconnect(nil, req.GetRtime(), self.ua.GetOrigin()))
        return nil
    }
    if req.GetMethod() == "INVITE" {
        self.ua.SetUasResp(req.GenResponse(100, "Trying", nil, self.ua.GetLocalUA().AsSipServer()))
        t.SendResponse(self.ua.GetUasResp(), false, nil)
        body := req.GetBody()
        if body == nil {
            // Some brain-damaged stacks use body-less re-INVITE as a means
            // for putting session on hold. Quick and dirty hack to make this
            // scenario working.
            body = self.ua.GetRSDP().GetCopy()
            parsed_body, err := body.GetParsedBody()
            if err != nil {
                self.config.ErrorLogger().Error("UaStateConnected::RecvRequest: #2: " + err.Error())
                return nil
            }
            parsed_body.SetCHeaderAddr("0.0.0.0")
        } else if self.ua.GetRSDP().String() == body.String() {
            t.SendResponse(req.GenResponse(200, "OK", self.ua.GetLSDP(), self.ua.GetLocalUA().AsSipServer()), false, nil)
            return nil
        }
        event := NewCCEventUpdate(req.GetRtime(), self.ua.GetOrigin(), req.GetReason(), req.GetMaxForwards(), body)
        if body != nil {
            if self.ua.HasOnRemoteSdpChange() {
                self.ua.OnRemoteSdpChange(body, func (x sippy_types.MsgBody) { self.ua.DelayedRemoteSdpUpdate(event, x) })
                return NewUasStateUpdating(self.ua, self.config)
            } else {
                self.ua.SetRSDP(body.GetCopy())
            }
        } else {
            self.ua.SetRSDP(nil)
        }
        self.ua.Enqueue(event)
        return NewUasStateUpdating(self.ua, self.config)
    }
    if req.GetMethod() == "BYE" {
        t.SendResponse(req.GenResponse(200, "OK", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        //print "BYE received in the Connected state, going to the Disconnected state"
        var also *sippy_header.SipAddress
        if len(req.GetAlso()) > 0 {
            also_body, err := req.GetAlso()[0].GetBody(self.config)
            if err != nil {
                self.config.ErrorLogger().Error("UaStateConnected::RecvRequest: #3: " + err.Error())
                return nil
            }
            also = also_body.GetCopy()
        }
        event := NewCCEventDisconnect(also, req.GetRtime(), self.ua.GetOrigin())
        event.SetReason(req.GetReason())
        self.ua.Enqueue(event)
        self.ua.CancelCreditTimer()
        self.ua.SetDisconnectTs(req.GetRtime())
        return NewUaStateDisconnected(self.ua, req.GetRtime(), self.ua.GetOrigin(), 0, req, self.config)
    }
    if req.GetMethod() == "INFO" {
        t.SendResponse(req.GenResponse(200, "OK", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        event := NewCCEventInfo(req.GetRtime(), self.ua.GetOrigin(), req.GetBody())
        event.SetReason(req.GetReason())
        self.ua.Enqueue(event)
        return nil
    }
    if req.GetMethod() == "OPTIONS" || req.GetMethod() == "UPDATE" {
        t.SendResponse(req.GenResponse(200, "OK", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        return nil
    }
    //print "wrong request %s in the state Connected" % req.GetMethod()
    return nil
}

func (self *UaStateConnected) RecvEvent(event sippy_types.CCEvent) (sippy_types.UaState, error) {
    var err error
    var req sippy_types.SipRequest

    eh := event.GetExtraHeaders()
    ok := false
    var redirect *sippy_header.SipAddress = nil

    switch ev := event.(type) {
    case *CCEventDisconnect:
        redirect = ev.GetRedirectURL()
        ok = true
    case *CCEventRedirect:
        redirect = ev.GetRedirectURL()
        ok = true
    case *CCEventFail:
        ok = true
    }
    if ok {
        //println("event", event.String(), "received in the Connected state sending BYE")
        if redirect != nil && self.ua.ShouldUseRefer() {
            var lUri *sippy_header.SipAddress

            req, err = self.ua.GenRequest("REFER", nil, "", "", nil, eh...)
            if err != nil {
                return nil, err
            }
            self.ua.IncLCSeq()
            also := sippy_header.NewSipReferTo(redirect)
            req.AppendHeader(also)
            lUri, err = self.ua.GetLUri().GetBody(self.config)
            if err != nil {
                return nil, err
            }
            rby := sippy_header.NewSipReferredBy(sippy_header.NewSipAddress("", lUri.GetUrl()))
            req.AppendHeader(rby)
            self.ua.SipTM().BeginNewClientTransaction(req, newRedirectController(self.ua), self.ua.GetSessionLock(), self.ua.GetSourceAddress(), nil, self.ua.BeforeRequestSent)
        } else {
            req, err = self.ua.GenRequest("BYE", nil, "", "", nil, eh...)
            if err != nil {
                return nil, err
            }
            self.ua.IncLCSeq()
            if redirect != nil {
                also := sippy_header.NewSipAlso(redirect)
                req.AppendHeader(also)
            }
            self.ua.SipTM().BeginNewClientTransaction(req, nil, self.ua.GetSessionLock(), self.ua.GetSourceAddress(), nil, self.ua.BeforeRequestSent)
        }
        self.ua.CancelCreditTimer()
        self.ua.SetDisconnectTs(event.GetRtime())
        return NewUaStateDisconnected(self.ua, event.GetRtime(), event.GetOrigin(), 0, nil, self.config), nil
    }
    if _event, ok := event.(*CCEventUpdate); ok {
        var tr sippy_types.ClientTransaction

        body := _event.GetBody()
        if self.ua.GetLSDP() != nil && body != nil && self.ua.GetLSDP().String() == body.String() {
            if self.ua.GetRSDP() != nil {
                self.ua.Enqueue(NewCCEventConnect(200, "OK", self.ua.GetRSDP().GetCopy(), event.GetRtime(), event.GetOrigin()))
            } else {
                self.ua.Enqueue(NewCCEventConnect(200, "OK", nil, event.GetRtime(), event.GetOrigin()))
            }
            return nil, nil
        }
        if body != nil && self.ua.HasOnLocalSdpChange() && body.NeedsUpdate() {
            err := self.ua.OnLocalSdpChange(body, event, func(sippy_types.MsgBody) { self.ua.RecvEvent(event) })
            if err != nil {
                ev := NewCCEventFail(400, "Malformed SDP Body", event.GetRtime(), "")
                ev.SetWarning(err.Error())
                self.ua.Enqueue(ev)
            }
            return nil, nil
        }
        eh2 := eh
        if _event.GetMaxForwards() != nil {
            var max_forwards *sippy_header.SipNumericHF

            max_forwards, err = _event.GetMaxForwards().GetBody()
            if err != nil {
                return nil, err
            }
            if max_forwards.Number <= 0 {
                self.ua.Enqueue(NewCCEventFail(483, "Too Many Hops", event.GetRtime(), ""))
                return nil, nil
            }
            eh2 = append(eh2, sippy_header.NewSipMaxForwards(max_forwards.Number - 1))
        }
        req, err = self.ua.GenRequest("INVITE", body, "", "", nil, eh2...)
        if err != nil {
            return nil, err
        }
        self.ua.IncLCSeq()
        self.ua.SetLSDP(body)
        tr, err = self.ua.PrepTr(req)
        if err != nil {
            return nil, err
        }
        self.ua.SetClientTransaction(tr)
        self.ua.SipTM().BeginClientTransaction(req, tr)
        return NewUacStateUpdating(self.ua, self.config), nil
    }
    if _event, ok := event.(*CCEventInfo); ok {
        body := _event.GetBody()
        req, err = self.ua.GenRequest("INFO", nil, "", "", nil, eh...)
        if err != nil {
            return nil, err
        }
        req.SetBody(body)
        self.ua.IncLCSeq()
        self.ua.SipTM().BeginNewClientTransaction(req, nil, self.ua.GetSessionLock(), self.ua.GetSourceAddress(), nil, self.ua.BeforeRequestSent)
        return nil, nil
    }
    if _event, ok := event.(*CCEventConnect); ok && self.ua.GetPendingTr() != nil {
        self.ua.CancelExpireTimer()
        body := _event.GetBody()
        if body != nil && self.ua.HasOnLocalSdpChange() && body.NeedsUpdate() {
            self.ua.OnLocalSdpChange(body, event, func(sippy_types.MsgBody) { self.ua.RecvEvent(event) })
            return nil, nil
        }
        self.ua.StartCreditTimer(event.GetRtime())
        self.ua.SetConnectTs(event.GetRtime())
        self.ua.SetLSDP(body)
        self.ua.GetPendingTr().GetACK().SetBody(body)
        self.ua.GetPendingTr().SendACK()
        self.ua.SetPendingTr(nil)
        self.ua.ConnCb(event.GetRtime(), self.ua.GetOrigin())
    }
    //print "wrong event %s in the Connected state" % event
    return nil, nil
}

func (self *UaStateConnected) OnStateChange() {
    if self.ka_tr != nil {
        self.ka_tr.Cancel()
        self.ka_tr = nil
    }
    if self.ua.GetPendingTr() != nil {
        self.ua.GetPendingTr().SendACK()
        self.ua.SetPendingTr(nil)
    }
    self.ua.CancelExpireTimer()
}

func (self *UaStateConnected) RecvACK(req sippy_types.SipRequest) {
    rtime := req.GetRtime()
    body := req.GetBody()
    event := NewCCEventConnect(0, "ACK", body, rtime, self.ua.GetOrigin())
    self.ua.CancelExpireTimer()
    self.ua.StartCreditTimer(rtime)
    self.ua.SetConnectTs(rtime)
    self.ua.ConnCb(rtime, self.ua.GetOrigin())
    if body != nil {
        if self.ua.HasOnRemoteSdpChange() {
            self.ua.OnRemoteSdpChange(body, func (x sippy_types.MsgBody) { self.ua.DelayedRemoteSdpUpdate(event, x) })
            return
        } else {
            self.ua.SetRSDP(body.GetCopy())
        }
    } else {
        self.ua.SetRSDP(nil)
    }
    self.ua.Enqueue(event)
    return
}
<commit_msg>Do not forget to add Contact: to the 200 OK.<commit_after>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2015 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2015 Andrii Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "sippy/conf"
    "sippy/headers"
    "sippy/time"
    "sippy/types"
)

type UaStateConnected struct {
    *uaStateGeneric
    ka_tr       sippy_types.ClientTransaction
    rtime       *sippy_time.MonoTime
    origin      string
}

func NewUaStateConnected(ua sippy_types.UA, rtime *sippy_time.MonoTime, origin string, config sippy_conf.Config) *UaStateConnected {
    ua.SetBranch("")
    self := &UaStateConnected{
        uaStateGeneric : newUaStateGeneric(ua, config),
        ka_tr       : nil,
        rtime       : rtime,
        origin      : origin,
    }
    newKeepaliveController(ua, config)
    self.connected = true
    return self
}

func (self *UaStateConnected) OnActivation() {
    if self.rtime != nil {
        self.ua.ConnCb(self.rtime, self.origin)
    }
}

func (self *UaStateConnected) String() string {
    return "Connected"
}

func (self *UaStateConnected) RecvRequest(req sippy_types.SipRequest, t sippy_types.ServerTransaction) sippy_types.UaState {
    if req.GetMethod() == "REFER" {
        if req.GetReferTo() == nil {
            t.SendResponse(req.GenResponse(400, "Bad Request", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
            return nil
        }
        t.SendResponse(req.GenResponse(202, "Accepted", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        refer_to, err := req.GetReferTo().GetBody(self.config)
        if err != nil {
            self.config.ErrorLogger().Error("UaStateConnected::RecvRequest: #1: " + err.Error())
            return nil
        }
        self.ua.Enqueue(NewCCEventDisconnect(refer_to.GetCopy(), req.GetRtime(), self.ua.GetOrigin()))
        self.ua.RecvEvent(NewCCEventDisconnect(nil, req.GetRtime(), self.ua.GetOrigin()))
        return nil
    }
    if req.GetMethod() == "INVITE" {
        self.ua.SetUasResp(req.GenResponse(100, "Trying", nil, self.ua.GetLocalUA().AsSipServer()))
        t.SendResponse(self.ua.GetUasResp(), false, nil)
        body := req.GetBody()
        if body == nil {
            // Some brain-damaged stacks use body-less re-INVITE as a means
            // for putting session on hold. Quick and dirty hack to make this
            // scenario working.
            body = self.ua.GetRSDP().GetCopy()
            parsed_body, err := body.GetParsedBody()
            if err != nil {
                self.config.ErrorLogger().Error("UaStateConnected::RecvRequest: #2: " + err.Error())
                return nil
            }
            parsed_body.SetCHeaderAddr("0.0.0.0")
        } else if self.ua.GetRSDP().String() == body.String() {
            self.ua.SendUasResponse(t, 200, "OK", self.ua.GetLSDP(), self.ua.GetLContacts(), false /*ack_wait*/)
            return nil
        }
        event := NewCCEventUpdate(req.GetRtime(), self.ua.GetOrigin(), req.GetReason(), req.GetMaxForwards(), body)
        if body != nil {
            if self.ua.HasOnRemoteSdpChange() {
                self.ua.OnRemoteSdpChange(body, func (x sippy_types.MsgBody) { self.ua.DelayedRemoteSdpUpdate(event, x) })
                return NewUasStateUpdating(self.ua, self.config)
            } else {
                self.ua.SetRSDP(body.GetCopy())
            }
        } else {
            self.ua.SetRSDP(nil)
        }
        self.ua.Enqueue(event)
        return NewUasStateUpdating(self.ua, self.config)
    }
    if req.GetMethod() == "BYE" {
        t.SendResponse(req.GenResponse(200, "OK", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        //print "BYE received in the Connected state, going to the Disconnected state"
        var also *sippy_header.SipAddress
        if len(req.GetAlso()) > 0 {
            also_body, err := req.GetAlso()[0].GetBody(self.config)
            if err != nil {
                self.config.ErrorLogger().Error("UaStateConnected::RecvRequest: #3: " + err.Error())
                return nil
            }
            also = also_body.GetCopy()
        }
        event := NewCCEventDisconnect(also, req.GetRtime(), self.ua.GetOrigin())
        event.SetReason(req.GetReason())
        self.ua.Enqueue(event)
        self.ua.CancelCreditTimer()
        self.ua.SetDisconnectTs(req.GetRtime())
        return NewUaStateDisconnected(self.ua, req.GetRtime(), self.ua.GetOrigin(), 0, req, self.config)
    }
    if req.GetMethod() == "INFO" {
        t.SendResponse(req.GenResponse(200, "OK", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        event := NewCCEventInfo(req.GetRtime(), self.ua.GetOrigin(), req.GetBody())
        event.SetReason(req.GetReason())
        self.ua.Enqueue(event)
        return nil
    }
    if req.GetMethod() == "OPTIONS" || req.GetMethod() == "UPDATE" {
        t.SendResponse(req.GenResponse(200, "OK", nil, self.ua.GetLocalUA().AsSipServer()), false, nil)
        return nil
    }
    //print "wrong request %s in the state Connected" % req.GetMethod()
    return nil
}

func (self *UaStateConnected) RecvEvent(event sippy_types.CCEvent) (sippy_types.UaState, error) {
    var err error
    var req sippy_types.SipRequest

    eh := event.GetExtraHeaders()
    ok := false
    var redirect *sippy_header.SipAddress = nil

    switch ev := event.(type) {
    case *CCEventDisconnect:
        redirect = ev.GetRedirectURL()
        ok = true
    case *CCEventRedirect:
        redirect = ev.GetRedirectURL()
        ok = true
    case *CCEventFail:
        ok = true
    }
    if ok {
        //println("event", event.String(), "received in the Connected state sending BYE")
        if redirect != nil && self.ua.ShouldUseRefer() {
            var lUri *sippy_header.SipAddress

            req, err = self.ua.GenRequest("REFER", nil, "", "", nil, eh...)
            if err != nil {
                return nil, err
            }
            self.ua.IncLCSeq()
            also := sippy_header.NewSipReferTo(redirect)
            req.AppendHeader(also)
            lUri, err = self.ua.GetLUri().GetBody(self.config)
            if err != nil {
                return nil, err
            }
            rby := sippy_header.NewSipReferredBy(sippy_header.NewSipAddress("", lUri.GetUrl()))
            req.AppendHeader(rby)
            self.ua.SipTM().BeginNewClientTransaction(req, newRedirectController(self.ua), self.ua.GetSessionLock(), self.ua.GetSourceAddress(), nil, self.ua.BeforeRequestSent)
        } else {
            req, err = self.ua.GenRequest("BYE", nil, "", "", nil, eh...)
            if err != nil {
                return nil, err
            }
            self.ua.IncLCSeq()
            if redirect != nil {
                also := sippy_header.NewSipAlso(redirect)
                req.AppendHeader(also)
            }
            self.ua.SipTM().BeginNewClientTransaction(req, nil, self.ua.GetSessionLock(), self.ua.GetSourceAddress(), nil, self.ua.BeforeRequestSent)
        }
        self.ua.CancelCreditTimer()
        self.ua.SetDisconnectTs(event.GetRtime())
        return NewUaStateDisconnected(self.ua, event.GetRtime(), event.GetOrigin(), 0, nil, self.config), nil
    }
    if _event, ok := event.(*CCEventUpdate); ok {
        var tr sippy_types.ClientTransaction

        body := _event.GetBody()
        if self.ua.GetLSDP() != nil && body != nil && self.ua.GetLSDP().String() == body.String() {
            if self.ua.GetRSDP() != nil {
                self.ua.Enqueue(NewCCEventConnect(200, "OK", self.ua.GetRSDP().GetCopy(), event.GetRtime(), event.GetOrigin()))
            } else {
                self.ua.Enqueue(NewCCEventConnect(200, "OK", nil, event.GetRtime(), event.GetOrigin()))
            }
            return nil, nil
        }
        if body != nil && self.ua.HasOnLocalSdpChange() && body.NeedsUpdate() {
            err := self.ua.OnLocalSdpChange(body, event, func(sippy_types.MsgBody) { self.ua.RecvEvent(event) })
            if err != nil {
                ev := NewCCEventFail(400, "Malformed SDP Body", event.GetRtime(), "")
                ev.SetWarning(err.Error())
                self.ua.Enqueue(ev)
            }
            return nil, nil
        }
        eh2 := eh
        if _event.GetMaxForwards() != nil {
            var max_forwards *sippy_header.SipNumericHF

            max_forwards, err = _event.GetMaxForwards().GetBody()
            if err != nil {
                return nil, err
            }
            if max_forwards.Number <= 0 {
                self.ua.Enqueue(NewCCEventFail(483, "Too Many Hops", event.GetRtime(), ""))
                return nil, nil
            }
            eh2 = append(eh2, sippy_header.NewSipMaxForwards(max_forwards.Number - 1))
        }
        req, err = self.ua.GenRequest("INVITE", body, "", "", nil, eh2...)
        if err != nil {
            return nil, err
        }
        self.ua.IncLCSeq()
        self.ua.SetLSDP(body)
        tr, err = self.ua.PrepTr(req)
        if err != nil {
            return nil, err
        }
        self.ua.SetClientTransaction(tr)
        self.ua.SipTM().BeginClientTransaction(req, tr)
        return NewUacStateUpdating(self.ua, self.config), nil
    }
    if _event, ok := event.(*CCEventInfo); ok {
        body := _event.GetBody()
        req, err = self.ua.GenRequest("INFO", nil, "", "", nil, eh...)
        if err != nil {
            return nil, err
        }
        req.SetBody(body)
        self.ua.IncLCSeq()
        self.ua.SipTM().BeginNewClientTransaction(req, nil, self.ua.GetSessionLock(), self.ua.GetSourceAddress(), nil, self.ua.BeforeRequestSent)
        return nil, nil
    }
    if _event, ok := event.(*CCEventConnect); ok && self.ua.GetPendingTr() != nil {
        self.ua.CancelExpireTimer()
        body := _event.GetBody()
        if body != nil && self.ua.HasOnLocalSdpChange() && body.NeedsUpdate() {
            self.ua.OnLocalSdpChange(body, event, func(sippy_types.MsgBody) { self.ua.RecvEvent(event) })
            return nil, nil
        }
        self.ua.StartCreditTimer(event.GetRtime())
        self.ua.SetConnectTs(event.GetRtime())
        self.ua.SetLSDP(body)
        self.ua.GetPendingTr().GetACK().SetBody(body)
        self.ua.GetPendingTr().SendACK()
        self.ua.SetPendingTr(nil)
        self.ua.ConnCb(event.GetRtime(), self.ua.GetOrigin())
    }
    //print "wrong event %s in the Connected state" % event
    return nil, nil
}

func (self *UaStateConnected) OnStateChange() {
    if self.ka_tr != nil {
        self.ka_tr.Cancel()
        self.ka_tr = nil
    }
    if self.ua.GetPendingTr() != nil {
        self.ua.GetPendingTr().SendACK()
        self.ua.SetPendingTr(nil)
    }
    self.ua.CancelExpireTimer()
}

func (self *UaStateConnected) RecvACK(req sippy_types.SipRequest) {
    rtime := req.GetRtime()
    body := req.GetBody()
    event := NewCCEventConnect(0, "ACK", body, rtime, self.ua.GetOrigin())
    self.ua.CancelExpireTimer()
    self.ua.StartCreditTimer(rtime)
    self.ua.SetConnectTs(rtime)
    self.ua.ConnCb(rtime, self.ua.GetOrigin())
    if body != nil {
        if self.ua.HasOnRemoteSdpChange() {
            self.ua.OnRemoteSdpChange(body, func (x sippy_types.MsgBody) { self.ua.DelayedRemoteSdpUpdate(event, x) })
            return
        } else {
            self.ua.SetRSDP(body.GetCopy())
        }
    } else {
        self.ua.SetRSDP(nil)
    }
    self.ua.Enqueue(event)
    return
}
<|endoftext|>