<commit_before>package models

import (
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/brnstz/bus/common"
	"github.com/jmoiron/sqlx"
)

const maxStops = 3

type Trip struct {
	Id          string
	Headsign    string
	DirectionId int
}

type Service struct {
	Id      string
	RouteId string
}

type ScheduledStopTime struct {
	RouteId      string `db:"route_id"`
	StopId       string `db:"stop_id"`
	ServiceId    string `db:"service_id"`
	DepartureSec int    `db:"departure_sec"`
}

func df(t time.Time) string {
	return t.Format("2006-01-02")
}

type timeSlice []time.Time

func (p timeSlice) Len() int {
	return len(p)
}

func (p timeSlice) Less(i, j int) bool {
	return p[i].Before(p[j])
}

func (p timeSlice) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func NewScheduledStopTime(routeId, stopId, serviceId, timeStr string) (sst ScheduledStopTime, err error) {
	dsec := common.TimeStrToSecs(timeStr)

	sst = ScheduledStopTime{
		RouteId:      routeId,
		StopId:       stopId,
		ServiceId:    serviceId,
		DepartureSec: dsec,
	}

	return
}
func (s ScheduledStopTime) String() string {
	return fmt.Sprintf("{%v %v %v @ %v (%v)}",
		s.RouteId, s.ServiceId, s.StopId,
		common.SecsToTimeStr(s.DepartureSec), s.DepartureSec,
	)
}

type Stop struct {
	Id          string `json:"stop_id" db:"stop_id"`
	Name        string `json:"stop_name" db:"stop_name"`
	RouteId     string `json:"route_id" db:"route_id"`
	StationType string `json:"station_type" db:"stype"`

	DirectionId int    `json:"direction_id" db:"direction_id"`
	Headsign    string `json:"headsign" db:"headsign"`

	Lat float64 `json:"lat" db:"lat"`
	Lon float64 `json:"lon" db:"lon"`

	Dist float64 `json:"dist" db:"dist"`

	Scheduled []*Departure `json:"scheduled"`
	Live      []*Departure `json:"live"`
}

func (s *Stop) AppendLive(now time.Time) {
	log.Println("AppendLive(): ", s)
	if s.StationType == "bus" {
		calls, err := GetCallsByRouteStop(
			s.RouteId, strconv.Itoa(s.DirectionId),
			s.Id,
		)
		if err != nil {
			log.Println("can't append live schedules")
			return
		}

		sort.Sort(calls)
		for i := 0; i < len(calls) && i < maxStops; i++ {
			s.Live = append(s.Live, &Departure{
				Desc: calls[i].Extensions.Distances.PresentableDistance,
			})
		}
	} else if s.StationType == "subway" {

		times, err := GetLiveSubways(
			s.RouteId, strconv.Itoa(s.DirectionId),
			s.Id,
		)
		if err != nil {
			log.Println("can't append live subway sched", err)
			return
		}

		sort.Sort(times)
		for i := 0; i < len(times) && i < maxStops; i++ {
			s.Live = append(s.Live, &Departure{
				Time: times[i],
			})
		}

	}
}

func (s Stop) String() string {
	return fmt.Sprintf("{%v %v %v %v %v @ (%v,%v)}",
		s.Id, s.Name, s.RouteId, s.Headsign, s.DirectionId, s.Lat, s.Lon,
	)
}

func (s Stop) Key() string {
	return fmt.Sprintf("%v%v", s.Id, s.RouteId)
}

// FIXME: is this correct?
func getServiceIdByDay(db sqlx.Ext, routeId, day string, now *time.Time) (serviceId string, err error) {
	row := db.QueryRowx(`
		SELECT service_id, route_id, max(start_date)
		FROM   service_route_day
		WHERE  day         = $1 AND
		       end_date    > $2 AND
			   route_id    = $3
		GROUP BY service_id, route_id
		LIMIT 1
	`, day, now, routeId,
	)

	var dummy1 string
	var dummy2 time.Time

	err = row.Scan(&serviceId, &dummy1, &dummy2)
	if err != nil {
		log.Println("can't scan service id", err, day, now, routeId)
		return
	}

	return
}

func oneStop(stop *Stop, now time.Time, db sqlx.Ext) (err error) {

	ydaysecs := []int64{}
	todaysecs := []int64{}

	allTimes := timeSlice{}

	yesterday := now.Add(-time.Hour * 12)
	yesterdayName := strings.ToLower(yesterday.Format("Monday"))
	todayName := strings.ToLower(now.Format("Monday"))

	func() {
		if yesterdayName != todayName {
			var yesterdayId string
			// Looks for trips starting yesterday that arrive here
			// after midnight
			yesterdayId, err = getServiceIdByDay(
				db, stop.RouteId, yesterdayName, &now,
			)
			if err == sql.ErrNoRows {
				err = nil
				log.Println("no rows, ok, moving on")
				return
			}
			if err != nil {
				log.Println("can't get yesterday id", err)
				return
			}

			qYesterday := `
			SELECT scheduled_stop_time.departure_sec
			FROM   scheduled_stop_time
			WHERE  route_id   = $1 AND
			       stop_id    = $2 AND
				   service_id = $3 AND
				   departure_sec >= 86400 AND
				   departure_sec > $4
			ORDER BY departure_sec LIMIT $5
		`
			nowSecs :=
				now.Hour()*3600 + now.Minute()*60 + now.Second() + 86400

			err = sqlx.Select(db, &ydaysecs,
				qYesterday, stop.RouteId, stop.Id,
				yesterdayId, nowSecs, maxStops)

			if err != nil {
				log.Println("can't scan yesterday values", err)
				return
			}

			yesterday = yesterday.Add(
				-time.Hour * time.Duration(yesterday.Hour()))
			yesterday = yesterday.Add(
				-time.Minute * time.Duration(yesterday.Minute()))
			yesterday = yesterday.Add(
				-time.Second * time.Duration(yesterday.Second()))
			yesterday = yesterday.Add(
				-time.Nanosecond * time.Duration(yesterday.Nanosecond()))

			for _, ydaysec := range ydaysecs {
				thisTime := yesterday.Add(time.Second * time.Duration(ydaysec))
				allTimes = append(allTimes, thisTime)
			}
		}
	}()

	func() {
		var todayId string
		todayId, err = getServiceIdByDay(db, stop.RouteId, todayName, &now)
		if err == sql.ErrNoRows {
			err = nil
			log.Println("no rows there", err)
			return
		}
		if err != nil {
			log.Println("can't get today id", err)
			return
		}

		qToday := `
		SELECT scheduled_stop_time.departure_sec
		FROM   scheduled_stop_time
		WHERE  route_id   = $1 AND
			   stop_id    = $2 AND
			   service_id = $3 AND
			   departure_sec > $4
		ORDER BY departure_sec LIMIT $5
	`

		nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second()
		err = sqlx.Select(db, &todaysecs, qToday, stop.RouteId, stop.Id,
			todayId, nowSecs, maxStops)

		today := now
		today = today.Add(
			-time.Hour * time.Duration(today.Hour()))
		today = today.Add(
			-time.Minute * time.Duration(today.Minute()))
		today = today.Add(
			-time.Second * time.Duration(today.Second()))
		today = today.Add(
			-time.Nanosecond * time.Duration(today.Nanosecond()))

		for _, todaysec := range todaysecs {
			thisTime := today.Add(time.Second * time.Duration(todaysec))
			allTimes = append(allTimes, thisTime)
		}

		if err != nil {
			log.Println("can't scan today values", err)
			return
		}
	}()

	sort.Sort(allTimes)

	for i, thisTime := range allTimes {
		if i > maxStops {
			break
		}
		stop.Scheduled = append(
			stop.Scheduled, &Departure{Time: thisTime},
		)
	}
	stop.AppendLive(now)

	return
}

func GetStopsByLoc(db sqlx.Ext, lat, lon, meters float64, filter string) (stops []*Stop, err error) {

	stops = []*Stop{}
	params := []interface{}{lat, lon, lat, lon, meters}

	// FIXME: this is not returning the closet stop
	q := `
		SELECT * FROM (
			SELECT
				DISTINCT ON (route_id, direction_id)
				stop_id,
				stop_name,
				direction_id,
				headsign,
				route_id,
				stype,
				latitude(location) AS lat,
				longitude(location) AS lon,
				earth_distance(location, ll_to_earth($1, $2)) AS dist
			FROM stop
			WHERE earth_box(ll_to_earth($3, $4), $5) @> location
	`

	if len(filter) > 0 {
		q = q + ` AND stype = $6 `
		params = append(params, filter)
	}

	q = q + `
			ORDER BY route_id, direction_id, dist
		) unique_routes

		ORDER BY dist ASC
	`

	err = sqlx.Select(db, &stops, q, params...)
	if err != nil {
		log.Println("can't get stop", err)
		return
	}

	now := time.Now()
	for _, stop := range stops {
		oneStop(stop, now, db)
	}

	return stops, err
}

type ServiceRouteDay struct {
	ServiceId string
	RouteId   string
	Day       string

	StartDate time.Time
	EndDate   time.Time
}

func (s ServiceRouteDay) String() string {
	return fmt.Sprintf("{%v %v %v %v %v}",
		s.ServiceId, s.RouteId, s.Day, df(s.StartDate), df(s.EndDate),
	)
}

type ServiceRouteException struct {
	ServiceId     string
	RouteId       string
	ExceptionDate time.Time
}

type Departure struct {
	Time time.Time `json:"time" db:"time"`
	Desc string    `json:"desc"`

	// FIXME: stops away? miles away?
}
<commit_msg>comment, need to test<commit_after>package models

import (
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/brnstz/bus/common"
	"github.com/jmoiron/sqlx"
)

const maxStops = 3

type Trip struct {
	Id          string
	Headsign    string
	DirectionId int
}

type Service struct {
	Id      string
	RouteId string
}

type ScheduledStopTime struct {
	RouteId      string `db:"route_id"`
	StopId       string `db:"stop_id"`
	ServiceId    string `db:"service_id"`
	DepartureSec int    `db:"departure_sec"`
}

func df(t time.Time) string {
	return t.Format("2006-01-02")
}

type timeSlice []time.Time

func (p timeSlice) Len() int {
	return len(p)
}

func (p timeSlice) Less(i, j int) bool {
	return p[i].Before(p[j])
}

func (p timeSlice) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func NewScheduledStopTime(routeId, stopId, serviceId, timeStr string) (sst ScheduledStopTime, err error) {
	dsec := common.TimeStrToSecs(timeStr)

	sst = ScheduledStopTime{
		RouteId:      routeId,
		StopId:       stopId,
		ServiceId:    serviceId,
		DepartureSec: dsec,
	}

	return
}
func (s ScheduledStopTime) String() string {
	return fmt.Sprintf("{%v %v %v @ %v (%v)}",
		s.RouteId, s.ServiceId, s.StopId,
		common.SecsToTimeStr(s.DepartureSec), s.DepartureSec,
	)
}

type Stop struct {
	Id          string `json:"stop_id" db:"stop_id"`
	Name        string `json:"stop_name" db:"stop_name"`
	RouteId     string `json:"route_id" db:"route_id"`
	StationType string `json:"station_type" db:"stype"`

	DirectionId int    `json:"direction_id" db:"direction_id"`
	Headsign    string `json:"headsign" db:"headsign"`

	Lat float64 `json:"lat" db:"lat"`
	Lon float64 `json:"lon" db:"lon"`

	Dist float64 `json:"dist" db:"dist"`

	Scheduled []*Departure `json:"scheduled"`
	Live      []*Departure `json:"live"`
}

func (s *Stop) AppendLive(now time.Time) {
	log.Println("AppendLive(): ", s)
	if s.StationType == "bus" {
		calls, err := GetCallsByRouteStop(
			s.RouteId, strconv.Itoa(s.DirectionId),
			s.Id,
		)
		if err != nil {
			log.Println("can't append live schedules")
			return
		}

		sort.Sort(calls)
		for i := 0; i < len(calls) && i < maxStops; i++ {
			s.Live = append(s.Live, &Departure{
				Desc: calls[i].Extensions.Distances.PresentableDistance,
			})
		}
	} else if s.StationType == "subway" {

		times, err := GetLiveSubways(
			s.RouteId, strconv.Itoa(s.DirectionId),
			s.Id,
		)
		if err != nil {
			log.Println("can't append live subway sched", err)
			return
		}

		sort.Sort(times)
		for i := 0; i < len(times) && i < maxStops; i++ {
			s.Live = append(s.Live, &Departure{
				Time: times[i],
			})
		}

	}
}

func (s Stop) String() string {
	return fmt.Sprintf("{%v %v %v %v %v @ (%v,%v)}",
		s.Id, s.Name, s.RouteId, s.Headsign, s.DirectionId, s.Lat, s.Lon,
	)
}

func (s Stop) Key() string {
	return fmt.Sprintf("%v%v", s.Id, s.RouteId)
}

// FIXME: is this correct?
func getServiceIdByDay(db sqlx.Ext, routeId, day string, now *time.Time) (serviceId string, err error) {
	row := db.QueryRowx(`
		SELECT service_id, route_id, max(start_date)
		FROM   service_route_day
		WHERE  day         = $1 AND
		       end_date    > $2 AND
			   route_id    = $3
		GROUP BY service_id, route_id
		LIMIT 1
	`, day, now, routeId,
	)

	var dummy1 string
	var dummy2 time.Time

	err = row.Scan(&serviceId, &dummy1, &dummy2)
	if err != nil {
		log.Println("can't scan service id", err, day, now, routeId)
		return
	}

	return
}

func oneStop(stop *Stop, now time.Time, db sqlx.Ext) (err error) {

	ydaysecs := []int64{}
	todaysecs := []int64{}

	allTimes := timeSlice{}

	yesterday := now.Add(-time.Hour * 12)
	yesterdayName := strings.ToLower(yesterday.Format("Monday"))
	todayName := strings.ToLower(now.Format("Monday"))

	func() {
		if yesterdayName != todayName {
			var yesterdayId string
			// Looks for trips starting yesterday that arrive here
			// after midnight
			yesterdayId, err = getServiceIdByDay(
				db, stop.RouteId, yesterdayName, &now,
			)
			if err == sql.ErrNoRows {
				err = nil
				log.Println("no rows, ok, moving on")
				return
			}
			if err != nil {
				log.Println("can't get yesterday id", err)
				return
			}

			qYesterday := `
			SELECT scheduled_stop_time.departure_sec
			FROM   scheduled_stop_time
			WHERE  route_id   = $1 AND
			       stop_id    = $2 AND
				   service_id = $3 AND
				   departure_sec >= 86400 AND
				   departure_sec > $4
			ORDER BY departure_sec LIMIT $5
		`
			nowSecs :=
				now.Hour()*3600 + now.Minute()*60 + now.Second() + 86400

			err = sqlx.Select(db, &ydaysecs,
				qYesterday, stop.RouteId, stop.Id,
				yesterdayId, nowSecs, maxStops)

			if err != nil {
				log.Println("can't scan yesterday values", err)
				return
			}

			yesterday = yesterday.Add(
				-time.Hour * time.Duration(yesterday.Hour()))
			yesterday = yesterday.Add(
				-time.Minute * time.Duration(yesterday.Minute()))
			yesterday = yesterday.Add(
				-time.Second * time.Duration(yesterday.Second()))
			yesterday = yesterday.Add(
				-time.Nanosecond * time.Duration(yesterday.Nanosecond()))

			for _, ydaysec := range ydaysecs {
				thisTime := yesterday.Add(time.Second * time.Duration(ydaysec))
				allTimes = append(allTimes, thisTime)
			}
		}
	}()

	func() {
		var todayId string
		todayId, err = getServiceIdByDay(db, stop.RouteId, todayName, &now)
		if err == sql.ErrNoRows {
			err = nil
			log.Println("no rows there", err)
			return
		}
		if err != nil {
			log.Println("can't get today id", err)
			return
		}

		qToday := `
		SELECT scheduled_stop_time.departure_sec
		FROM   scheduled_stop_time
		WHERE  route_id   = $1 AND
			   stop_id    = $2 AND
			   service_id = $3 AND
			   departure_sec > $4
		ORDER BY departure_sec LIMIT $5
	`

		nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second()
		err = sqlx.Select(db, &todaysecs, qToday, stop.RouteId, stop.Id,
			todayId, nowSecs, maxStops)

		today := now
		today = today.Add(
			-time.Hour * time.Duration(today.Hour()))
		today = today.Add(
			-time.Minute * time.Duration(today.Minute()))
		today = today.Add(
			-time.Second * time.Duration(today.Second()))
		today = today.Add(
			-time.Nanosecond * time.Duration(today.Nanosecond()))

		for _, todaysec := range todaysecs {
			thisTime := today.Add(time.Second * time.Duration(todaysec))
			allTimes = append(allTimes, thisTime)
		}

		if err != nil {
			log.Println("can't scan today values", err)
			return
		}
	}()

	sort.Sort(allTimes)

	for i, thisTime := range allTimes {
		if i > maxStops {
			break
		}
		stop.Scheduled = append(
			stop.Scheduled, &Departure{Time: thisTime},
		)
	}
	stop.AppendLive(now)

	return
}

func GetStopsByLoc(db sqlx.Ext, lat, lon, meters float64, filter string) (stops []*Stop, err error) {

	stops = []*Stop{}
	params := []interface{}{lat, lon, lat, lon, meters}

	// FIXME: this is not returning the closet stop
	// DONTFIXME: I think the above is already fixed
	q := `
		SELECT * FROM (
			SELECT
				DISTINCT ON (route_id, direction_id)
				stop_id,
				stop_name,
				direction_id,
				headsign,
				route_id,
				stype,
				latitude(location) AS lat,
				longitude(location) AS lon,
				earth_distance(location, ll_to_earth($1, $2)) AS dist
			FROM stop
			WHERE earth_box(ll_to_earth($3, $4), $5) @> location
	`

	if len(filter) > 0 {
		q = q + ` AND stype = $6 `
		params = append(params, filter)
	}

	q = q + `
			ORDER BY route_id, direction_id, dist
		) unique_routes

		ORDER BY dist ASC
	`

	err = sqlx.Select(db, &stops, q, params...)
	if err != nil {
		log.Println("can't get stop", err)
		return
	}

	now := time.Now()
	for _, stop := range stops {
		oneStop(stop, now, db)
	}

	return stops, err
}

type ServiceRouteDay struct {
	ServiceId string
	RouteId   string
	Day       string

	StartDate time.Time
	EndDate   time.Time
}

func (s ServiceRouteDay) String() string {
	return fmt.Sprintf("{%v %v %v %v %v}",
		s.ServiceId, s.RouteId, s.Day, df(s.StartDate), df(s.EndDate),
	)
}

type ServiceRouteException struct {
	ServiceId     string
	RouteId       string
	ExceptionDate time.Time
}

type Departure struct {
	Time time.Time `json:"time" db:"time"`
	Desc string    `json:"desc"`

	// FIXME: stops away? miles away?
}
<|endoftext|>


<commit_before>package logical

import (
	"fmt"
	"time"
)

// Auth is the resulting authentication information that is part of
// Response for credential backends.
type Auth struct {
	LeaseOptions

	// InternalData is JSON-encodable data that is stored with the auth struct.
	// This will be sent back during a Renew/Revoke for storing internal data
	// used for those operations.
	InternalData map[string]interface{} `json:"internal_data" mapstructure:"internal_data" structs:"internal_data"`

	// DisplayName is a non-security sensitive identifier that is
	// applicable to this Auth. It is used for logging and prefixing
	// of dynamic secrets. For example, DisplayName may be "armon" for
	// the github credential backend. If the client token is used to
	// generate a SQL credential, the user may be "github-armon-uuid".
	// This is to help identify the source without using audit tables.
	DisplayName string `json:"display_name" mapstructure:"display_name" structs:"display_name"`

	// Policies is the list of policies that the authenticated user
	// is associated with.
	Policies []string `json:"policies" mapstructure:"policies" structs:"policies"`

	// Metadata is used to attach arbitrary string-type metadata to
	// an authenticated user. This metadata will be outputted into the
	// audit log.
	Metadata map[string]string `json:"metadata" mapstructure:"metadata" structs:"metadata"`

	// ClientToken is the token that is generated for the authentication.
	// This will be filled in by Vault core when an auth structure is
	// returned. Setting this manually will have no effect.
	ClientToken string `json:"client_token" mapstructure:"client_token" structs:"client_token"`

	// Accessor is the identifier for the ClientToken. This can be used
	// to perform management functionalities (especially revocation) when
	// ClientToken in the audit logs are obfuscated. Accessor can be used
	// to revoke a ClientToken and to lookup the capabilities of the ClientToken,
	// both without actually knowing the ClientToken.
	Accessor string `json:"accessor" mapstructure:"accessor" structs:"accessor"`

	// Period indicates that the token generated using this Auth object
	// should never expire. The token should be renewed within the duration
	// specified by this period.
	Period time.Duration `json:"period" mapstructure:"period" structs:"period"`

	// Number of allowed uses of the issued token
	NumUses int `json:"num_uses" mapstructure:"num_uses" structs:"num_uses"`
}

func (a *Auth) GoString() string {
	return fmt.Sprintf("*%#v", *a)
}
<commit_msg>Added persona to logical auth<commit_after>package logical

import (
	"fmt"
	"time"
)

// Auth is the resulting authentication information that is part of
// Response for credential backends.
type Auth struct {
	LeaseOptions

	// InternalData is JSON-encodable data that is stored with the auth struct.
	// This will be sent back during a Renew/Revoke for storing internal data
	// used for those operations.
	InternalData map[string]interface{} `json:"internal_data" mapstructure:"internal_data" structs:"internal_data"`

	// DisplayName is a non-security sensitive identifier that is
	// applicable to this Auth. It is used for logging and prefixing
	// of dynamic secrets. For example, DisplayName may be "armon" for
	// the github credential backend. If the client token is used to
	// generate a SQL credential, the user may be "github-armon-uuid".
	// This is to help identify the source without using audit tables.
	DisplayName string `json:"display_name" mapstructure:"display_name" structs:"display_name"`

	// Policies is the list of policies that the authenticated user
	// is associated with.
	Policies []string `json:"policies" mapstructure:"policies" structs:"policies"`

	// Metadata is used to attach arbitrary string-type metadata to
	// an authenticated user. This metadata will be outputted into the
	// audit log.
	Metadata map[string]string `json:"metadata" mapstructure:"metadata" structs:"metadata"`

	// ClientToken is the token that is generated for the authentication.
	// This will be filled in by Vault core when an auth structure is
	// returned. Setting this manually will have no effect.
	ClientToken string `json:"client_token" mapstructure:"client_token" structs:"client_token"`

	// Accessor is the identifier for the ClientToken. This can be used
	// to perform management functionalities (especially revocation) when
	// ClientToken in the audit logs are obfuscated. Accessor can be used
	// to revoke a ClientToken and to lookup the capabilities of the ClientToken,
	// both without actually knowing the ClientToken.
	Accessor string `json:"accessor" mapstructure:"accessor" structs:"accessor"`

	// Period indicates that the token generated using this Auth object
	// should never expire. The token should be renewed within the duration
	// specified by this period.
	Period time.Duration `json:"period" mapstructure:"period" structs:"period"`

	// Number of allowed uses of the issued token
	NumUses int `json:"num_uses" mapstructure:"num_uses" structs:"num_uses"`

	// Persona is the information about the authenticated client returned by
	// the auth backend
	Persona *Persona `json:"persona" structs:"persona" mapstructure:"persona"`
}

func (a *Auth) GoString() string {
	return fmt.Sprintf("*%#v", *a)
}
<|endoftext|>


<commit_before>// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build integration

package tests

import (
	"net/http"
	"reflect"
	"testing"

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

func TestRepositories_CRUD(t *testing.T) {
	if !checkAuth("TestRepositories_CRUD") {
		return
	}

	// get authenticated user
	me, _, err := client.Users.Get("")
	if err != nil {
		t.Fatalf("Users.Get('') returned error: %v", err)
	}

	repo, err := createRandomTestRepository(*me.Login, false)
	if err != nil {
		t.Fatalf("createRandomTestRepository returned error: %v", err)
	}

	// update the repository description
	repo.Description = github.String("description")
	repo.DefaultBranch = nil // FIXME: this shouldn't be necessary
	_, _, err = client.Repositories.Edit(*repo.Owner.Login, *repo.Name, repo)
	if err != nil {
		t.Fatalf("Repositories.Edit() returned error: %v", err)
	}

	// delete the repository
	_, err = client.Repositories.Delete(*repo.Owner.Login, *repo.Name)
	if err != nil {
		t.Fatalf("Repositories.Delete() returned error: %v", err)
	}

	// verify that the repository was deleted
	_, resp, err := client.Repositories.Get(*repo.Owner.Login, *repo.Name)
	if err == nil {
		t.Fatalf("Test repository still exists after deleting it.")
	}
	if err != nil && resp.StatusCode != http.StatusNotFound {
		t.Fatalf("Repositories.Get() returned error: %v", err)
	}
}

func TestRepositories_BranchesTags(t *testing.T) {
	// branches
	branches, _, err := client.Repositories.ListBranches("git", "git", nil)
	if err != nil {
		t.Fatalf("Repositories.ListBranches() returned error: %v", err)
	}

	if len(branches) == 0 {
		t.Fatalf("Repositories.ListBranches('git', 'git') returned no branches")
	}

	_, _, err = client.Repositories.GetBranch("git", "git", *branches[0].Name)
	if err != nil {
		t.Fatalf("Repositories.GetBranch() returned error: %v", err)
	}

	// tags
	tags, _, err := client.Repositories.ListTags("git", "git", nil)
	if err != nil {
		t.Fatalf("Repositories.ListTags() returned error: %v", err)
	}

	if len(tags) == 0 {
		t.Fatalf("Repositories.ListTags('git', 'git') returned no tags")
	}
}

func TestRepositories_ServiceHooks(t *testing.T) {
	hooks, _, err := client.Repositories.ListServiceHooks()
	if err != nil {
		t.Fatalf("Repositories.ListServiceHooks() returned error: %v", err)
	}

	if len(hooks) == 0 {
		t.Fatalf("Repositories.ListServiceHooks() returned no hooks")
	}
}

func TestRepositories_EditBranches(t *testing.T) {
	if !checkAuth("TestRepositories_EditBranches") {
		return
	}

	// get authenticated user
	me, _, err := client.Users.Get("")
	if err != nil {
		t.Fatalf("Users.Get('') returned error: %v", err)
	}

	repo, err := createRandomTestRepository(*me.Login, true)
	if err != nil {
		t.Fatalf("createRandomTestRepository returned error: %v", err)
	}

	branch, _, err := client.Repositories.GetBranch(*repo.Owner.Login, *repo.Name, "master")
	if err != nil {
		t.Fatalf("Repositories.GetBranch() returned error: %v", err)
	}

	if *branch.Protection.Enabled {
		t.Fatalf("Branch %v of repo %v is already protected", "master", *repo.Name)
	}

	branch.Protection.Enabled = github.Bool(true)
	branch.Protection.RequiredStatusChecks = &github.RequiredStatusChecks{
		EnforcementLevel: github.String("everyone"),
		Contexts:         &[]string{"continous-integration"},
	}
	branch, _, err = client.Repositories.EditBranch(*repo.Owner.Login, *repo.Name, "master", branch)
	if err != nil {
		t.Fatalf("Repositories.EditBranch() returned error: %v", err)
	}

	if !*branch.Protection.Enabled {
		t.Fatalf("Branch %v of repo %v should be protected, but is not!", "master", *repo.Name)
	}
	if *branch.Protection.RequiredStatusChecks.EnforcementLevel != "everyone" {
		t.Fatalf("RequiredStatusChecks should be enabled for everyone, set for: %v", *branch.Protection.RequiredStatusChecks.EnforcementLevel)
	}

	wantedContexts := []string{"continous-integration"}
	if !reflect.DeepEqual(*branch.Protection.RequiredStatusChecks.Contexts, wantedContexts) {
		t.Fatalf("RequiredStatusChecks.Contexts should be: %v but is: %v", wantedContexts, *branch.Protection.RequiredStatusChecks.Contexts)
	}

	_, err = client.Repositories.Delete(*repo.Owner.Login, *repo.Name)
	if err != nil {
		t.Fatalf("Repositories.Delete() returned error: %v", err)
	}
}

func TestRepositories_List(t *testing.T) {
	if !checkAuth("TestRepositories_List") {
		return
	}

	_, _, err := client.Repositories.List("", nil)
	if err != nil {
		t.Fatalf("Repositories.List('') returned error: %v", err)
	}

	_, _, err = client.Repositories.List("google", nil)
	if err != nil {
		t.Fatalf("Repositories.List('google') returned error: %v", err)
	}

	opt := github.RepositoryListOptions{Sort: "created"}
	repos, _, err := client.Repositories.List("google", &opt)
	if err != nil {
		t.Fatalf("Repositories.List('google') with Sort opt returned error: %v", err)
	}
	for i, repo := range repos {
		if i > 0 && (*repos[i-1].CreatedAt).Time.Before((*repo.CreatedAt).Time) {
			t.Fatalf("Repositories.List('google') with default descending Sort returned incorrect order")
		}
	}
}
<commit_msg>tests/integration: Update for new branch protection API.<commit_after>// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build integration

package tests

import (
	"net/http"
	"reflect"
	"testing"

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

func TestRepositories_CRUD(t *testing.T) {
	if !checkAuth("TestRepositories_CRUD") {
		return
	}

	// get authenticated user
	me, _, err := client.Users.Get("")
	if err != nil {
		t.Fatalf("Users.Get('') returned error: %v", err)
	}

	repo, err := createRandomTestRepository(*me.Login, false)
	if err != nil {
		t.Fatalf("createRandomTestRepository returned error: %v", err)
	}

	// update the repository description
	repo.Description = github.String("description")
	repo.DefaultBranch = nil // FIXME: this shouldn't be necessary
	_, _, err = client.Repositories.Edit(*repo.Owner.Login, *repo.Name, repo)
	if err != nil {
		t.Fatalf("Repositories.Edit() returned error: %v", err)
	}

	// delete the repository
	_, err = client.Repositories.Delete(*repo.Owner.Login, *repo.Name)
	if err != nil {
		t.Fatalf("Repositories.Delete() returned error: %v", err)
	}

	// verify that the repository was deleted
	_, resp, err := client.Repositories.Get(*repo.Owner.Login, *repo.Name)
	if err == nil {
		t.Fatalf("Test repository still exists after deleting it.")
	}
	if err != nil && resp.StatusCode != http.StatusNotFound {
		t.Fatalf("Repositories.Get() returned error: %v", err)
	}
}

func TestRepositories_BranchesTags(t *testing.T) {
	// branches
	branches, _, err := client.Repositories.ListBranches("git", "git", nil)
	if err != nil {
		t.Fatalf("Repositories.ListBranches() returned error: %v", err)
	}

	if len(branches) == 0 {
		t.Fatalf("Repositories.ListBranches('git', 'git') returned no branches")
	}

	_, _, err = client.Repositories.GetBranch("git", "git", *branches[0].Name)
	if err != nil {
		t.Fatalf("Repositories.GetBranch() returned error: %v", err)
	}

	// tags
	tags, _, err := client.Repositories.ListTags("git", "git", nil)
	if err != nil {
		t.Fatalf("Repositories.ListTags() returned error: %v", err)
	}

	if len(tags) == 0 {
		t.Fatalf("Repositories.ListTags('git', 'git') returned no tags")
	}
}

func TestRepositories_ServiceHooks(t *testing.T) {
	hooks, _, err := client.Repositories.ListServiceHooks()
	if err != nil {
		t.Fatalf("Repositories.ListServiceHooks() returned error: %v", err)
	}

	if len(hooks) == 0 {
		t.Fatalf("Repositories.ListServiceHooks() returned no hooks")
	}
}

func TestRepositories_EditBranches(t *testing.T) {
	if !checkAuth("TestRepositories_EditBranches") {
		return
	}

	// get authenticated user
	me, _, err := client.Users.Get("")
	if err != nil {
		t.Fatalf("Users.Get('') returned error: %v", err)
	}

	repo, err := createRandomTestRepository(*me.Login, true)
	if err != nil {
		t.Fatalf("createRandomTestRepository returned error: %v", err)
	}

	branch, _, err := client.Repositories.GetBranch(*repo.Owner.Login, *repo.Name, "master")
	if err != nil {
		t.Fatalf("Repositories.GetBranch() returned error: %v", err)
	}

	if *branch.Protected {
		t.Fatalf("Branch %v of repo %v is already protected", "master", *repo.Name)
	}

	protectionRequest := &github.ProtectionRequest{
		RequiredStatusChecks: &github.RequiredStatusChecks{
			IncludeAdmins: true,
			Strict:        true,
			Contexts:      []string{"continuous-integration"},
		},
		RequiredPullRequestReviews: &github.RequiredPullRequestReviews{
			IncludeAdmins: true,
		},
		// TODO: Only organization repositories can have users and team restrictions.
		//       In order to be able to test these Restrictions, need to add support
		//       for creating temporary organization repositories.
		Restrictions: nil,
	}

	protection, _, err := client.Repositories.UpdateBranchProtection(*repo.Owner.Login, *repo.Name, "master", protectionRequest)
	if err != nil {
		t.Fatalf("Repositories.UpdateBranchProtection() returned error: %v", err)
	}

	want := &github.Protection{
		RequiredStatusChecks: &github.RequiredStatusChecks{
			IncludeAdmins: true,
			Strict:        true,
			Contexts:      []string{"continuous-integration"},
		},
		RequiredPullRequestReviews: &github.RequiredPullRequestReviews{
			IncludeAdmins: true,
		},
		Restrictions: nil,
	}
	if !reflect.DeepEqual(protection, want) {
		t.Errorf("Repositories.UpdateBranchProtection() returned %+v, want %+v", protection, want)
	}

	_, err = client.Repositories.Delete(*repo.Owner.Login, *repo.Name)
	if err != nil {
		t.Fatalf("Repositories.Delete() returned error: %v", err)
	}
}

func TestRepositories_List(t *testing.T) {
	if !checkAuth("TestRepositories_List") {
		return
	}

	_, _, err := client.Repositories.List("", nil)
	if err != nil {
		t.Fatalf("Repositories.List('') returned error: %v", err)
	}

	_, _, err = client.Repositories.List("google", nil)
	if err != nil {
		t.Fatalf("Repositories.List('google') returned error: %v", err)
	}

	opt := github.RepositoryListOptions{Sort: "created"}
	repos, _, err := client.Repositories.List("google", &opt)
	if err != nil {
		t.Fatalf("Repositories.List('google') with Sort opt returned error: %v", err)
	}
	for i, repo := range repos {
		if i > 0 && (*repos[i-1].CreatedAt).Time.Before((*repo.CreatedAt).Time) {
			t.Fatalf("Repositories.List('google') with default descending Sort returned incorrect order")
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy 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 (
    "bufio"
    "net"
    "strconv"
    "strings"

    "sippy/types"
)

func NewRtpProxyClient(opts *rtpProxyClientOpts) sippy_types.RtpProxyClient {
    return NewRtp_proxy_client_base(nil, opts)
}

type Rtp_proxy_client_base struct {
    heir            sippy_types.RtpProxyClient
    opts            *rtpProxyClientOpts
    transport       rtp_proxy_transport
    online          bool
    sbind_supported bool
    tnot_supported  bool
    copy_supported  bool
    stat_supported  bool
    wdnt_supported  bool
    caps_done       bool
    shut_down       bool
    active_sessions int64
    sessions_created int64
    active_streams  int64
    preceived       int64
    ptransmitted    int64
}

type rtp_proxy_transport interface {
    address() net.Addr
    get_rtpc_delay() float64
    is_local() bool
    send_command(string, func(string))
    shutdown()
}

func (self *Rtp_proxy_client_base) IsLocal() bool {
    return self.transport.is_local()
}

func (self *Rtp_proxy_client_base) IsOnline() bool {
    return self.online
}

func (self *Rtp_proxy_client_base) WdntSupported() bool {
    return self.wdnt_supported
}

func (self *Rtp_proxy_client_base) SBindSupported() bool {
    return self.sbind_supported
}

func (self *Rtp_proxy_client_base) TNotSupported() bool {
    return self.tnot_supported
}

func (self *Rtp_proxy_client_base) GetProxyAddress() string {
    return self.opts.proxy_address
}

func (self *Rtp_proxy_client_base) me() sippy_types.RtpProxyClient {
    if self.heir != nil {
        return self.heir
    }
    return self
}

func (self *Rtp_proxy_client_base) Address() net.Addr {
    return self.transport.address()
}

func NewRtp_proxy_client_base(heir sippy_types.RtpProxyClient, opts *rtpProxyClientOpts) *Rtp_proxy_client_base {
    return &Rtp_proxy_client_base{
        heir            : heir,
        caps_done       : false,
        shut_down       : false,
        opts            : opts,
    }
}

func (self *Rtp_proxy_client_base) Start() error {
    var err error

    self.transport, err = self.opts.rtpp_class(self.me(), self.opts.config, self.opts.rtppaddr)
    if err != nil {
        return err
    }
    if ! self.opts.no_version_check {
        self.version_check()
    } else {
        self.caps_done = true
        self.online = true
    }
    return nil
}

func (self *Rtp_proxy_client_base) SendCommand(cmd string, cb func(string)) {
    self.transport.send_command(cmd, cb)
}
/*
    def reconnect(self, *args, **kwargs):
        self.rtpp_class.reconnect(self, *args, **kwargs)
*/

func (self *Rtp_proxy_client_base) version_check() {
    if self.shut_down {
        return
    }
    self.transport.send_command("V", self.version_check_reply)
}

func (self *Rtp_proxy_client_base) version_check_reply(version string) {
    if self.shut_down {
        return
    }
    if version == "20040107" {
        self.me().GoOnline()
    } else if self.online {
        self.me().GoOffline()
    } else {
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) heartbeat() {
    //print "heartbeat", self, self.address
    if self.shut_down {
        return
    }
    self.transport.send_command("Ib", self.heartbeat_reply)
}

func (self *Rtp_proxy_client_base) heartbeat_reply(stats string) {
    //print "heartbeat_reply", self.address, stats, self.online
    if self.shut_down || ! self.online {
        return
    }
    if stats == "" {
        self.active_sessions = 0
        self.me().GoOffline()
    } else {
        sessions_created := int64(0)
        active_sessions := int64(0)
        active_streams := int64(0)
        preceived := int64(0)
        ptransmitted := int64(0)
        scanner := bufio.NewScanner(strings.NewReader(stats))
        for scanner.Scan() {
            line_parts := strings.SplitN(scanner.Text(), ":", 2)
            if len(line_parts) != 2 { continue }
            switch line_parts[0] {
            case "sessions created":
                sessions_created, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "active sessions":
                active_sessions, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "active streams":
                active_streams, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "packets received":
                preceived, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "packets transmitted":
                ptransmitted, _ = strconv.ParseInt(line_parts[1], 10, 64)
            }
        }
        self.update_active(active_sessions, sessions_created, active_streams, preceived, ptransmitted)
    }
    StartTimeoutWithSpread(self.heartbeat, nil, self.opts.hrtb_ival, 1, self.opts.logger, 0.1)
}

func (self *Rtp_proxy_client_base) GoOnline() {
    if self.shut_down {
        return
    }
    if ! self.online {
        if ! self.caps_done {
            newRtppCapsChecker(self)
            return
        }
        self.online = true
        self.heartbeat()
    }
}

func (self *Rtp_proxy_client_base) GoOffline() {
    if self.shut_down {
        return
    }
    //print "go_offline", self.address, self.online
    if self.online {
        self.online = false
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) update_active(active_sessions, sessions_created, active_streams, preceived, ptransmitted int64) {
    self.sessions_created = sessions_created
    self.active_sessions = active_sessions
    self.active_streams = active_streams
    self.preceived = preceived
    self.ptransmitted = ptransmitted
}

func (self *Rtp_proxy_client_base) Shutdown() {
    if self.shut_down { // do not crash when shutdown() called twice
        return
    }
    self.shut_down = true
    self.transport.shutdown()
    self.transport = nil
}

func (self *Rtp_proxy_client_base) GetOpts() sippy_types.RtpProxyClientOpts {
    return self.opts
}

func (self *Rtp_proxy_client_base) GetRtpcDelay() {
    self.transport.get_rtpc_delay()
}

type rtppCapsChecker struct {
    caps_requested  int
    caps_received   int
    rtpc            *Rtp_proxy_client_base
}

func newRtppCapsChecker(rtpc *Rtp_proxy_client_base) *rtppCapsChecker {
    self := &rtppCapsChecker{
        rtpc    : rtpc,
    }
    rtpc.caps_done = false
    CAPSTABLE := []struct{ vers string; attr *bool }{
        { "20071218", &self.rtpc.copy_supported },
        { "20080403", &self.rtpc.stat_supported },
        { "20081224", &self.rtpc.tnot_supported },
        { "20090810", &self.rtpc.sbind_supported },
        { "20150617", &self.rtpc.wdnt_supported },
    }
    self.caps_requested = len(CAPSTABLE)
    for _, it := range CAPSTABLE {
        attr := it.attr // For some reason the it.attr cannot be passed into the following
                        // function directly - the resulting value is always that of the
                        // last 'it.attr' value.
        rtpc.transport.send_command("VF " + it.vers, func(res string) { self.caps_query_done(res, attr) })
    }
    return self
}

func (self *rtppCapsChecker) caps_query_done(result string, attr *bool) {
    self.caps_received += 1
    if result == "1" {
        *attr = true
    } else {
        *attr = false
    }
    if self.caps_received == self.caps_requested {
        self.rtpc.caps_done = true
        self.rtpc.GoOnline()
        self.rtpc = nil
    }
}
<commit_msg>Fix the return value type.<commit_after>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy 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 (
    "bufio"
    "net"
    "strconv"
    "strings"

    "sippy/types"
)

func NewRtpProxyClient(opts *rtpProxyClientOpts) sippy_types.RtpProxyClient {
    return NewRtp_proxy_client_base(nil, opts)
}

type Rtp_proxy_client_base struct {
    heir            sippy_types.RtpProxyClient
    opts            *rtpProxyClientOpts
    transport       rtp_proxy_transport
    online          bool
    sbind_supported bool
    tnot_supported  bool
    copy_supported  bool
    stat_supported  bool
    wdnt_supported  bool
    caps_done       bool
    shut_down       bool
    active_sessions int64
    sessions_created int64
    active_streams  int64
    preceived       int64
    ptransmitted    int64
}

type rtp_proxy_transport interface {
    address() net.Addr
    get_rtpc_delay() float64
    is_local() bool
    send_command(string, func(string))
    shutdown()
}

func (self *Rtp_proxy_client_base) IsLocal() bool {
    return self.transport.is_local()
}

func (self *Rtp_proxy_client_base) IsOnline() bool {
    return self.online
}

func (self *Rtp_proxy_client_base) WdntSupported() bool {
    return self.wdnt_supported
}

func (self *Rtp_proxy_client_base) SBindSupported() bool {
    return self.sbind_supported
}

func (self *Rtp_proxy_client_base) TNotSupported() bool {
    return self.tnot_supported
}

func (self *Rtp_proxy_client_base) GetProxyAddress() string {
    return self.opts.proxy_address
}

func (self *Rtp_proxy_client_base) me() sippy_types.RtpProxyClient {
    if self.heir != nil {
        return self.heir
    }
    return self
}

func (self *Rtp_proxy_client_base) Address() net.Addr {
    return self.transport.address()
}

func NewRtp_proxy_client_base(heir sippy_types.RtpProxyClient, opts *rtpProxyClientOpts) *Rtp_proxy_client_base {
    return &Rtp_proxy_client_base{
        heir            : heir,
        caps_done       : false,
        shut_down       : false,
        opts            : opts,
    }
}

func (self *Rtp_proxy_client_base) Start() error {
    var err error

    self.transport, err = self.opts.rtpp_class(self.me(), self.opts.config, self.opts.rtppaddr)
    if err != nil {
        return err
    }
    if ! self.opts.no_version_check {
        self.version_check()
    } else {
        self.caps_done = true
        self.online = true
    }
    return nil
}

func (self *Rtp_proxy_client_base) SendCommand(cmd string, cb func(string)) {
    self.transport.send_command(cmd, cb)
}
/*
    def reconnect(self, *args, **kwargs):
        self.rtpp_class.reconnect(self, *args, **kwargs)
*/

func (self *Rtp_proxy_client_base) version_check() {
    if self.shut_down {
        return
    }
    self.transport.send_command("V", self.version_check_reply)
}

func (self *Rtp_proxy_client_base) version_check_reply(version string) {
    if self.shut_down {
        return
    }
    if version == "20040107" {
        self.me().GoOnline()
    } else if self.online {
        self.me().GoOffline()
    } else {
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) heartbeat() {
    //print "heartbeat", self, self.address
    if self.shut_down {
        return
    }
    self.transport.send_command("Ib", self.heartbeat_reply)
}

func (self *Rtp_proxy_client_base) heartbeat_reply(stats string) {
    //print "heartbeat_reply", self.address, stats, self.online
    if self.shut_down || ! self.online {
        return
    }
    if stats == "" {
        self.active_sessions = 0
        self.me().GoOffline()
    } else {
        sessions_created := int64(0)
        active_sessions := int64(0)
        active_streams := int64(0)
        preceived := int64(0)
        ptransmitted := int64(0)
        scanner := bufio.NewScanner(strings.NewReader(stats))
        for scanner.Scan() {
            line_parts := strings.SplitN(scanner.Text(), ":", 2)
            if len(line_parts) != 2 { continue }
            switch line_parts[0] {
            case "sessions created":
                sessions_created, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "active sessions":
                active_sessions, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "active streams":
                active_streams, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "packets received":
                preceived, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "packets transmitted":
                ptransmitted, _ = strconv.ParseInt(line_parts[1], 10, 64)
            }
        }
        self.update_active(active_sessions, sessions_created, active_streams, preceived, ptransmitted)
    }
    StartTimeoutWithSpread(self.heartbeat, nil, self.opts.hrtb_ival, 1, self.opts.logger, 0.1)
}

func (self *Rtp_proxy_client_base) GoOnline() {
    if self.shut_down {
        return
    }
    if ! self.online {
        if ! self.caps_done {
            newRtppCapsChecker(self)
            return
        }
        self.online = true
        self.heartbeat()
    }
}

func (self *Rtp_proxy_client_base) GoOffline() {
    if self.shut_down {
        return
    }
    //print "go_offline", self.address, self.online
    if self.online {
        self.online = false
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) update_active(active_sessions, sessions_created, active_streams, preceived, ptransmitted int64) {
    self.sessions_created = sessions_created
    self.active_sessions = active_sessions
    self.active_streams = active_streams
    self.preceived = preceived
    self.ptransmitted = ptransmitted
}

func (self *Rtp_proxy_client_base) Shutdown() {
    if self.shut_down { // do not crash when shutdown() called twice
        return
    }
    self.shut_down = true
    self.transport.shutdown()
    self.transport = nil
}

func (self *Rtp_proxy_client_base) GetOpts() sippy_types.RtpProxyClientOpts {
    return self.opts
}

func (self *Rtp_proxy_client_base) GetRtpcDelay() float64 {
    return self.transport.get_rtpc_delay()
}

type rtppCapsChecker struct {
    caps_requested  int
    caps_received   int
    rtpc            *Rtp_proxy_client_base
}

func newRtppCapsChecker(rtpc *Rtp_proxy_client_base) *rtppCapsChecker {
    self := &rtppCapsChecker{
        rtpc    : rtpc,
    }
    rtpc.caps_done = false
    CAPSTABLE := []struct{ vers string; attr *bool }{
        { "20071218", &self.rtpc.copy_supported },
        { "20080403", &self.rtpc.stat_supported },
        { "20081224", &self.rtpc.tnot_supported },
        { "20090810", &self.rtpc.sbind_supported },
        { "20150617", &self.rtpc.wdnt_supported },
    }
    self.caps_requested = len(CAPSTABLE)
    for _, it := range CAPSTABLE {
        attr := it.attr // For some reason the it.attr cannot be passed into the following
                        // function directly - the resulting value is always that of the
                        // last 'it.attr' value.
        rtpc.transport.send_command("VF " + it.vers, func(res string) { self.caps_query_done(res, attr) })
    }
    return self
}

func (self *rtppCapsChecker) caps_query_done(result string, attr *bool) {
    self.caps_received += 1
    if result == "1" {
        *attr = true
    } else {
        *attr = false
    }
    if self.caps_received == self.caps_requested {
        self.rtpc.caps_done = true
        self.rtpc.GoOnline()
        self.rtpc = nil
    }
}
<|endoftext|>


<commit_before>/*
Package pfsutil provides utility functions that wrap a pfs.APIClient
to make the calling code slightly cleaner.
*/
package pfsutil

import (
	"io"
	"math"

	"github.com/pachyderm/pachyderm/src/pfs"
	"go.pedge.io/proto/stream"
	"golang.org/x/net/context"
)

const chunkSize = 4096

func NewRepo(repoName string) *pfs.Repo {
	return &pfs.Repo{Name: repoName}
}

func NewCommit(repoName string, commitID string) *pfs.Commit {
	return &pfs.Commit{
		Repo: &pfs.Repo{
			Name: repoName,
		},
		Id: commitID,
	}
}

func NewFile(repoName string, commitID string, path string) *pfs.File {
	return &pfs.File{
		Commit: &pfs.Commit{
			Repo: &pfs.Repo{
				Name: repoName,
			},
			Id: commitID,
		},
		Path: path,
	}
}

func CreateRepo(apiClient pfs.APIClient, repoName string) error {
	_, err := apiClient.CreateRepo(
		context.Background(),
		&pfs.CreateRepoRequest{
			Repo: &pfs.Repo{
				Name: repoName,
			},
		},
	)
	return err
}

func InspectRepo(apiClient pfs.APIClient, repoName string) (*pfs.RepoInfo, error) {
	repoInfo, err := apiClient.InspectRepo(
		context.Background(),
		&pfs.InspectRepoRequest{
			Repo: &pfs.Repo{
				Name: repoName,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return repoInfo, nil
}

func ListRepo(apiClient pfs.APIClient) ([]*pfs.RepoInfo, error) {
	repoInfos, err := apiClient.ListRepo(
		context.Background(),
		&pfs.ListRepoRequest{},
	)
	if err != nil {
		return nil, err
	}
	return repoInfos.RepoInfo, nil
}

func DeleteRepo(apiClient pfs.APIClient, repoName string) error {
	_, err := apiClient.DeleteRepo(
		context.Background(),
		&pfs.DeleteRepoRequest{
			Repo: &pfs.Repo{
				Name: repoName,
			},
		},
	)
	return err
}

func StartCommit(apiClient pfs.APIClient, repoName string, parentCommit string) (*pfs.Commit, error) {
	commit, err := apiClient.StartCommit(
		context.Background(),
		&pfs.StartCommitRequest{
			Parent: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: parentCommit,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return commit, nil
}

func FinishCommit(apiClient pfs.APIClient, repoName string, commitID string) error {
	_, err := apiClient.FinishCommit(
		context.Background(),
		&pfs.FinishCommitRequest{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
		},
	)
	return err
}

func InspectCommit(apiClient pfs.APIClient, repoName string, commitID string) (*pfs.CommitInfo, error) {
	commitInfo, err := apiClient.InspectCommit(
		context.Background(),
		&pfs.InspectCommitRequest{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return commitInfo, nil
}

func ListCommit(apiClient pfs.APIClient, repoNames []string) ([]*pfs.CommitInfo, error) {
	var repos []*pfs.Repo
	for _, repoName := range repoNames {
		repos = append(repos, &pfs.Repo{Name: repoName})
	}
	commitInfos, err := apiClient.ListCommit(
		context.Background(),
		&pfs.ListCommitRequest{
			Repo: repos,
		},
	)
	if err != nil {
		return nil, err
	}
	return commitInfos.CommitInfo, nil
}

func DeleteCommit(apiClient pfs.APIClient, repoName string, commitID string) error {
	_, err := apiClient.DeleteCommit(
		context.Background(),
		&pfs.DeleteCommitRequest{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
		},
	)
	return err
}

func PutBlock(apiClient pfs.BlockAPIClient, reader io.Reader) (*pfs.BlockRefs, error) {
	putBlockClient, err := apiClient.PutBlock(context.Background())
	if err != nil {
		return nil, err
	}
	if _, err := io.Copy(protostream.NewStreamingBytesWriter(putBlockClient), reader); err != nil {
		return nil, err
	}
	return putBlockClient.CloseAndRecv()
}

func GetBlock(apiClient pfs.BlockAPIClient, hash string, offsetBytes uint64, sizeBytes uint64) (io.Reader, error) {
	apiGetBlockClient, err := apiClient.GetBlock(
		context.Background(),
		&pfs.GetBlockRequest{
			Block: &pfs.Block{
				Hash: hash,
			},
			OffsetBytes: offsetBytes,
			SizeBytes:   sizeBytes,
		},
	)
	if err != nil {
		return nil, err
	}
	return protostream.NewStreamingBytesReader(apiGetBlockClient), nil
}

func InspectBlock(apiClient pfs.BlockAPIClient, hash string) (*pfs.BlockInfo, error) {
	blockInfo, err := apiClient.InspectBlock(
		context.Background(),
		&pfs.InspectBlockRequest{
			Block: &pfs.Block{
				Hash: hash,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return blockInfo, nil
}

func ListBlock(apiClient pfs.BlockAPIClient) ([]*pfs.BlockInfo, error) {
	blockInfos, err := apiClient.ListBlock(
		context.Background(),
		&pfs.ListBlockRequest{},
	)
	if err != nil {
		return nil, err
	}
	return blockInfos.BlockInfo, nil
}

func PutFile(apiClient pfs.APIClient, repoName string, commitID string, path string, offset int64, reader io.Reader) (_ int, retErr error) {
	putFileClient, err := apiClient.PutFile(context.Background())
	if err != nil {
		return 0, err
	}
	defer func() {
		if _, err := putFileClient.CloseAndRecv(); err != nil && retErr == nil {
			retErr = err
		}
	}()
	request := pfs.PutFileRequest{
		File: &pfs.File{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
			Path: path,
		},
		FileType:    pfs.FileType_FILE_TYPE_REGULAR,
		OffsetBytes: offset,
	}
	var size int
	for {
		value := make([]byte, chunkSize)
		iSize, err := reader.Read(value)
		if err != nil {
			if err == io.EOF {
				break
			}
			return 0, err
		}
		request.Value = value[0:iSize]
		size += iSize
		if err := putFileClient.Send(&request); err != nil {
			return 0, err
		}
	}
	if err != nil && err != io.EOF {
		return 0, err
	}
	return size, err
}

func GetFile(apiClient pfs.APIClient, repoName string, commitID string, path string, offset int64, size int64, shard *pfs.Shard, writer io.Writer) error {
	if size == 0 {
		size = math.MaxInt64
	}
	apiGetFileClient, err := apiClient.GetFile(
		context.Background(),
		&pfs.GetFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			Shard:       shard,
			OffsetBytes: offset,
			SizeBytes:   size,
		},
	)
	if err != nil {
		return err
	}
	if err := protostream.WriteFromStreamingBytesClient(apiGetFileClient, writer); err != nil {
		return err
	}
	return nil
}

func InspectFile(apiClient pfs.APIClient, repoName string, commitID string, path string, shard *pfs.Shard) (*pfs.FileInfo, error) {
	fileInfo, err := apiClient.InspectFile(
		context.Background(),
		&pfs.InspectFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			Shard: shard,
		},
	)
	if err != nil {
		return nil, err
	}
	return fileInfo, nil
}

func ListFile(apiClient pfs.APIClient, repoName string, commitID string, path string, shard *pfs.Shard) ([]*pfs.FileInfo, error) {
	fileInfos, err := apiClient.ListFile(
		context.Background(),
		&pfs.ListFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			Shard: shard,
		},
	)
	if err != nil {
		return nil, err
	}
	return fileInfos.FileInfo, nil
}

func DeleteFile(apiClient pfs.APIClient, repoName string, commitID string, path string) error {
	_, err := apiClient.DeleteFile(
		context.Background(),
		&pfs.DeleteFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
		},
	)
	return err
}

func MakeDirectory(apiClient pfs.APIClient, repoName string, commitID string, path string) (retErr error) {
	putFileClient, err := apiClient.PutFile(context.Background())
	if err != nil {
		return err
	}
	defer func() {
		if _, err := putFileClient.CloseAndRecv(); err != nil && retErr == nil {
			retErr = err
		}
	}()
	return putFileClient.Send(
		&pfs.PutFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			FileType: pfs.FileType_FILE_TYPE_DIR,
		},
	)
}
<commit_msg>Up chunk size and handle eof better.<commit_after>/*
Package pfsutil provides utility functions that wrap a pfs.APIClient
to make the calling code slightly cleaner.
*/
package pfsutil

import (
	"io"
	"math"

	"github.com/pachyderm/pachyderm/src/pfs"
	"go.pedge.io/proto/stream"
	"golang.org/x/net/context"
)

const chunkSize = 1024 * 1024

func NewRepo(repoName string) *pfs.Repo {
	return &pfs.Repo{Name: repoName}
}

func NewCommit(repoName string, commitID string) *pfs.Commit {
	return &pfs.Commit{
		Repo: &pfs.Repo{
			Name: repoName,
		},
		Id: commitID,
	}
}

func NewFile(repoName string, commitID string, path string) *pfs.File {
	return &pfs.File{
		Commit: &pfs.Commit{
			Repo: &pfs.Repo{
				Name: repoName,
			},
			Id: commitID,
		},
		Path: path,
	}
}

func CreateRepo(apiClient pfs.APIClient, repoName string) error {
	_, err := apiClient.CreateRepo(
		context.Background(),
		&pfs.CreateRepoRequest{
			Repo: &pfs.Repo{
				Name: repoName,
			},
		},
	)
	return err
}

func InspectRepo(apiClient pfs.APIClient, repoName string) (*pfs.RepoInfo, error) {
	repoInfo, err := apiClient.InspectRepo(
		context.Background(),
		&pfs.InspectRepoRequest{
			Repo: &pfs.Repo{
				Name: repoName,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return repoInfo, nil
}

func ListRepo(apiClient pfs.APIClient) ([]*pfs.RepoInfo, error) {
	repoInfos, err := apiClient.ListRepo(
		context.Background(),
		&pfs.ListRepoRequest{},
	)
	if err != nil {
		return nil, err
	}
	return repoInfos.RepoInfo, nil
}

func DeleteRepo(apiClient pfs.APIClient, repoName string) error {
	_, err := apiClient.DeleteRepo(
		context.Background(),
		&pfs.DeleteRepoRequest{
			Repo: &pfs.Repo{
				Name: repoName,
			},
		},
	)
	return err
}

func StartCommit(apiClient pfs.APIClient, repoName string, parentCommit string) (*pfs.Commit, error) {
	commit, err := apiClient.StartCommit(
		context.Background(),
		&pfs.StartCommitRequest{
			Parent: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: parentCommit,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return commit, nil
}

func FinishCommit(apiClient pfs.APIClient, repoName string, commitID string) error {
	_, err := apiClient.FinishCommit(
		context.Background(),
		&pfs.FinishCommitRequest{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
		},
	)
	return err
}

func InspectCommit(apiClient pfs.APIClient, repoName string, commitID string) (*pfs.CommitInfo, error) {
	commitInfo, err := apiClient.InspectCommit(
		context.Background(),
		&pfs.InspectCommitRequest{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return commitInfo, nil
}

func ListCommit(apiClient pfs.APIClient, repoNames []string) ([]*pfs.CommitInfo, error) {
	var repos []*pfs.Repo
	for _, repoName := range repoNames {
		repos = append(repos, &pfs.Repo{Name: repoName})
	}
	commitInfos, err := apiClient.ListCommit(
		context.Background(),
		&pfs.ListCommitRequest{
			Repo: repos,
		},
	)
	if err != nil {
		return nil, err
	}
	return commitInfos.CommitInfo, nil
}

func DeleteCommit(apiClient pfs.APIClient, repoName string, commitID string) error {
	_, err := apiClient.DeleteCommit(
		context.Background(),
		&pfs.DeleteCommitRequest{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
		},
	)
	return err
}

func PutBlock(apiClient pfs.BlockAPIClient, reader io.Reader) (*pfs.BlockRefs, error) {
	putBlockClient, err := apiClient.PutBlock(context.Background())
	if err != nil {
		return nil, err
	}
	if _, err := io.Copy(protostream.NewStreamingBytesWriter(putBlockClient), reader); err != nil {
		return nil, err
	}
	return putBlockClient.CloseAndRecv()
}

func GetBlock(apiClient pfs.BlockAPIClient, hash string, offsetBytes uint64, sizeBytes uint64) (io.Reader, error) {
	apiGetBlockClient, err := apiClient.GetBlock(
		context.Background(),
		&pfs.GetBlockRequest{
			Block: &pfs.Block{
				Hash: hash,
			},
			OffsetBytes: offsetBytes,
			SizeBytes:   sizeBytes,
		},
	)
	if err != nil {
		return nil, err
	}
	return protostream.NewStreamingBytesReader(apiGetBlockClient), nil
}

func InspectBlock(apiClient pfs.BlockAPIClient, hash string) (*pfs.BlockInfo, error) {
	blockInfo, err := apiClient.InspectBlock(
		context.Background(),
		&pfs.InspectBlockRequest{
			Block: &pfs.Block{
				Hash: hash,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	return blockInfo, nil
}

func ListBlock(apiClient pfs.BlockAPIClient) ([]*pfs.BlockInfo, error) {
	blockInfos, err := apiClient.ListBlock(
		context.Background(),
		&pfs.ListBlockRequest{},
	)
	if err != nil {
		return nil, err
	}
	return blockInfos.BlockInfo, nil
}

func PutFile(apiClient pfs.APIClient, repoName string, commitID string, path string, offset int64, reader io.Reader) (_ int, retErr error) {
	putFileClient, err := apiClient.PutFile(context.Background())
	if err != nil {
		return 0, err
	}
	defer func() {
		if _, err := putFileClient.CloseAndRecv(); err != nil && retErr == nil {
			retErr = err
		}
	}()
	request := pfs.PutFileRequest{
		File: &pfs.File{
			Commit: &pfs.Commit{
				Repo: &pfs.Repo{
					Name: repoName,
				},
				Id: commitID,
			},
			Path: path,
		},
		FileType:    pfs.FileType_FILE_TYPE_REGULAR,
		OffsetBytes: offset,
	}
	var size int
	eof := false
	for !eof {
		value := make([]byte, chunkSize)
		iSize, err := reader.Read(value)
		if err != nil && err != io.EOF {
			return 0, err
		}
		if err == io.EOF {
			eof = true
		}
		request.Value = value[0:iSize]
		size += iSize
		if err := putFileClient.Send(&request); err != nil {
			return 0, err
		}
	}
	if err != nil && err != io.EOF {
		return 0, err
	}
	return size, err
}

func GetFile(apiClient pfs.APIClient, repoName string, commitID string, path string, offset int64, size int64, shard *pfs.Shard, writer io.Writer) error {
	if size == 0 {
		size = math.MaxInt64
	}
	apiGetFileClient, err := apiClient.GetFile(
		context.Background(),
		&pfs.GetFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			Shard:       shard,
			OffsetBytes: offset,
			SizeBytes:   size,
		},
	)
	if err != nil {
		return err
	}
	if err := protostream.WriteFromStreamingBytesClient(apiGetFileClient, writer); err != nil {
		return err
	}
	return nil
}

func InspectFile(apiClient pfs.APIClient, repoName string, commitID string, path string, shard *pfs.Shard) (*pfs.FileInfo, error) {
	fileInfo, err := apiClient.InspectFile(
		context.Background(),
		&pfs.InspectFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			Shard: shard,
		},
	)
	if err != nil {
		return nil, err
	}
	return fileInfo, nil
}

func ListFile(apiClient pfs.APIClient, repoName string, commitID string, path string, shard *pfs.Shard) ([]*pfs.FileInfo, error) {
	fileInfos, err := apiClient.ListFile(
		context.Background(),
		&pfs.ListFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			Shard: shard,
		},
	)
	if err != nil {
		return nil, err
	}
	return fileInfos.FileInfo, nil
}

func DeleteFile(apiClient pfs.APIClient, repoName string, commitID string, path string) error {
	_, err := apiClient.DeleteFile(
		context.Background(),
		&pfs.DeleteFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
		},
	)
	return err
}

func MakeDirectory(apiClient pfs.APIClient, repoName string, commitID string, path string) (retErr error) {
	putFileClient, err := apiClient.PutFile(context.Background())
	if err != nil {
		return err
	}
	defer func() {
		if _, err := putFileClient.CloseAndRecv(); err != nil && retErr == nil {
			retErr = err
		}
	}()
	return putFileClient.Send(
		&pfs.PutFileRequest{
			File: &pfs.File{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{
						Name: repoName,
					},
					Id: commitID,
				},
				Path: path,
			},
			FileType: pfs.FileType_FILE_TYPE_DIR,
		},
	)
}
<|endoftext|>


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

package ebnf

import (
	"io"
	"os"
	"scanner"
	"strconv"
)

type parser struct {
	errors  errorList
	scanner scanner.Scanner
	pos     scanner.Position // token position
	tok     rune             // one token look-ahead
	lit     string           // token literal
}

func (p *parser) next() {
	p.tok = p.scanner.Scan()
	p.pos = p.scanner.Position
	p.lit = p.scanner.TokenText()
}

func (p *parser) error(pos scanner.Position, msg string) {
	p.errors = append(p.errors, newError(pos, msg))
}

func (p *parser) errorExpected(pos scanner.Position, msg string) {
	msg = `expected "` + msg + `"`
	if pos.Offset == p.pos.Offset {
		// the error happened at the current position;
		// make the error message more specific
		msg += ", found " + scanner.TokenString(p.tok)
		if p.tok < 0 {
			msg += " " + p.lit
		}
	}
	p.error(pos, msg)
}

func (p *parser) expect(tok rune) scanner.Position {
	pos := p.pos
	if p.tok != tok {
		p.errorExpected(pos, scanner.TokenString(tok))
	}
	p.next() // make progress in any case
	return pos
}

func (p *parser) parseIdentifier() *Name {
	pos := p.pos
	name := p.lit
	p.expect(scanner.Ident)
	return &Name{pos, name}
}

func (p *parser) parseToken() *Token {
	pos := p.pos
	value := ""
	if p.tok == scanner.String {
		value, _ = strconv.Unquote(p.lit)
		// Unquote may fail with an error, but only if the scanner found
		// an illegal string in the first place. In this case the error
		// has already been reported.
		p.next()
	} else {
		p.expect(scanner.String)
	}
	return &Token{pos, value}
}

// ParseTerm returns nil if no term was found.
func (p *parser) parseTerm() (x Expression) {
	pos := p.pos

	switch p.tok {
	case scanner.Ident:
		x = p.parseIdentifier()

	case scanner.String:
		tok := p.parseToken()
		x = tok
		const ellipsis = '…' // U+2026, the horizontal ellipsis character
		if p.tok == ellipsis {
			p.next()
			x = &Range{tok, p.parseToken()}
		}

	case '(':
		p.next()
		x = &Group{pos, p.parseExpression()}
		p.expect(')')

	case '[':
		p.next()
		x = &Option{pos, p.parseExpression()}
		p.expect(']')

	case '{':
		p.next()
		x = &Repetition{pos, p.parseExpression()}
		p.expect('}')
	}

	return x
}

func (p *parser) parseSequence() Expression {
	var list Sequence

	for x := p.parseTerm(); x != nil; x = p.parseTerm() {
		list = append(list, x)
	}

	// no need for a sequence if list.Len() < 2
	switch len(list) {
	case 0:
		p.errorExpected(p.pos, "term")
		return &Bad{p.pos, "term expected"}
	case 1:
		return list[0]
	}

	return list
}

func (p *parser) parseExpression() Expression {
	var list Alternative

	for {
		list = append(list, p.parseSequence())
		if p.tok != '|' {
			break
		}
		p.next()
	}
	// len(list) > 0

	// no need for an Alternative node if list.Len() < 2
	if len(list) == 1 {
		return list[0]
	}

	return list
}

func (p *parser) parseProduction() *Production {
	name := p.parseIdentifier()
	p.expect('=')
	var expr Expression
	if p.tok != '.' {
		expr = p.parseExpression()
	}
	p.expect('.')
	return &Production{name, expr}
}

func (p *parser) parse(filename string, src io.Reader) Grammar {
	p.scanner.Init(src)
	p.scanner.Filename = filename
	p.next() // initializes pos, tok, lit

	grammar := make(Grammar)
	for p.tok != scanner.EOF {
		prod := p.parseProduction()
		name := prod.Name.String
		if _, found := grammar[name]; !found {
			grammar[name] = prod
		} else {
			p.error(prod.Pos(), name+" declared already")
		}
	}

	return grammar
}

// Parse parses a set of EBNF productions from source src.
// It returns a set of productions. Errors are reported
// for incorrect syntax and if a production is declared
// more than once; the filename is used only for error
// positions.
//
func Parse(filename string, src io.Reader) (Grammar, os.Error) {
	var p parser
	grammar := p.parse(filename, src)
	return grammar, p.errors.Error()
}
<commit_msg>exp/ebnf: manual fixup for error<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ebnf

import (
	"io"
	"os"
	"scanner"
	"strconv"
)

type parser struct {
	errors  errorList
	scanner scanner.Scanner
	pos     scanner.Position // token position
	tok     rune             // one token look-ahead
	lit     string           // token literal
}

func (p *parser) next() {
	p.tok = p.scanner.Scan()
	p.pos = p.scanner.Position
	p.lit = p.scanner.TokenText()
}

func (p *parser) error(pos scanner.Position, msg string) {
	p.errors = append(p.errors, newError(pos, msg))
}

func (p *parser) errorExpected(pos scanner.Position, msg string) {
	msg = `expected "` + msg + `"`
	if pos.Offset == p.pos.Offset {
		// the error happened at the current position;
		// make the error message more specific
		msg += ", found " + scanner.TokenString(p.tok)
		if p.tok < 0 {
			msg += " " + p.lit
		}
	}
	p.error(pos, msg)
}

func (p *parser) expect(tok rune) scanner.Position {
	pos := p.pos
	if p.tok != tok {
		p.errorExpected(pos, scanner.TokenString(tok))
	}
	p.next() // make progress in any case
	return pos
}

func (p *parser) parseIdentifier() *Name {
	pos := p.pos
	name := p.lit
	p.expect(scanner.Ident)
	return &Name{pos, name}
}

func (p *parser) parseToken() *Token {
	pos := p.pos
	value := ""
	if p.tok == scanner.String {
		value, _ = strconv.Unquote(p.lit)
		// Unquote may fail with an error, but only if the scanner found
		// an illegal string in the first place. In this case the error
		// has already been reported.
		p.next()
	} else {
		p.expect(scanner.String)
	}
	return &Token{pos, value}
}

// ParseTerm returns nil if no term was found.
func (p *parser) parseTerm() (x Expression) {
	pos := p.pos

	switch p.tok {
	case scanner.Ident:
		x = p.parseIdentifier()

	case scanner.String:
		tok := p.parseToken()
		x = tok
		const ellipsis = '…' // U+2026, the horizontal ellipsis character
		if p.tok == ellipsis {
			p.next()
			x = &Range{tok, p.parseToken()}
		}

	case '(':
		p.next()
		x = &Group{pos, p.parseExpression()}
		p.expect(')')

	case '[':
		p.next()
		x = &Option{pos, p.parseExpression()}
		p.expect(']')

	case '{':
		p.next()
		x = &Repetition{pos, p.parseExpression()}
		p.expect('}')
	}

	return x
}

func (p *parser) parseSequence() Expression {
	var list Sequence

	for x := p.parseTerm(); x != nil; x = p.parseTerm() {
		list = append(list, x)
	}

	// no need for a sequence if list.Len() < 2
	switch len(list) {
	case 0:
		p.errorExpected(p.pos, "term")
		return &Bad{p.pos, "term expected"}
	case 1:
		return list[0]
	}

	return list
}

func (p *parser) parseExpression() Expression {
	var list Alternative

	for {
		list = append(list, p.parseSequence())
		if p.tok != '|' {
			break
		}
		p.next()
	}
	// len(list) > 0

	// no need for an Alternative node if list.Len() < 2
	if len(list) == 1 {
		return list[0]
	}

	return list
}

func (p *parser) parseProduction() *Production {
	name := p.parseIdentifier()
	p.expect('=')
	var expr Expression
	if p.tok != '.' {
		expr = p.parseExpression()
	}
	p.expect('.')
	return &Production{name, expr}
}

func (p *parser) parse(filename string, src io.Reader) Grammar {
	p.scanner.Init(src)
	p.scanner.Filename = filename
	p.next() // initializes pos, tok, lit

	grammar := make(Grammar)
	for p.tok != scanner.EOF {
		prod := p.parseProduction()
		name := prod.Name.String
		if _, found := grammar[name]; !found {
			grammar[name] = prod
		} else {
			p.error(prod.Pos(), name+" declared already")
		}
	}

	return grammar
}

// Parse parses a set of EBNF productions from source src.
// It returns a set of productions. Errors are reported
// for incorrect syntax and if a production is declared
// more than once; the filename is used only for error
// positions.
//
func Parse(filename string, src io.Reader) (Grammar, os.Error) {
	var p parser
	grammar := p.parse(filename, src)
	return grammar, p.errors.Err()
}
<|endoftext|>


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

package monitor

import (
	"errors"
	"github.com/nanobox-io/yoke/config"
	"github.com/nanobox-io/yoke/state"
	"sync"
	"time"
)

var (
	ClusterUnaviable = errors.New("none of the nodes in the cluster are available")
)

type (
	Looper interface {
		Loop(time.Duration) error
	}

	decider struct {
		sync.Mutex

		me        state.State
		other     state.State
		monitor   state.State
		performer Performer
	}
)

func NewDecider(me, other, monitor state.State, performer Performer) Looper {
	decider := decider{
		me:        me,
		other:     other,
		monitor:   monitor,
		performer: performer,
	}
	for {
		// Really we only have to wait for a quorum, 2 out of 3 will allow everything to be ok.
		// But in certain conditions, this node was a backup that was down, and the current active
		// if offline, we need to wait for all 3 nodes.
		// So really we are going to wait for all 3 nodes to make it simple
		// me is already Ready. no need to call it
		config.Log.Info("waiting for cluster to be ready")
		other.Ready()
		monitor.Ready()
		config.Log.Info("cluster is ready")

		err := decider.reCheck()
		switch err {
		case ClusterUnaviable: // we try again.
		case nil: // the cluster was successfully rechecked
			return decider
		default: // another kind of error occured
			panic(err)
		}
	}
}

// this is the main loop for monitoring the cluster and making any changes needed to
// reflect changes in remote nodes in the cluster
func (decider decider) Loop(check time.Duration) error {
	timer := time.Tick(check)
	for range timer {
		err := decider.reCheck()
		if err != nil {
			return err
			// just print it out? we will probably be able to decide later
		}
	}
	return nil
}

// this is used to move a active node to a backup node
func (decider decider) Demote() {
	decider.Lock()
	defer decider.Unlock()

	decider.me.SetDBRole("backup")
	decider.performer.TransitionToBackup()
}

// this is used to move a backup node to an active node
func (decider decider) Promote() {
	decider.Lock()
	defer decider.Unlock()

	decider.me.SetDBRole("active")
	decider.performer.TransitionToActive()
}

// Checks the other node in the cluster, falling back to bouncing the check off of the monitor,
// to see if the states between this node and the remote node match up
func (decider decider) reCheck() error {
	decider.Lock()
	defer decider.Unlock()

	var otherDBRole string
	var err error
	otherDBRole, err = decider.other.GetDBRole()
	if err != nil {
		address := decider.other.Location()
		otherDBRole, err = decider.monitor.Bounce(address).GetDBRole()
		if err != nil {
			// this node can't talk to the other member of the cluster or the monitor
			// if this node is not in single mode it needs to shut off
			if role, err := decider.me.GetDBRole(); role != "single" || err != nil {
				decider.performer.Stop()
				return ClusterUnaviable
			}
			return nil
		}
	}

	// we need to handle multiple possible states that the remote node is in
	switch otherDBRole {
	case "single":
		fallthrough
	case "active":
		decider.me.SetDBRole("backup")
		decider.performer.TransitionToBackup()
	case "dead":
		DBrole, err := decider.me.GetDBRole()
		if err != nil {
			return err
		}
		if DBrole == "backup" {
			// if this node is not synced up to the previous master, then we must wait for the other node to
			// come online
			hasSynced, err := decider.me.HasSynced()
			if err != nil {
				return err
			}
			if !hasSynced {
				decider.performer.Stop()
				return ClusterUnaviable
			}
		}

		decider.me.SetDBRole("single")
		decider.performer.TransitionToSingle()
	case "initialized":
		role, err := decider.me.GetRole()
		if err != nil {
			return err
		}
		switch role {
		case "primary":
			decider.me.SetDBRole("active")
			decider.performer.TransitionToActive()
		case "secondary":
			decider.me.SetDBRole("backup")
			decider.performer.TransitionToBackup()
		}
	case "backup":
		decider.me.SetDBRole("active")
		decider.performer.TransitionToActive()
	}
	return nil
}
<commit_msg>added more logging<commit_after>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package monitor

import (
	"errors"
	"github.com/nanobox-io/yoke/config"
	"github.com/nanobox-io/yoke/state"
	"sync"
	"time"
)

var (
	ClusterUnaviable = errors.New("none of the nodes in the cluster are available")
)

type (
	Looper interface {
		Loop(time.Duration) error
	}

	decider struct {
		sync.Mutex

		me        state.State
		other     state.State
		monitor   state.State
		performer Performer
	}
)

func NewDecider(me, other, monitor state.State, performer Performer) Looper {
	decider := decider{
		me:        me,
		other:     other,
		monitor:   monitor,
		performer: performer,
	}
	for {
		// Really we only have to wait for a quorum, 2 out of 3 will allow everything to be ok.
		// But in certain conditions, this node was a backup that was down, and the current active
		// if offline, we need to wait for all 3 nodes.
		// So really we are going to wait for all 3 nodes to make it simple
		// me is already Ready. no need to call it
		config.Log.Info("waiting for cluster to be ready")
		other.Ready()
		monitor.Ready()
		config.Log.Info("cluster is ready")

		err := decider.reCheck()
		switch err {
		case ClusterUnaviable: // we try again.
		case nil: // the cluster was successfully rechecked
			return decider
		default: // another kind of error occured
			panic(err)
		}
	}
}

// this is the main loop for monitoring the cluster and making any changes needed to
// reflect changes in remote nodes in the cluster
func (decider decider) Loop(check time.Duration) error {
	timer := time.Tick(check)
	for range timer {
		err := decider.reCheck()
		switch {
		case err == ClusterUnaviable:
		case err != nil:
			return err
		default:
		}
	}
	return nil
}

// this is used to move a active node to a backup node
func (decider decider) Demote() {
	decider.Lock()
	defer decider.Unlock()

	decider.me.SetDBRole("backup")
	decider.performer.TransitionToBackup()
}

// this is used to move a backup node to an active node
func (decider decider) Promote() {
	decider.Lock()
	defer decider.Unlock()

	decider.me.SetDBRole("active")
	decider.performer.TransitionToActive()
}

// Checks the other node in the cluster, falling back to bouncing the check off of the monitor,
// to see if the states between this node and the remote node match up
func (decider decider) reCheck() error {
	decider.Lock()
	defer decider.Unlock()

	var otherDBRole string
	var err error
	config.Log.Info("checking other role")
	otherDBRole, err = decider.other.GetDBRole()
	if err != nil {
		config.Log.Info("checking other role (bounce)")
		address := decider.other.Location()
		otherDBRole, err = decider.monitor.Bounce(address).GetDBRole()
		if err != nil {
			// this node can't talk to the other member of the cluster or the monitor
			// if this node is not in single mode it needs to shut off
			if role, err := decider.me.GetDBRole(); role != "single" || err != nil {
				config.Log.Info("stopping, no one here")
				decider.performer.Stop()
				return ClusterUnaviable
			}
			return nil
		}
	}

	config.Log.Info("got role", otherDBRole)

	// we need to handle multiple possible states that the remote node is in
	switch otherDBRole {
	case "single":
		fallthrough
	case "active":
		decider.me.SetDBRole("backup")
		decider.performer.TransitionToBackup()
	case "dead":
		DBrole, err := decider.me.GetDBRole()
		if err != nil {
			return err
		}
		if DBrole == "backup" {
			// if this node is not synced up to the previous master, then we must wait for the other node to
			// come online
			hasSynced, err := decider.me.HasSynced()
			if err != nil {
				return err
			}
			if !hasSynced {
				decider.performer.Stop()
				return ClusterUnaviable
			}
		}

		decider.me.SetDBRole("single")
		decider.performer.TransitionToSingle()
	case "initialized":
		role, err := decider.me.GetRole()
		if err != nil {
			return err
		}
		switch role {
		case "primary":
			decider.me.SetDBRole("active")
			decider.performer.TransitionToActive()
		case "secondary":
			decider.me.SetDBRole("backup")
			decider.performer.TransitionToBackup()
		}
	case "backup":
		decider.me.SetDBRole("active")
		decider.performer.TransitionToActive()
	}
	return nil
}
<|endoftext|>


<commit_before>package textprocessor

import (
  "log"
  "unicode/utf8"
)

var vowels = map[rune]bool {
  'a': true,
  'e': true,
  'i': true,
  'o': true,
  'u': true,
  'y': true,
}

type Vowel struct {
  Index int
}

type Consonant struct {
  Index int
}

func IsVowel(char rune) (bool) {
  return vowels[char]
}

type Word struct {
  StartIndex int
  EndIndex int
}

func (w Word) GetSlice(word string) ([]rune) {
  wordRunes := getRunesFromString(word)
  startIndex := w.StartIndex

  var endIndex int
  if w.EndIndex < 0 {
    endIndex = len(wordRunes) - w.EndIndex + 1
  } else {
    endIndex = w.EndIndex
  }

  if endIndex < 0 {
    log.Fatal(`Specified an EndIndex on a word that cannot exist. You specified
               '%d', but the word only has length %d`, w.EndIndex, len(wordRunes))
  }

  if startIndex > endIndex {
    log.Fatal(`Specified a StartIndex '%d' on a word that is greater than the
              EndIndex '%d' (for word of length '%d').`,
              w.StartIndex, w.EndIndex, len(wordRunes))
  }

  return wordRunes[startIndex:endIndex]
}

type FilterFunction func(word string) (bool)

/*
ProcessingRule is a rule for processing a string. It contains all the information
needed to determine whether a string should be processed, and what the resulting
normalized word will be.
*/
type ProcessingRule struct {
  Rule func(word string) (bool, []rune)
}

/*
IntermediateProcessingResult is an intermediate object used by the text processor
for chaining together declarations of a processing rule.
*/
type IntermediateProcessingResult interface {
  FilterBy(filterType string, args ...interface{}) (IntermediateProcessingResult)
  Try(args ...interface{}) (ProcessingRule)
}

/*
DefaultIntermediateProcessingResult is the default implementation of the
IntermediateProcessingResult interface. It carries everything necessary to
chain together a set of declarations to create a ProcessingRule.
*/
type DefaultIntermediateProcessingResult struct {
  FilterFunctions []FilterFunction
}

/*
FilterBy is a convenience method for creating an IntermediateProcessingResult and
calling FilterBy on the resulting struct.
*/
func FilterBy(filterType string, args ...interface{}) (IntermediateProcessingResult) {
  filterFunctions := []FilterFunction{}
  processingResult := DefaultIntermediateProcessingResult{filterFunctions}
  return processingResult.FilterBy(filterType, args)
}

/*
FilterBy defines a declaration for filtering words by a particular set of instructions.
The filterType can be one of the following:
  - "LongerThan"(integer): Filters words to be longer than a certain integer
  - "EndsWith"(Consonants/runes): Filters words to end with a certain set of consonants
    or rune characters.
*/
func (d DefaultIntermediateProcessingResult) FilterBy(filterType string, args ...interface{}) (IntermediateProcessingResult) {
  var currentFilterFunction FilterFunction

  switch filterType {
  case "LongerThan":
    currentFilterFunction = func(word string) (bool) {
      wordRunes := getRunesFromString(word)
      if len(args) != 1 {
        log.Fatal("Must use a single argument for 'LongerThan' filter type.")
      }
      length, validInt := args[0].(int)
      if validInt {
        log.Fatal("Must use an integer as the argument for 'LongerThan' filter type.")
      }
      return len(wordRunes) > length
    }
  case "EndsWith":
    currentFilterFunction = func(word string) (bool) {
      wordRunes := getRunesFromString(word)

      // Not enough characters in word, so it can't possibly end with the arguments provided
      if len(wordRunes) < len(args) {
        return false
      }

      consonantMapping := make(map[int][]int)
      vowelMapping := make(map[int][]int)
      for i, argument := range args {
        wordIndex := len(wordRunes) - i - 1

        switch arg := argument.(type) {
        case Consonant:
          consonantMapping[arg.Index] = append(consonantMapping[arg.Index], wordIndex)
        case Vowel:
          vowelMapping[arg.Index] = append(vowelMapping[arg.Index], wordIndex)
        case rune:
          if wordRunes[wordIndex] != arg {
            return false
          }
        default:
          log.Fatal("Invalid argument type '%T' for 'EndsWith' filter type.", argument)
        }
      }

      for consonantIndex := range consonantMapping {
        var requiredLetter rune
        for j, wordIndex := range consonantMapping[consonantIndex] {
          currentLetter := wordRunes[wordIndex]
          if !IsVowel(currentLetter) {
            return false
          }
          if j == 0 {
            requiredLetter = currentLetter
          } else if currentLetter != requiredLetter {
            return false
          }
        }
      }

      for vowelIndex := range vowelMapping {
        var requiredLetter rune
        for j, wordIndex := range vowelMapping[vowelIndex] {
          currentLetter := wordRunes[wordIndex]
          if !IsVowel(currentLetter) {
            return false
          }
          if j == 0 {
            requiredLetter = currentLetter
          } else if currentLetter != requiredLetter {
            return false
          }
        }
      }

      return true
    }
  default:
    log.Fatal(`Specified an invalid filter type '%s'.`, filterType)
  }

  var filterFunctions []FilterFunction
  filterFunctions = append(filterFunctions, d.FilterFunctions...)
  filterFunctions = append(filterFunctions, currentFilterFunction)

  return d
}

func (d DefaultIntermediateProcessingResult) Try(args ...interface{}) (ProcessingRule) {
  processFunc := func (word string) (bool, []rune) {
    for _, filterFunc := range d.FilterFunctions {
      if !filterFunc(word) {
        return false, []rune{}
      }
    }

    runeResult := make([]rune, 0)
    for _, argument := range args {
      switch argument.(type) {
      case Word:
        wordArgument, _ := argument.(Word)
        currentRunes := wordArgument.GetSlice(word)
        runeResult = append(runeResult, currentRunes...)
      case rune:
        runeArgument, _ := argument.(rune)
        runeResult = append(runeResult, runeArgument)
      default:
        log.Fatal(`Used an unknown argument type in the 'Try' method. Argument
                  '%q' with type '%T' cannot but used.`, argument, argument)
      }
    }
    return true, runeResult
  }

  return ProcessingRule{processFunc}
}

func getRunesFromString(str string) ([]rune) {
  runes := make([]rune, 0)
  for i, w := 0, 0; i < len(str); i += w {
    runeValue, width := utf8.DecodeRuneInString(str[i:])
    runes = append(runes, runeValue)
    w = width
  }

  return runes
}

<commit_msg>EndIndex is negative, so we should add (not subtract).<commit_after>package textprocessor

import (
  "log"
  "unicode/utf8"
)

var vowels = map[rune]bool {
  'a': true,
  'e': true,
  'i': true,
  'o': true,
  'u': true,
  'y': true,
}

type Vowel struct {
  Index int
}

type Consonant struct {
  Index int
}

func IsVowel(char rune) (bool) {
  return vowels[char]
}

type Word struct {
  StartIndex int
  EndIndex int
}

func (w Word) GetSlice(word string) ([]rune) {
  wordRunes := getRunesFromString(word)
  startIndex := w.StartIndex

  var endIndex int
  if w.EndIndex < 0 {
    endIndex = len(wordRunes) + w.EndIndex + 1
  } else {
    endIndex = w.EndIndex
  }

  if endIndex < 0 {
    log.Fatal(`Specified an EndIndex on a word that cannot exist. You specified
               '%d', but the word only has length %d`, w.EndIndex, len(wordRunes))
  }

  if startIndex > endIndex {
    log.Fatal(`Specified a StartIndex '%d' on a word that is greater than the
              EndIndex '%d' (for word of length '%d').`,
              w.StartIndex, w.EndIndex, len(wordRunes))
  }

  return wordRunes[startIndex:endIndex]
}

type FilterFunction func(word string) (bool)

/*
ProcessingRule is a rule for processing a string. It contains all the information
needed to determine whether a string should be processed, and what the resulting
normalized word will be.
*/
type ProcessingRule struct {
  Rule func(word string) (bool, []rune)
}

/*
IntermediateProcessingResult is an intermediate object used by the text processor
for chaining together declarations of a processing rule.
*/
type IntermediateProcessingResult interface {
  FilterBy(filterType string, args ...interface{}) (IntermediateProcessingResult)
  Try(args ...interface{}) (ProcessingRule)
}

/*
DefaultIntermediateProcessingResult is the default implementation of the
IntermediateProcessingResult interface. It carries everything necessary to
chain together a set of declarations to create a ProcessingRule.
*/
type DefaultIntermediateProcessingResult struct {
  FilterFunctions []FilterFunction
}

/*
FilterBy is a convenience method for creating an IntermediateProcessingResult and
calling FilterBy on the resulting struct.
*/
func FilterBy(filterType string, args ...interface{}) (IntermediateProcessingResult) {
  filterFunctions := []FilterFunction{}
  processingResult := DefaultIntermediateProcessingResult{filterFunctions}
  return processingResult.FilterBy(filterType, args)
}

/*
FilterBy defines a declaration for filtering words by a particular set of instructions.
The filterType can be one of the following:
  - "LongerThan"(integer): Filters words to be longer than a certain integer
  - "EndsWith"(Consonants/runes): Filters words to end with a certain set of consonants
    or rune characters.
*/
func (d DefaultIntermediateProcessingResult) FilterBy(filterType string, args ...interface{}) (IntermediateProcessingResult) {
  var currentFilterFunction FilterFunction

  switch filterType {
  case "LongerThan":
    currentFilterFunction = func(word string) (bool) {
      wordRunes := getRunesFromString(word)
      if len(args) != 1 {
        log.Fatal("Must use a single argument for 'LongerThan' filter type.")
      }
      length, validInt := args[0].(int)
      if validInt {
        log.Fatal("Must use an integer as the argument for 'LongerThan' filter type.")
      }
      return len(wordRunes) > length
    }
  case "EndsWith":
    currentFilterFunction = func(word string) (bool) {
      wordRunes := getRunesFromString(word)

      // Not enough characters in word, so it can't possibly end with the arguments provided
      if len(wordRunes) < len(args) {
        return false
      }

      consonantMapping := make(map[int][]int)
      vowelMapping := make(map[int][]int)
      for i, argument := range args {
        wordIndex := len(wordRunes) - i - 1

        switch arg := argument.(type) {
        case Consonant:
          consonantMapping[arg.Index] = append(consonantMapping[arg.Index], wordIndex)
        case Vowel:
          vowelMapping[arg.Index] = append(vowelMapping[arg.Index], wordIndex)
        case rune:
          if wordRunes[wordIndex] != arg {
            return false
          }
        default:
          log.Fatal("Invalid argument type '%T' for 'EndsWith' filter type.", argument)
        }
      }

      for consonantIndex := range consonantMapping {
        var requiredLetter rune
        for j, wordIndex := range consonantMapping[consonantIndex] {
          currentLetter := wordRunes[wordIndex]
          if !IsVowel(currentLetter) {
            return false
          }
          if j == 0 {
            requiredLetter = currentLetter
          } else if currentLetter != requiredLetter {
            return false
          }
        }
      }

      for vowelIndex := range vowelMapping {
        var requiredLetter rune
        for j, wordIndex := range vowelMapping[vowelIndex] {
          currentLetter := wordRunes[wordIndex]
          if !IsVowel(currentLetter) {
            return false
          }
          if j == 0 {
            requiredLetter = currentLetter
          } else if currentLetter != requiredLetter {
            return false
          }
        }
      }

      return true
    }
  default:
    log.Fatal(`Specified an invalid filter type '%s'.`, filterType)
  }

  var filterFunctions []FilterFunction
  filterFunctions = append(filterFunctions, d.FilterFunctions...)
  filterFunctions = append(filterFunctions, currentFilterFunction)

  return d
}

func (d DefaultIntermediateProcessingResult) Try(args ...interface{}) (ProcessingRule) {
  processFunc := func (word string) (bool, []rune) {
    for _, filterFunc := range d.FilterFunctions {
      if !filterFunc(word) {
        return false, []rune{}
      }
    }

    runeResult := make([]rune, 0)
    for _, argument := range args {
      switch argument.(type) {
      case Word:
        wordArgument, _ := argument.(Word)
        currentRunes := wordArgument.GetSlice(word)
        runeResult = append(runeResult, currentRunes...)
      case rune:
        runeArgument, _ := argument.(rune)
        runeResult = append(runeResult, runeArgument)
      default:
        log.Fatal(`Used an unknown argument type in the 'Try' method. Argument
                  '%q' with type '%T' cannot but used.`, argument, argument)
      }
    }
    return true, runeResult
  }

  return ProcessingRule{processFunc}
}

func getRunesFromString(str string) ([]rune) {
  runes := make([]rune, 0)
  for i, w := 0, 0; i < len(str); i += w {
    runeValue, width := utf8.DecodeRuneInString(str[i:])
    runes = append(runes, runeValue)
    w = width
  }

  return runes
}

<|endoftext|>