<commit_before>package controllers

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"

	"github.com/UniversityRadioYork/2016-site/models"
	"github.com/UniversityRadioYork/2016-site/structs"
	"github.com/UniversityRadioYork/2016-site/utils"
	"github.com/UniversityRadioYork/myradio-go"
	"github.com/gorilla/mux"
)

// weekFromVars extracts the year, and week strings from vars.
func weekFromVars(vars map[string]string) (string, string, error) {
	y, ok := vars["year"]
	if !ok {
		return "", "", errors.New("no year provided")
	}
	w, ok := vars["week"]
	if !ok {
		return "", "", errors.New("no week provided")
	}

	return y, w, nil
}

// weekdayFromVars extracts the year, week, and weekday strings from vars.
func weekdayFromVars(vars map[string]string) (string, string, string, error) {
	y, ok := vars["year"]
	if !ok {
		return "", "", "", errors.New("no year provided")
	}
	w, ok := vars["week"]
	if !ok {
		return "", "", "", errors.New("no week provided")
	}
	d, ok := vars["weekday"]
	if !ok {
		return "", "", "", errors.New("no weekday provided")
	}

	return y, w, d, nil
}

//
// Week schedule algorithm
// TODO(CaptainHayashi): move?
//

// WeekScheduleCell represents one cell in the week schedule.
type WeekScheduleCell struct {
	// Number of rows this cell spans.
	// If 0, this is a continuation from a cell further up.
	RowSpan uint

	// Pointer to the timeslot in this cell, if any.
	// Will be nil if 'RowSpan' is 0.
	Item *structs.ScheduleItem
}

// WeekScheduleRow represents one row in the week schedule.
type WeekScheduleRow struct {
	// The hour of the row (0..23).
	Hour int
	// The minute of the show (0..59).
	Minute int
	// The cells inside this row.
	Cells []WeekScheduleCell
}

// showStraddlesDay checks whether a show's start and finish cross over the boundary of a URY day.
func showStraddlesDay(start, finish time.Time) bool {
	nextDayStart := utils.StartOfDayOn(start.AddDate(0, 0, 1))
	return finish.After(nextDayStart)
}

// calculateScheduleBoundaries works out the earliest and latest hours in the schedule that need to display.
// It returns these as a pair of start and finish bound, both in terms of offsets from URY start time.
func calculateScheduleBoundaries(items []structs.ScheduleItem) (sOffset, fOffset int, err error) {
	if len(items) == 0 {
		err = errors.New("calculateScheduleBoundaries: no schedule")
		return
	}

	// These are the boundaries for culling, and are expanded upwards when we find shows that start earlier or finish later than the last-set boundary.
	// Initially they are set to one past their worst case to make the updating logic easier.
	// Since we assert we have a schedule, these values _will_ change.
	sOffset = 24
	fOffset = -1

	for _, s := range items {
		start := s.GetStart()
		finish := s.GetFinish()

		// Any show that isn't a sustainer affects the culling boundaries.
		if s.IsSustainer() {
			continue
		}

		if showStraddlesDay(start, finish) {
			// A show that straddles the day crosses over from the end of a day to the start of the day.
			// This means that we saturate the culling boundaries.
			// As an optimisation we don't need to consider any other show.
			sOffset = 0
			fOffset = 23
			return
		}

		// Otherwise, if its start/finish as offsets from start time are outside the current boundaries, update them.
		so := 0
		so, err = utils.HourToStartOffset(start.Hour())
		if err != nil {
			return
		}
		if so < sOffset {
			sOffset = so
		}

		fo := 0
		fo, err = utils.HourToStartOffset(finish.Hour())
		if err != nil {
			return
		}
		if fOffset < fo {
			fOffset = fo
		}
	}

	return
}

// calculateScheduleRows takes a schedule and determines which rows should be displayed.
func calculateScheduleRows(items []structs.ScheduleItem) ([]WeekScheduleRow, error) {
	// Internally, we use a 24-hour array to store our decisions.
	rows := make([]struct {
		MinuteMarks map[int]bool
		Cull        bool
	}, 24)

	// Now decide which rows to cull by calculating boundaries, then marking the rows outside of the boundaries.
	sOffset, fOffset, err := calculateScheduleBoundaries(items)
	if err != nil {
		return nil, err
	}
	if 23 < sOffset || sOffset < 0 || 23 < fOffset || fOffset < 0 || fOffset < sOffset {
		return nil, fmt.Errorf("calculateScheduleRows: row boundaries %d to %d are invalid", sOffset, fOffset)
	}

	// Go through each hour, culling ones before the boundaries, and adding on-the-hour minute marks to the others.
	// Boundaries are inclusive, so cull only things outside of them.
	for i := 0; i < 24; i++ {
		ri, err := utils.StartOffsetToHour(i)
		if err != nil {
			return nil, err
		}
		if i < sOffset || fOffset < i {
			rows[ri].Cull = true
		} else {
			rows[ri].MinuteMarks = map[int]bool{0: true}
		}
	}
	// Calculate the minute marks from non-on-the-hour show starts now.
	for _, item := range items {
		h := item.GetStart().Hour()
		if !rows[h].Cull {
			rows[item.GetStart().Hour()].MinuteMarks[item.GetStart().Minute()] = true
		}
	}

	// Now translate the above into a row table.
	wsrs := []WeekScheduleRow{}
	for i := 0; i < 24; i++ {
		ri, err := utils.StartOffsetToHour(i)
		if err != nil {
			return nil, err
		}

		if rows[ri].Cull {
			continue
		}

		minutes := make([]int, len(rows[ri].MinuteMarks))
		j := 0
		for k := range rows[ri].MinuteMarks {
			minutes[j] = k
			j++
		}
		sort.Ints(minutes)

		hwsrs := make([]WeekScheduleRow, len(minutes))
		for j, m := range minutes {
			hwsrs[j] = WeekScheduleRow{Hour: ri, Minute: m, Cells: []WeekScheduleCell{}}
		}

		wsrs = append(wsrs, hwsrs...)
	}

	return wsrs, nil
}

// populateRows fills schedule rows with timeslots.
// It takes the list of schedule start times on the days the schedule spans,
// the slice of rows to populate, and the schedule items to add.
func populateRows(days []time.Time, rows []WeekScheduleRow, items []structs.ScheduleItem) {
	currentItem := 0

	for d, day := range days {
		// We use this to find out when we've gone over midnight
		lastHour := -1
		// And this to find out where the current show started
		thisShowIndex := -1

		// Now, go through all the rows for this day.
		// We have to be careful to make sure we tick over day if we go past midnight.
		for i := range rows {
			if rows[i].Hour < lastHour {
				day = day.AddDate(0, 0, 1)
			}
			lastHour = rows[i].Hour

			rowTime := time.Date(day.Year(), day.Month(), day.Day(), rows[i].Hour, rows[i].Minute, 0, 0, time.Local)

			// Seek forwards if the current show has finished.
			for !items[currentItem].GetFinish().After(rowTime) {
				currentItem++
				thisShowIndex = -1
			}

			// If this is not the first time we've seen this slot, update its rowspan
			// and put in a placeholder.
			if thisShowIndex != -1 {
				rows[thisShowIndex].Cells[d].RowSpan++
				rows[i].Cells = append(rows[i].Cells, WeekScheduleCell{RowSpan: 0, Item: nil})
			} else {
				thisShowIndex = i
				rows[i].Cells = append(rows[i].Cells, WeekScheduleCell{RowSpan: 1, Item: &(items[currentItem])})
			}
		}
	}
}

// WeekSchedule is the type of week schedules.
type WeekSchedule struct {
	// Dates enumerates the dates this week schedule covers.
	Dates []time.Time
	// Table is the actual week table.
	// If there is no schedule for the given week, this will be nil.
	Table []WeekScheduleRow
}

// hasShows asks whether a schedule slice contains any non-sustainer shows.
// It assumes the slice has been filled with sustainer.
func hasShows(schedule []structs.ScheduleItem) bool {
	// This shouldn't happen, but if it does, this is the right thing to
	// do.
	if len(schedule) == 0 {
		return false
	}

	// We know that, if a slice is filled but has no non-sustainer, then
	// the slice will contain only one sustainer item.  So, eliminate the
	// other cases.
	if 1 < len(schedule) || !schedule[0].IsSustainer() {
		return true
	}

	return false
}

// generateWeekSchedule creates a schedule table from the given schedule slice.
func generateWeekSchedule(start, finish time.Time, schedule []structs.ScheduleItem) (*WeekSchedule, error) {
	days := []time.Time{}
	for d := start; d.Before(finish); d = d.AddDate(0, 0, 1) {
		days = append(days, d)
	}

	if !hasShows(schedule) {
		return &WeekSchedule{
			Dates: days,
			Table: nil,
		}, nil
	}

	table, err := calculateScheduleRows(schedule)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	populateRows(days, table, schedule)

	return &WeekSchedule{
		Dates: days,
		Table: table,
	}, nil
}

//
// Controller
//

// ScheduleWeekController is the controller for looking up week schedules.
type ScheduleWeekController struct {
	Controller

	timeslotURLBuilder func(*myradio.Timeslot) (*url.URL, error)
}

// NewScheduleWeekController returns a new ScheduleWeekController with the MyRadio session s,
// router r, and configuration context c.
func NewScheduleWeekController(s *myradio.Session, r *mux.Router, c *structs.Config) *ScheduleWeekController {
	// We pass in the router so we can generate URL reversal functions.
	// Eventually we might want to clean this up, either by passing in
	// something more loosely coupled or handling this at a higher level.
	troute := r.Get("timeslot")
	tbuilder := func(t *myradio.Timeslot) (*url.URL, error) {
		return troute.URLPath("id", strconv.FormatUint(t.TimeslotID, 10))
	}

	return &ScheduleWeekController{
		Controller:         Controller{session: s, config: c},
		timeslotURLBuilder: tbuilder,
	}
}

// GetByYearWeek handles the HTTP GET request r for week schedules by year/week date reference, writing to w.
//
// It takes two request variables--year and week--, which correspond to an ISO 8601 year-week date.
func (sc *ScheduleWeekController) GetByYearWeek(w http.ResponseWriter, r *http.Request) {
	sm := models.NewScheduleWeekModel(sc.session)

	vars := mux.Vars(r)

	year, week, err := weekFromVars(vars)
	if err != nil {
		log.Println(err)
		return
	}

	yr, wk, dy, err := utils.ParseIsoWeek(year, week, "1")
	if err != nil {
		log.Println(err)
		return
	}

	startDate, err := utils.IsoWeekToDate(yr, wk, dy)
	if err != nil {
		log.Println(err)
		return
	}
	finishDate := startDate.AddDate(0, 0, 7)

	log.Printf("getting year %d week %d\n", yr, wk)
	timeslots, err := sm.Get(yr, wk)
	if err != nil {
		//@TODO: Do something proper here, render 404 or something
		log.Println(err)
		return
	}

	// Flatten the timeslots into one stream
	flat := []myradio.Timeslot{}
	for d := 1; d <= 7; d++ {
		flat = append(flat, timeslots[d]...)
	}

	// Now start filling from day start to day finish.
	weekStart := utils.StartOfDayOn(startDate)
	weekFinish := utils.StartOfDayOn(finishDate)
	tbuilder := func(t *myradio.Timeslot) (*structs.TimeslotItem, error) {
		ts, err := structs.NewTimeslotItem(t, sc.timeslotURLBuilder)
		if err == nil && ts == nil {
			return nil, errors.New("NewTimeslotItem created nil timeslot item")
		}
		return ts, err
	}
	filled, err := structs.FillTimeslotSlice(weekStart, weekFinish, flat, tbuilder)
	if err != nil {
		log.Println(err)
		return
	}

	data, err := generateWeekSchedule(weekStart, weekFinish, filled)
	if err != nil {
		log.Println(err)
		return
	}

	err = utils.RenderTemplate(w, sc.config.PageContext, data, "schedule_week.tmpl")
	if err != nil {
		log.Println(err)
		return
	}
}
<commit_msg>Refactor the week schedule tabulator<commit_after>package controllers

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"

	"github.com/UniversityRadioYork/2016-site/models"
	"github.com/UniversityRadioYork/2016-site/structs"
	"github.com/UniversityRadioYork/2016-site/utils"
	"github.com/UniversityRadioYork/myradio-go"
	"github.com/gorilla/mux"
)

// weekFromVars extracts the year, and week strings from vars.
func weekFromVars(vars map[string]string) (string, string, error) {
	y, ok := vars["year"]
	if !ok {
		return "", "", errors.New("no year provided")
	}
	w, ok := vars["week"]
	if !ok {
		return "", "", errors.New("no week provided")
	}

	return y, w, nil
}

// weekdayFromVars extracts the year, week, and weekday strings from vars.
func weekdayFromVars(vars map[string]string) (string, string, string, error) {
	y, ok := vars["year"]
	if !ok {
		return "", "", "", errors.New("no year provided")
	}
	w, ok := vars["week"]
	if !ok {
		return "", "", "", errors.New("no week provided")
	}
	d, ok := vars["weekday"]
	if !ok {
		return "", "", "", errors.New("no weekday provided")
	}

	return y, w, d, nil
}

//
// Week schedule algorithm
// TODO(CaptainHayashi): move?
//

// WeekScheduleCell represents one cell in the week schedule.
type WeekScheduleCell struct {
	// Number of rows this cell spans.
	// If 0, this is a continuation from a cell further up.
	RowSpan uint

	// Pointer to the timeslot in this cell, if any.
	// Will be nil if 'RowSpan' is 0.
	Item *structs.ScheduleItem
}

// WeekScheduleRow represents one row in the week schedule.
type WeekScheduleRow struct {
	// The hour of the row (0..23).
	Hour int
	// The minute of the show (0..59).
	Minute int
	// The cells inside this row.
	Cells []WeekScheduleCell
}

// addCell adds a cell with rowspan s and item i to the row r.
func (r *WeekScheduleRow) addCell(s uint, i *structs.ScheduleItem) {
	r.Cells = append(r.Cells, WeekScheduleCell{RowSpan: s, Item: i})
}

// showStraddlesDay checks whether a show's start and finish cross over the boundary of a URY day.
func showStraddlesDay(start, finish time.Time) bool {
	nextDayStart := utils.StartOfDayOn(start.AddDate(0, 0, 1))
	return finish.After(nextDayStart)
}

// calculateScheduleBoundaries works out the earliest and latest hours in the schedule that need to display.
// It returns these as a pair of start and finish bound, both in terms of offsets from URY start time.
func calculateScheduleBoundaries(items []structs.ScheduleItem) (sOffset, fOffset int, err error) {
	if len(items) == 0 {
		err = errors.New("calculateScheduleBoundaries: no schedule")
		return
	}

	// These are the boundaries for culling, and are expanded upwards when we find shows that start earlier or finish later than the last-set boundary.
	// Initially they are set to one past their worst case to make the updating logic easier.
	// Since we assert we have a schedule, these values _will_ change.
	sOffset = 24
	fOffset = -1

	for _, s := range items {
		start := s.GetStart()
		finish := s.GetFinish()

		// Any show that isn't a sustainer affects the culling boundaries.
		if s.IsSustainer() {
			continue
		}

		if showStraddlesDay(start, finish) {
			// A show that straddles the day crosses over from the end of a day to the start of the day.
			// This means that we saturate the culling boundaries.
			// As an optimisation we don't need to consider any other show.
			sOffset = 0
			fOffset = 23
			return
		}

		// Otherwise, if its start/finish as offsets from start time are outside the current boundaries, update them.
		so := 0
		so, err = utils.HourToStartOffset(start.Hour())
		if err != nil {
			return
		}
		if so < sOffset {
			sOffset = so
		}

		fo := 0
		fo, err = utils.HourToStartOffset(finish.Hour())
		if err != nil {
			return
		}
		if fOffset < fo {
			fOffset = fo
		}
	}

	return
}

// calculateScheduleRows takes a schedule and determines which rows should be displayed.
func calculateScheduleRows(items []structs.ScheduleItem) ([]WeekScheduleRow, error) {
	// Internally, we use a 24-hour array to store our decisions.
	rows := make([]struct {
		MinuteMarks map[int]bool
		Cull        bool
	}, 24)

	// Now decide which rows to cull by calculating boundaries, then marking the rows outside of the boundaries.
	sOffset, fOffset, err := calculateScheduleBoundaries(items)
	if err != nil {
		return nil, err
	}
	if 23 < sOffset || sOffset < 0 || 23 < fOffset || fOffset < 0 || fOffset < sOffset {
		return nil, fmt.Errorf("calculateScheduleRows: row boundaries %d to %d are invalid", sOffset, fOffset)
	}

	// Go through each hour, culling ones before the boundaries, and adding on-the-hour minute marks to the others.
	// Boundaries are inclusive, so cull only things outside of them.
	for i := 0; i < 24; i++ {
		ri, err := utils.StartOffsetToHour(i)
		if err != nil {
			return nil, err
		}
		if i < sOffset || fOffset < i {
			rows[ri].Cull = true
		} else {
			rows[ri].MinuteMarks = map[int]bool{0: true}
		}
	}
	// Calculate the minute marks from non-on-the-hour show starts now.
	for _, item := range items {
		h := item.GetStart().Hour()
		if !rows[h].Cull {
			rows[item.GetStart().Hour()].MinuteMarks[item.GetStart().Minute()] = true
		}
	}

	// Now translate the above into a row table.
	wsrs := []WeekScheduleRow{}
	for i := 0; i < 24; i++ {
		ri, err := utils.StartOffsetToHour(i)
		if err != nil {
			return nil, err
		}

		if rows[ri].Cull {
			continue
		}

		minutes := make([]int, len(rows[ri].MinuteMarks))
		j := 0
		for k := range rows[ri].MinuteMarks {
			minutes[j] = k
			j++
		}
		sort.Ints(minutes)

		hwsrs := make([]WeekScheduleRow, len(minutes))
		for j, m := range minutes {
			hwsrs[j] = WeekScheduleRow{Hour: ri, Minute: m, Cells: []WeekScheduleCell{}}
		}

		wsrs = append(wsrs, hwsrs...)
	}

	return wsrs, nil
}

// populateRows fills schedule rows with timeslots.
// It takes the list of schedule start times on the days the schedule spans,
// the slice of rows to populate, and the schedule items to add.
func populateRows(days []time.Time, rows []WeekScheduleRow, items []structs.ScheduleItem) {
	currentItem := 0

	for d, day := range days {
		// We use this to find out when we've gone over midnight
		lastHour := -1
		// And this to find out where the current show started
		thisShowIndex := -1

		// Now, go through all the rows for this day.
		// We have to be careful to make sure we tick over day if we go past midnight.
		for i := range rows {
			if rows[i].Hour < lastHour {
				day = day.AddDate(0, 0, 1)
			}
			lastHour = rows[i].Hour

			rowTime := time.Date(day.Year(), day.Month(), day.Day(), rows[i].Hour, rows[i].Minute, 0, 0, time.Local)

			// Seek forwards if the current show has finished.
			for !items[currentItem].GetFinish().After(rowTime) {
				currentItem++
				thisShowIndex = -1
			}

			// If this is not the first time we've seen this slot,
			// update the rowspan in the first instance's cell and
			// put in a placeholder.
			if thisShowIndex != -1 {
				rows[thisShowIndex].Cells[d].RowSpan++
				rows[i].addCell(0, nil)
			} else {
				thisShowIndex = i
				rows[i].addCell(1, &(items[currentItem]))
			}
		}
	}
}

// WeekSchedule is the type of week schedules.
type WeekSchedule struct {
	// Dates enumerates the dates this week schedule covers.
	Dates []time.Time
	// Table is the actual week table.
	// If there is no schedule for the given week, this will be nil.
	Table []WeekScheduleRow
}

// hasShows asks whether a schedule slice contains any non-sustainer shows.
// It assumes the slice has been filled with sustainer.
func hasShows(schedule []structs.ScheduleItem) bool {
	// This shouldn't happen, but if it does, this is the right thing to
	// do.
	if len(schedule) == 0 {
		return false
	}

	// We know that, if a slice is filled but has no non-sustainer, then
	// the slice will contain only one sustainer item.  So, eliminate the
	// other cases.
	if 1 < len(schedule) || !schedule[0].IsSustainer() {
		return true
	}

	return false
}

// tabulateWeekSchedule creates a schedule table from the given schedule slice.
func tabulateWeekSchedule(start, finish time.Time, schedule []structs.ScheduleItem) (*WeekSchedule, error) {
	days := []time.Time{}
	for d := start; d.Before(finish); d = d.AddDate(0, 0, 1) {
		days = append(days, d)
	}

	if !hasShows(schedule) {
		return &WeekSchedule{
			Dates: days,
			Table: nil,
		}, nil
	}

	table, err := calculateScheduleRows(schedule)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	populateRows(days, table, schedule)

	return &WeekSchedule{
		Dates: days,
		Table: table,
	}, nil
}

//
// Controller
//

// ScheduleWeekController is the controller for looking up week schedules.
type ScheduleWeekController struct {
	Controller

	timeslotURLBuilder func(*myradio.Timeslot) (*url.URL, error)
}

// NewScheduleWeekController returns a new ScheduleWeekController with the MyRadio session s,
// router r, and configuration context c.
func NewScheduleWeekController(s *myradio.Session, r *mux.Router, c *structs.Config) *ScheduleWeekController {
	// We pass in the router so we can generate URL reversal functions.
	// Eventually we might want to clean this up, either by passing in
	// something more loosely coupled or handling this at a higher level.
	troute := r.Get("timeslot")
	tbuilder := func(t *myradio.Timeslot) (*url.URL, error) {
		return troute.URLPath("id", strconv.FormatUint(t.TimeslotID, 10))
	}

	return &ScheduleWeekController{
		Controller:         Controller{session: s, config: c},
		timeslotURLBuilder: tbuilder,
	}
}

// makeTimeslotItem creates a TimeslotItem for a given MyRadio timeslot.
func (sc *ScheduleWeekController) makeTimeslotItem(t *myradio.Timeslot) (*structs.TimeslotItem, error) {
	ts, err := structs.NewTimeslotItem(t, sc.timeslotURLBuilder)
	if err == nil && ts == nil {
		return nil, errors.New("NewTimeslotItem created nil timeslot item")
	}
	return ts, err
}

// makeWeekSchedule gets the week schedule for a given ISO year and week.
func (sc *ScheduleWeekController) makeWeekSchedule(yr, wk int) (*WeekSchedule, error) {
	startDate, err := utils.IsoWeekToDate(yr, wk, time.Monday)
	if err != nil {
		return nil, err
	}
	finishDate := startDate.AddDate(0, 0, 7)

	sm := models.NewScheduleWeekModel(sc.session)
	timeslots, err := sm.Get(yr, wk)
	if err != nil {
		return nil, err
	}

	// Flatten the timeslots into one stream
	flat := []myradio.Timeslot{}
	for d := 1; d <= 7; d++ {
		flat = append(flat, timeslots[d]...)
	}

	// Now start filling from day start to day finish.
	weekStart := utils.StartOfDayOn(startDate)
	weekFinish := utils.StartOfDayOn(finishDate)
	filled, err := structs.FillTimeslotSlice(weekStart, weekFinish, flat, sc.makeTimeslotItem)
	if err != nil {
		return nil, err
	}

	return tabulateWeekSchedule(weekStart, weekFinish, filled)
}

// GetByYearWeek handles the HTTP GET request r for week schedules by year/week date reference, writing to w.
//
// It takes two request variables--year and week--, which correspond to an ISO 8601 year-week date.
func (sc *ScheduleWeekController) GetByYearWeek(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	year, week, err := weekFromVars(vars)
	if err != nil {
		log.Println(err)
		return
	}

	yr, wk, _, err := utils.ParseIsoWeek(year, week, "1")
	if err != nil {
		log.Println(err)
		return
	}

	data, err := sc.makeWeekSchedule(yr, wk)
	if err != nil {
		//@TODO: Do something proper here, render 404 or something
		log.Println(err)
		return
	}

	err = utils.RenderTemplate(w, sc.config.PageContext, data, "schedule_week.tmpl")
	if err != nil {
		log.Println(err)
		return
	}
}
<|endoftext|>


<commit_before><commit_msg>言語を英語か日本語に絞る<commit_after><|endoftext|>


<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"fmt"

	"launchpad.net/juju-core/charm"
)

// counterpartRole returns the RelationRole that this RelationRole
// can relate to.
// This should remain an internal method because the relation
// model does not guarantee that for every role there will
// necessarily exist a single counterpart role that is sensible
// for basing algorithms upon.
func counterpartRole(r charm.RelationRole) charm.RelationRole {
	switch r {
	case charm.RoleProvider:
		return charm.RoleRequirer
	case charm.RoleRequirer:
		return charm.RoleProvider
	case charm.RolePeer:
		return charm.RolePeer
	}
	panic(fmt.Errorf("unknown relation role %q", r))
}

// Endpoint represents one endpoint of a relation.
type Endpoint struct {
	ServiceName string
	charm.Relation
}

// String returns the unique identifier of the relation endpoint.
func (ep Endpoint) String() string {
	return ep.ServiceName + ":" + ep.Name
}

// CanRelateTo returns whether a relation may be established between e and other.
func (ep Endpoint) CanRelateTo(other Endpoint) bool {
	return ep.ServiceName != other.ServiceName &&
		ep.Interface == other.Interface &&
		ep.Role != charm.RolePeer &&
		counterpartRole(ep.Role) == other.Role
}

// ImplementedBy returns whether the endpoint is implemented by the supplied charm.
func (ep Endpoint) ImplementedBy(ch charm.Charm) bool {
	if ep.IsImplicit() {
		return true
	}
	var m map[string]charm.Relation
	switch ep.Role {
	case charm.RoleProvider:
		m = ch.Meta().Provides
	case charm.RoleRequirer:
		m = ch.Meta().Requires
	case charm.RolePeer:
		m = ch.Meta().Peers
	default:
		panic(fmt.Errorf("unknown relation role %q", ep.Role))
	}
	rel, found := m[ep.Name]
	if !found {
		return false
	}
	if rel.Interface == ep.Interface {
		switch ep.Scope {
		case charm.ScopeGlobal:
			return rel.Scope != charm.ScopeContainer
		case charm.ScopeContainer:
			return true
		default:
			panic(fmt.Errorf("unknown relation scope %q", ep.Scope))
		}
	}
	return false
}

// IsImplicit returns whether the endpoint is supplied by juju itself,
// rather than by a charm.
func (ep Endpoint) IsImplicit() bool {
	return (ep.Name == "juju-info" &&
		ep.Interface == "juju-info" &&
		ep.Role == charm.RoleProvider)
}

type epSlice []Endpoint

var roleOrder = map[charm.RelationRole]int{
	charm.RoleRequirer: 0,
	charm.RoleProvider: 1,
	charm.RolePeer:     2,
}

func (eps epSlice) Len() int      { return len(eps) }
func (eps epSlice) Swap(i, j int) { eps[i], eps[j] = eps[j], eps[i] }
func (eps epSlice) Less(i, j int) bool {
	ep1 := eps[i]
	ep2 := eps[j]
	if ep1.Role != ep2.Role {
		return roleOrder[ep1.Role] < roleOrder[ep2.Role]
	}
	return ep1.String() < ep2.String()
}
<commit_msg>Missed that change<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"fmt"

	"launchpad.net/juju-core/charm"
)

// counterpartRole returns the RelationRole that this RelationRole
// can relate to.
// This should remain an internal method because the relation
// model does not guarantee that for every role there will
// necessarily exist a single counterpart role that is sensible
// for basing algorithms upon.
func counterpartRole(r charm.RelationRole) charm.RelationRole {
	switch r {
	case charm.RoleProvider:
		return charm.RoleRequirer
	case charm.RoleRequirer:
		return charm.RoleProvider
	case charm.RolePeer:
		return charm.RolePeer
	}
	panic(fmt.Errorf("unknown relation role %q", r))
}

// Endpoint represents one endpoint of a relation.
type Endpoint struct {
	ServiceName string
	charm.Relation
}

// String returns the unique identifier of the relation endpoint.
func (ep Endpoint) String() string {
	return ep.ServiceName + ":" + ep.Name
}

// CanRelateTo returns whether a relation may be established between e and other.
func (ep Endpoint) CanRelateTo(other Endpoint) bool {
	return ep.ServiceName != other.ServiceName &&
		ep.Interface == other.Interface &&
		ep.Role != charm.RolePeer &&
		counterpartRole(ep.Role) == other.Role
}

type epSlice []Endpoint

var roleOrder = map[charm.RelationRole]int{
	charm.RoleRequirer: 0,
	charm.RoleProvider: 1,
	charm.RolePeer:     2,
}

func (eps epSlice) Len() int      { return len(eps) }
func (eps epSlice) Swap(i, j int) { eps[i], eps[j] = eps[j], eps[i] }
func (eps epSlice) Less(i, j int) bool {
	ep1 := eps[i]
	ep2 := eps[j]
	if ep1.Role != ep2.Role {
		return roleOrder[ep1.Role] < roleOrder[ep2.Role]
	}
	return ep1.String() < ep2.String()
}
<|endoftext|>


<commit_before>package steps

import (
	"os"

	templateapi "github.com/openshift/origin/pkg/template/api"

	"github.com/stretchr/testify/assert"
)

// registers all template related steps
func init() {
	RegisterSteps(func(c *Context) {

		c.When(`^I create a new template for the file "(.+?)"$`, func(templateFileName string) {
			expandedTemplateFileName := os.ExpandEnv(templateFileName)
			if expandedTemplateFileName == "" {
				c.Fail("Template file name '%s' (expanded to '%s') is empty !", templateFileName, expandedTemplateFileName)
				return
			}
			if _, err := os.Stat(expandedTemplateFileName); err != nil {
				c.Fail("Template file '%s' (expanded to '%s') does not exists: %v", templateFileName, expandedTemplateFileName, err)
				return
			}

			r, err := c.ParseResource(expandedTemplateFileName)
			if err != nil {
				c.Fail("Failed to parse template file '%s' (expanded to '%s'): %v", templateFileName, expandedTemplateFileName, err)
				return
			}

			err = r.Visit(CreateResource)
			if err != nil {
				c.Fail("Failed to create template for file '%s' (expanded to '%s'): %v", templateFileName, expandedTemplateFileName, err)
				return
			}
		})

		c.Then(`^I should have a template "(.+?)"$`, func(templateName string) {
			template, err := c.GetTemplate(templateName)
			if err != nil {
				c.Fail("Failed to get Template '%s': %v", templateName, err)
				return
			}

			assert.Equal(c.T, templateName, template.Name)
		})

		c.Then(`^I should have a template "(.+?)" with (\d+) objects and (\d+) parameters$`, func(templateName string, expectedObjects int, expectedParameters int) {
			template, err := c.GetTemplate(templateName)
			if err != nil {
				c.Fail("Failed to get Template '%s': %v", templateName, err)
				return
			}

			assert.Equal(c.T, templateName, template.Name)
			assert.Equal(c.T, expectedObjects, len(template.Objects), "Template %s has %d objects, but expected number is %d !", template.Name, len(template.Objects), expectedObjects)
			assert.Equal(c.T, expectedParameters, len(template.Parameters), "Template %s has %d parameters, but expected number is %d !", template.Name, len(template.Parameters), expectedParameters)
		})

		c.Given(`^I have a template "(.+?)"$`, func(templateName string) {
			if _, err := c.GetTemplate(templateName); err != nil {
				c.Fail("Template '%s' does not exists: %v", templateName, err)
			}
		})

	})
}

// GetTemplate gets the Template with the given name, or returns an error
func (c *Context) GetTemplate(templateName string) (*templateapi.Template, error) {
	client, _, err := c.Clients()
	if err != nil {
		return nil, err
	}

	namespace, err := c.Namespace()
	if err != nil {
		return nil, err
	}

	template, err := client.Templates(namespace).Get(templateName)
	if err != nil {
		return nil, err
	}

	return template, nil
}
<commit_msg>new template step<commit_after>package steps

import (
	"os"

	templateapi "github.com/openshift/origin/pkg/template/api"

	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"

	"github.com/stretchr/testify/assert"
)

// registers all template related steps
func init() {
	RegisterSteps(func(c *Context) {

		c.When(`^I create a new template for the file "(.+?)"$`, func(templateFileName string) {
			expandedTemplateFileName := os.ExpandEnv(templateFileName)
			if expandedTemplateFileName == "" {
				c.Fail("Template file name '%s' (expanded to '%s') is empty !", templateFileName, expandedTemplateFileName)
				return
			}
			if _, err := os.Stat(expandedTemplateFileName); err != nil {
				c.Fail("Template file '%s' (expanded to '%s') does not exists: %v", templateFileName, expandedTemplateFileName, err)
				return
			}

			r, err := c.ParseResource(expandedTemplateFileName)
			if err != nil {
				c.Fail("Failed to parse template file '%s' (expanded to '%s'): %v", templateFileName, expandedTemplateFileName, err)
				return
			}

			err = r.Visit(CreateResource)
			if err != nil {
				c.Fail("Failed to create template for file '%s' (expanded to '%s'): %v", templateFileName, expandedTemplateFileName, err)
				return
			}
		})

		c.Then(`^I should have a template "(.+?)"$`, func(templateName string) {
			template, err := c.GetTemplate(templateName)
			if err != nil {
				c.Fail("Failed to get Template '%s': %v", templateName, err)
				return
			}

			assert.Equal(c.T, templateName, template.Name)
		})

		c.Then(`^I should have a template "(.+?)" with (\d+) objects and (\d+) parameters$`, func(templateName string, expectedObjects int, expectedParameters int) {
			template, err := c.GetTemplate(templateName)
			if err != nil {
				c.Fail("Failed to get Template '%s': %v", templateName, err)
				return
			}

			assert.Equal(c.T, templateName, template.Name)
			assert.Equal(c.T, expectedObjects, len(template.Objects), "Template %s has %d objects, but expected number is %d !", template.Name, len(template.Objects), expectedObjects)
			assert.Equal(c.T, expectedParameters, len(template.Parameters), "Template %s has %d parameters, but expected number is %d !", template.Name, len(template.Parameters), expectedParameters)
		})

		c.Then(`^I should not have a template "(.+?)"$`, func(templateName string) {
			found, err := c.TemplateExists(templateName)
			if err != nil {
				c.Fail("Failed to check for template '%s' existance: %v", templateName, err)
				return
			}

			if found {
				c.Fail("Template %s should not exists", templateName)
				return
			}
		})

		c.Given(`^I have a template "(.+?)"$`, func(templateName string) {
			if _, err := c.GetTemplate(templateName); err != nil {
				c.Fail("Template '%s' does not exists: %v", templateName, err)
			}
		})

	})
}

// TemplateExists checks if a template with the given name exists.
func (c *Context) TemplateExists(tmplName string) (bool, error) {
	client, _, err := c.Clients()
	if err != nil {
		return false, err
	}

	namespace, err := c.Namespace()
	if err != nil {
		return false, err
	}

	tmplList, err := client.Templates(namespace).List(labels.Everything(), fields.Everything())
	if err != nil {
		return false, err
	}

	for _, t := range tmplList.Items {
		if t.Name == tmplName {
			return true, nil
		}
	}
	return false, nil
}

// GetTemplate gets the Template with the given name, or returns an error
func (c *Context) GetTemplate(templateName string) (*templateapi.Template, error) {
	client, _, err := c.Clients()
	if err != nil {
		return nil, err
	}

	namespace, err := c.Namespace()
	if err != nil {
		return nil, err
	}

	template, err := client.Templates(namespace).Get(templateName)
	if err != nil {
		return nil, err
	}

	return template, nil
}
<|endoftext|>


<commit_before>package stopwatch

import (
	"fmt"
	"sync"
	"testing"
)

func TestLaps(t *testing.T) {
	sw := New(0, true)

	sw.Lap("Session Create")
	sw.Lap("Delete File")
	sw.LapWithData("Close DB", map[string]interface{}{
		"row_count": 2,
	})

	if len(sw.Laps()) != 3 {
		t.Fatalf("Created 3 laps but found %d laps.", len(sw.Laps()))
	}

	expected := []string{"Session Create", "Delete File", "Close DB"}

	laps := sw.Laps()

	for i, state := range expected {
		if state != laps[i].state {
			t.Fatalf("Lap %d did not contain expected state: %s", i, state)
		}
	}

	// check additional bag data
	lapWithData := laps[2]
	if lapWithData.data["row_count"] != 2 {
		t.Fatalf("Missing data bag with row_count of 2")
	}
}

func TestReset(t *testing.T) {
	sw := New(0, true)

	sw.Lap("Session Create")

	expected := []string{"Session Create"}

	laps := sw.Laps()

	for i, state := range expected {
		if state != laps[i].state {
			t.Fatalf("Lap %d did not contain expected state: %s", i, state)
		}
	}

	sw.Reset(0, true)

	sw.Lap("Another Session Create")

	expected = []string{"Another Session Create"}

	laps = sw.Laps()

	for i, state := range expected {
		if state != laps[i].state {
			t.Fatalf("Lap %d did not contain expected state: %s", i, state)
		}
	}
}

func TestMultiThreadLaps(t *testing.T) {
	// Create a new StopWatch that starts off counting
	sw := New(0, true)

	// Optionally, format that time.Duration how you need it
	//	sw.SetFormatter(func(duration time.Duration) string {
	//		return fmt.Sprintf("%.1f", duration.Seconds())
	//	})

	// Take measurement of various states
	sw.Lap("Create File")

	var wg sync.WaitGroup

	wg.Add(2)
	go func() {
		defer wg.Done()
		for i := 0; i < 2; i++ {
			task := fmt.Sprintf("task %d", i)
			sw.Lap(task)
		}
	}()

	go func() {
		defer wg.Done()
		task := "task A"
		sw.LapWithData(task, map[string]interface{}{
			"filename": "word.doc",
		})
	}()

	// Simulate some time by sleeping
	sw.Lap("Upload File")

	// Stop the timer
	wg.Wait()
	sw.Stop()

	expected := map[string]struct{}{
		"Create File": struct{}{},
		"task 0":      struct{}{},
		"task 1":      struct{}{},
		"Upload File": struct{}{},
		"task A":      struct{}{},
	}

	laps := sw.Laps()

	if len(laps) != len(expected) {
		t.Fatalf("Did not get the expected number of lap %d, instead got %d",
			len(expected), len(laps))
	}

	for i, l := range laps {
		if _, found := expected[l.state]; !found {
			t.Fatalf("Lap %d: got state: %s expected state: %s", i, laps[i].state, l.state)
		}
	}
}

func TestPrintLaps(t *testing.T) {
	sw := New(0, true)
	sw.Lap("lap1")
	sw.Lap("lap2")
	laps := sw.Laps()
	go laps[0].String()
	go laps[1].String()
}

func TestLapTime(t *testing.T) {
	sw := New(0, true)
	sw.Start()
	laptime1 := sw.LapTime()
	laptime2 := sw.LapTime()
	if diff := laptime2.Nanoseconds() - laptime1.Nanoseconds(); diff <= 0 {
		t.Errorf("LapTime difference should be greater than zero")
	}
}

func TestInactiveStart(t *testing.T) {
	sw := New(0, false)
	sw.Start()
	sw.Lap("running lap")
	sw.Stop()
	sw.Lap("stopped lap")
	if laps := sw.Laps(); len(laps) != 2 {
		t.Errorf("Should capture laps even after Stop()")
	}
	sw.Reset(0, false)
	if laps := sw.Laps(); len(laps) != 0 {
		t.Errorf("After Reset(), Laps() should be empty")
	}
}
<commit_msg>parallelize all the tests<commit_after>package stopwatch

import (
	"fmt"
	"sync"
	"testing"
)

func TestLaps(t *testing.T) {
	t.Parallel()
	sw := New(0, true)

	sw.Lap("Session Create")
	sw.Lap("Delete File")
	sw.LapWithData("Close DB", map[string]interface{}{
		"row_count": 2,
	})

	if len(sw.Laps()) != 3 {
		t.Fatalf("Created 3 laps but found %d laps.", len(sw.Laps()))
	}

	expected := []string{"Session Create", "Delete File", "Close DB"}

	laps := sw.Laps()

	for i, state := range expected {
		if state != laps[i].state {
			t.Fatalf("Lap %d did not contain expected state: %s", i, state)
		}
	}

	// check additional bag data
	lapWithData := laps[2]
	if lapWithData.data["row_count"] != 2 {
		t.Fatalf("Missing data bag with row_count of 2")
	}
}

func TestReset(t *testing.T) {
	t.Parallel()
	sw := New(0, true)

	sw.Lap("Session Create")

	expected := []string{"Session Create"}

	laps := sw.Laps()

	for i, state := range expected {
		if state != laps[i].state {
			t.Fatalf("Lap %d did not contain expected state: %s", i, state)
		}
	}

	sw.Reset(0, true)

	sw.Lap("Another Session Create")

	expected = []string{"Another Session Create"}

	laps = sw.Laps()

	for i, state := range expected {
		if state != laps[i].state {
			t.Fatalf("Lap %d did not contain expected state: %s", i, state)
		}
	}
}

func TestMultiThreadLaps(t *testing.T) {
	t.Parallel()
	// Create a new StopWatch that starts off counting
	sw := New(0, true)

	// Optionally, format that time.Duration how you need it
	//	sw.SetFormatter(func(duration time.Duration) string {
	//		return fmt.Sprintf("%.1f", duration.Seconds())
	//	})

	// Take measurement of various states
	sw.Lap("Create File")

	var wg sync.WaitGroup

	wg.Add(2)
	go func() {
		defer wg.Done()
		for i := 0; i < 2; i++ {
			task := fmt.Sprintf("task %d", i)
			sw.Lap(task)
		}
	}()

	go func() {
		defer wg.Done()
		task := "task A"
		sw.LapWithData(task, map[string]interface{}{
			"filename": "word.doc",
		})
	}()

	// Simulate some time by sleeping
	sw.Lap("Upload File")

	// Stop the timer
	wg.Wait()
	sw.Stop()

	expected := map[string]struct{}{
		"Create File": struct{}{},
		"task 0":      struct{}{},
		"task 1":      struct{}{},
		"Upload File": struct{}{},
		"task A":      struct{}{},
	}

	laps := sw.Laps()

	if len(laps) != len(expected) {
		t.Fatalf("Did not get the expected number of lap %d, instead got %d",
			len(expected), len(laps))
	}

	for i, l := range laps {
		if _, found := expected[l.state]; !found {
			t.Fatalf("Lap %d: got state: %s expected state: %s", i, laps[i].state, l.state)
		}
	}
}

func TestPrintLaps(t *testing.T) {
	t.Parallel()
	sw := New(0, true)
	sw.Lap("lap1")
	sw.Lap("lap2")
	laps := sw.Laps()
	go laps[0].String()
	go laps[1].String()
}

func TestLapTime(t *testing.T) {
	t.Parallel()
	sw := New(0, true)
	sw.Start()
	laptime1 := sw.LapTime()
	laptime2 := sw.LapTime()
	if diff := laptime2.Nanoseconds() - laptime1.Nanoseconds(); diff <= 0 {
		t.Errorf("LapTime difference should be greater than zero")
	}
}

func TestInactiveStart(t *testing.T) {
	t.Parallel()
	sw := New(0, false)
	sw.Start()
	sw.Lap("running lap")
	sw.Stop()
	sw.Lap("stopped lap")
	if laps := sw.Laps(); len(laps) != 2 {
		t.Errorf("Should capture laps even after Stop()")
	}
	sw.Reset(0, false)
	if laps := sw.Laps(); len(laps) != 0 {
		t.Errorf("After Reset(), Laps() should be empty")
	}
}
<|endoftext|>


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

package storage

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
	"strings"
	"sync"

	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/coreos/mantle/Godeps/_workspace/src/google.golang.org/api/googleapi"
	"github.com/coreos/mantle/Godeps/_workspace/src/google.golang.org/api/storage/v1"
)

var (
	UnknownScheme = errors.New("storage: URL missing gs:// scheme")
	UnknownBucket = errors.New("storage: URL missing bucket name")
)

type Bucket struct {
	service *storage.Service
	name    string
	prefix  string

	mu       sync.RWMutex
	prefixes map[string]struct{}
	objects  map[string]*storage.Object

	// writeAlways enables overwriting of objects that appear up-to-date
	writeAlways bool
	// writeDryRun blocks any changes, merely logging them instead
	writeDryRun bool
}

func NewBucket(client *http.Client, bucketURL string) (*Bucket, error) {
	service, err := storage.New(client)
	if err != nil {
		return nil, err
	}

	parsedURL, err := url.Parse(bucketURL)
	if err != nil {
		return nil, err
	}
	if parsedURL.Scheme != "gs" {
		return nil, UnknownScheme
	}
	if parsedURL.Host == "" {
		return nil, UnknownBucket
	}

	return &Bucket{
		service:  service,
		name:     parsedURL.Host,
		prefix:   FixPrefix(parsedURL.Path),
		prefixes: make(map[string]struct{}),
		objects:  make(map[string]*storage.Object),
	}, nil
}

func (b *Bucket) Name() string {
	return b.name
}

func (b *Bucket) Prefix() string {
	return b.prefix
}

func (b *Bucket) URL() *url.URL {
	return &url.URL{Scheme: "gs", Host: b.name, Path: b.prefix}
}

func (b *Bucket) WriteAlways(always bool) {
	b.writeAlways = always
}

func (b *Bucket) WriteDryRun(dryrun bool) {
	b.writeDryRun = dryrun
}

func (b *Bucket) Object(objName string) *storage.Object {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return b.objects[objName]
}

func (b *Bucket) Objects() []*storage.Object {
	b.mu.RLock()
	defer b.mu.RUnlock()
	objs := make([]*storage.Object, 0, len(b.objects))
	for _, obj := range b.objects {
		objs = append(objs, obj)
	}
	return objs
}

func (b *Bucket) Prefixes() []string {
	seen := make(map[string]bool)
	list := make([]string, 0)
	add := func(prefix string) {
		for !seen[prefix] {
			seen[prefix] = true
			list = append(list, prefix)
			prefix = NextPrefix(prefix)
		}
	}

	b.mu.RLock()
	defer b.mu.RUnlock()
	for prefix := range b.prefixes {
		add(prefix)
	}
	for objName := range b.objects {
		add(NextPrefix(objName))
	}

	return list
}

func (b *Bucket) Len() int {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return len(b.objects)
}

func (b *Bucket) addObject(obj *storage.Object) {
	if obj.Bucket != b.name {
		panic(fmt.Errorf("adding gs://%s/%s to bucket %s", obj.Bucket, obj.Name, b.name))
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	b.objects[obj.Name] = obj
}

func (b *Bucket) addObjects(objs *storage.Objects) {
	b.mu.Lock()
	defer b.mu.Unlock()
	for _, obj := range objs.Items {
		if obj.Bucket != b.name {
			panic(fmt.Errorf("adding gs://%s/%s to bucket %s", obj.Bucket, obj.Name, b.name))
		}
		b.objects[obj.Name] = obj
	}
	for _, pfx := range objs.Prefixes {
		b.prefixes[pfx] = struct{}{}
	}
}

func (b *Bucket) delObject(objName string) {
	b.mu.Lock()
	defer b.mu.Unlock()
	delete(b.objects, objName)
}

func (b *Bucket) mkURL(obj interface{}) *url.URL {
	switch v := obj.(type) {
	case string:
		u := b.URL()
		u.Path = v
		return u
	case *storage.Object:
		u := b.URL()
		u.Path = v.Name
		if v.Bucket != "" {
			u.Host = v.Bucket
		}
		return u
	case *url.URL:
		return v
	case nil:
		return b.URL()
	default:
		panic(fmt.Errorf("unknown type %T", obj))
	}
}

func (b *Bucket) apiErr(op string, obj interface{}, e error) error {
	if _, ok := e.(*googleapi.Error); ok {
		return &Error{Op: op, URL: b.mkURL(obj).String(), Err: e}
	}
	return e
}

func (b *Bucket) Fetch(ctx context.Context) error {
	return b.FetchPrefix(ctx, b.prefix, true)
}

func (b *Bucket) FetchPrefix(ctx context.Context, prefix string, recursive bool) error {
	prefix = FixPrefix(prefix)
	req := b.service.Objects.List(b.name)
	if prefix != "" {
		req.Prefix(prefix)
	}
	if !recursive {
		req.Delimiter("/")
	}

	n := 0
	u := b.URL()
	u.Path = prefix
	add := func(objs *storage.Objects) error {
		b.addObjects(objs)
		n += len(objs.Items)
		plog.Infof("Found %d objects under %s", n, u)
		return nil
	}

	plog.Noticef("Fetching %s", u)

	return b.apiErr("storage.objects.list", nil, req.Pages(nil, add))
}

func (b *Bucket) Upload(ctx context.Context, obj *storage.Object, media io.ReadSeeker) error {
	// Calculate the checksum to enable upload integrity checking.
	if obj.Crc32c == "" {
		obj = dupObj(obj) // avoid editing the original
		if err := crcSum(obj, media); err != nil {
			return err
		}
	}

	old := b.Object(obj.Name)
	if !b.writeAlways && crcEq(old, obj) {
		return nil // up to date!
	}
	if b.writeDryRun {
		plog.Noticef("Would write %s", b.mkURL(obj))
		return nil
	}

	req := b.service.Objects.Insert(b.name, obj)
	req.Context(ctx)
	req.Media(media)

	// Watch out for unexpected conflicting updates.
	if old != nil {
		req.IfGenerationMatch(old.Generation)
	}

	plog.Noticef("Writing %s", b.mkURL(obj))

	inserted, err := req.Do()
	if err != nil {
		return b.apiErr("storage.objects.insert", obj, err)
	}

	b.addObject(inserted)
	return nil
}

func (b *Bucket) Copy(ctx context.Context, src *storage.Object, dstName string) error {
	if src.Bucket == "" {
		panic(fmt.Errorf("src.Bucket is blank: %#v", src))
	}

	old := b.Object(dstName)
	if !b.writeAlways && crcEq(old, src) {
		return nil // up to date!
	}

	// It does work to pass src directly to the Rewrite API call, the
	// name and bucket values don't really matter, they just cannot be
	// blank for whatever reason. We make a copy just to get consistent
	// results, e.g. always use the destination bucket's default ACL.
	dst := dupObj(src)
	dst.Name = dstName
	dst.Bucket = b.name

	if b.writeDryRun {
		plog.Noticef("Would copy %s to %s", b.mkURL(src), b.mkURL(dst))
		return nil
	}

	req := b.service.Objects.Rewrite(
		src.Bucket, src.Name, dst.Bucket, dst.Name, src)
	req.Context(ctx)

	// Watch out for unexpected conflicting updates.
	if old != nil {
		req.IfGenerationMatch(old.Generation)
	}
	if src.Generation != 0 {
		req.IfSourceGenerationMatch(src.Generation)
	}

	plog.Noticef("Copying %s to %s", b.mkURL(src), b.mkURL(dst))

	for {
		resp, err := req.Do()
		if err != nil {
			return b.apiErr("storage.objects.rewrite", dst, err)
		}
		if resp.Done {
			b.addObject(resp.Resource)
			return nil
		}
		req.RewriteToken(resp.RewriteToken)
	}
}

func (b *Bucket) Delete(ctx context.Context, objName string) error {
	if b.writeDryRun {
		plog.Noticef("Would delete %s", b.mkURL(objName))
		return nil
	}

	req := b.service.Objects.Delete(b.name, objName)
	req.Context(ctx)

	// Watch out for unexpected conflicting updates.
	if old := b.Object(objName); old != nil {
		req.IfGenerationMatch(old.Generation)
		req.IfMetagenerationMatch(old.Metageneration)
	}

	plog.Noticef("Deleting %s", b.mkURL(objName))

	if err := req.Do(); err != nil {
		return b.apiErr("storage.objects.delete", objName, err)
	}

	b.delObject(objName)
	return nil
}

// FixPrefix ensures non-empty paths end in a slash but never start with one.
func FixPrefix(p string) string {
	if p != "" && !strings.HasSuffix(p, "/") {
		p += "/"
	}
	return strings.TrimPrefix(p, "/")
}

// NextPrefix chops off the final component of an object name or prefix.
func NextPrefix(name string) string {
	prefix, _ := path.Split(strings.TrimSuffix(name, "/"))
	return prefix
}
<commit_msg>storage: log number of prefixes found in addition to objects<commit_after>// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package storage

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
	"strings"
	"sync"

	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/coreos/mantle/Godeps/_workspace/src/google.golang.org/api/googleapi"
	"github.com/coreos/mantle/Godeps/_workspace/src/google.golang.org/api/storage/v1"
)

var (
	UnknownScheme = errors.New("storage: URL missing gs:// scheme")
	UnknownBucket = errors.New("storage: URL missing bucket name")
)

type Bucket struct {
	service *storage.Service
	name    string
	prefix  string

	mu       sync.RWMutex
	prefixes map[string]struct{}
	objects  map[string]*storage.Object

	// writeAlways enables overwriting of objects that appear up-to-date
	writeAlways bool
	// writeDryRun blocks any changes, merely logging them instead
	writeDryRun bool
}

func NewBucket(client *http.Client, bucketURL string) (*Bucket, error) {
	service, err := storage.New(client)
	if err != nil {
		return nil, err
	}

	parsedURL, err := url.Parse(bucketURL)
	if err != nil {
		return nil, err
	}
	if parsedURL.Scheme != "gs" {
		return nil, UnknownScheme
	}
	if parsedURL.Host == "" {
		return nil, UnknownBucket
	}

	return &Bucket{
		service:  service,
		name:     parsedURL.Host,
		prefix:   FixPrefix(parsedURL.Path),
		prefixes: make(map[string]struct{}),
		objects:  make(map[string]*storage.Object),
	}, nil
}

func (b *Bucket) Name() string {
	return b.name
}

func (b *Bucket) Prefix() string {
	return b.prefix
}

func (b *Bucket) URL() *url.URL {
	return &url.URL{Scheme: "gs", Host: b.name, Path: b.prefix}
}

func (b *Bucket) WriteAlways(always bool) {
	b.writeAlways = always
}

func (b *Bucket) WriteDryRun(dryrun bool) {
	b.writeDryRun = dryrun
}

func (b *Bucket) Object(objName string) *storage.Object {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return b.objects[objName]
}

func (b *Bucket) Objects() []*storage.Object {
	b.mu.RLock()
	defer b.mu.RUnlock()
	objs := make([]*storage.Object, 0, len(b.objects))
	for _, obj := range b.objects {
		objs = append(objs, obj)
	}
	return objs
}

func (b *Bucket) Prefixes() []string {
	seen := make(map[string]bool)
	list := make([]string, 0)
	add := func(prefix string) {
		for !seen[prefix] {
			seen[prefix] = true
			list = append(list, prefix)
			prefix = NextPrefix(prefix)
		}
	}

	b.mu.RLock()
	defer b.mu.RUnlock()
	for prefix := range b.prefixes {
		add(prefix)
	}
	for objName := range b.objects {
		add(NextPrefix(objName))
	}

	return list
}

func (b *Bucket) Len() int {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return len(b.objects)
}

func (b *Bucket) addObject(obj *storage.Object) {
	if obj.Bucket != b.name {
		panic(fmt.Errorf("adding gs://%s/%s to bucket %s", obj.Bucket, obj.Name, b.name))
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	b.objects[obj.Name] = obj
}

func (b *Bucket) addObjects(objs *storage.Objects) {
	b.mu.Lock()
	defer b.mu.Unlock()
	for _, obj := range objs.Items {
		if obj.Bucket != b.name {
			panic(fmt.Errorf("adding gs://%s/%s to bucket %s", obj.Bucket, obj.Name, b.name))
		}
		b.objects[obj.Name] = obj
	}
	for _, pfx := range objs.Prefixes {
		b.prefixes[pfx] = struct{}{}
	}
}

func (b *Bucket) delObject(objName string) {
	b.mu.Lock()
	defer b.mu.Unlock()
	delete(b.objects, objName)
}

func (b *Bucket) mkURL(obj interface{}) *url.URL {
	switch v := obj.(type) {
	case string:
		u := b.URL()
		u.Path = v
		return u
	case *storage.Object:
		u := b.URL()
		u.Path = v.Name
		if v.Bucket != "" {
			u.Host = v.Bucket
		}
		return u
	case *url.URL:
		return v
	case nil:
		return b.URL()
	default:
		panic(fmt.Errorf("unknown type %T", obj))
	}
}

func (b *Bucket) apiErr(op string, obj interface{}, e error) error {
	if _, ok := e.(*googleapi.Error); ok {
		return &Error{Op: op, URL: b.mkURL(obj).String(), Err: e}
	}
	return e
}

func (b *Bucket) Fetch(ctx context.Context) error {
	return b.FetchPrefix(ctx, b.prefix, true)
}

func (b *Bucket) FetchPrefix(ctx context.Context, prefix string, recursive bool) error {
	prefix = FixPrefix(prefix)
	req := b.service.Objects.List(b.name)
	if prefix != "" {
		req.Prefix(prefix)
	}
	if !recursive {
		req.Delimiter("/")
	}

	n := 0
	p := 0
	u := b.URL()
	u.Path = prefix
	add := func(objs *storage.Objects) error {
		b.addObjects(objs)
		n += len(objs.Items)
		plog.Infof("Found %d objects under %s", n, u)
		if len(objs.Prefixes) > 0 {
			p += len(objs.Prefixes)
			plog.Infof("Found %d directories under %s", p, u)
		}
		return nil
	}

	plog.Noticef("Fetching %s", u)

	return b.apiErr("storage.objects.list", nil, req.Pages(nil, add))
}

func (b *Bucket) Upload(ctx context.Context, obj *storage.Object, media io.ReadSeeker) error {
	// Calculate the checksum to enable upload integrity checking.
	if obj.Crc32c == "" {
		obj = dupObj(obj) // avoid editing the original
		if err := crcSum(obj, media); err != nil {
			return err
		}
	}

	old := b.Object(obj.Name)
	if !b.writeAlways && crcEq(old, obj) {
		return nil // up to date!
	}
	if b.writeDryRun {
		plog.Noticef("Would write %s", b.mkURL(obj))
		return nil
	}

	req := b.service.Objects.Insert(b.name, obj)
	req.Context(ctx)
	req.Media(media)

	// Watch out for unexpected conflicting updates.
	if old != nil {
		req.IfGenerationMatch(old.Generation)
	}

	plog.Noticef("Writing %s", b.mkURL(obj))

	inserted, err := req.Do()
	if err != nil {
		return b.apiErr("storage.objects.insert", obj, err)
	}

	b.addObject(inserted)
	return nil
}

func (b *Bucket) Copy(ctx context.Context, src *storage.Object, dstName string) error {
	if src.Bucket == "" {
		panic(fmt.Errorf("src.Bucket is blank: %#v", src))
	}

	old := b.Object(dstName)
	if !b.writeAlways && crcEq(old, src) {
		return nil // up to date!
	}

	// It does work to pass src directly to the Rewrite API call, the
	// name and bucket values don't really matter, they just cannot be
	// blank for whatever reason. We make a copy just to get consistent
	// results, e.g. always use the destination bucket's default ACL.
	dst := dupObj(src)
	dst.Name = dstName
	dst.Bucket = b.name

	if b.writeDryRun {
		plog.Noticef("Would copy %s to %s", b.mkURL(src), b.mkURL(dst))
		return nil
	}

	req := b.service.Objects.Rewrite(
		src.Bucket, src.Name, dst.Bucket, dst.Name, src)
	req.Context(ctx)

	// Watch out for unexpected conflicting updates.
	if old != nil {
		req.IfGenerationMatch(old.Generation)
	}
	if src.Generation != 0 {
		req.IfSourceGenerationMatch(src.Generation)
	}

	plog.Noticef("Copying %s to %s", b.mkURL(src), b.mkURL(dst))

	for {
		resp, err := req.Do()
		if err != nil {
			return b.apiErr("storage.objects.rewrite", dst, err)
		}
		if resp.Done {
			b.addObject(resp.Resource)
			return nil
		}
		req.RewriteToken(resp.RewriteToken)
	}
}

func (b *Bucket) Delete(ctx context.Context, objName string) error {
	if b.writeDryRun {
		plog.Noticef("Would delete %s", b.mkURL(objName))
		return nil
	}

	req := b.service.Objects.Delete(b.name, objName)
	req.Context(ctx)

	// Watch out for unexpected conflicting updates.
	if old := b.Object(objName); old != nil {
		req.IfGenerationMatch(old.Generation)
		req.IfMetagenerationMatch(old.Metageneration)
	}

	plog.Noticef("Deleting %s", b.mkURL(objName))

	if err := req.Do(); err != nil {
		return b.apiErr("storage.objects.delete", objName, err)
	}

	b.delObject(objName)
	return nil
}

// FixPrefix ensures non-empty paths end in a slash but never start with one.
func FixPrefix(p string) string {
	if p != "" && !strings.HasSuffix(p, "/") {
		p += "/"
	}
	return strings.TrimPrefix(p, "/")
}

// NextPrefix chops off the final component of an object name or prefix.
func NextPrefix(name string) string {
	prefix, _ := path.Split(strings.TrimSuffix(name, "/"))
	return prefix
}
<|endoftext|>


<commit_before>package main

import "strings"

func messageCheck(msg string) string {
	var result = ""
	if isTrainingCommand(msg) {
		// train
		messsageDeploy(msg)
		result = "ข้าจำได้แล้ว ลองทักใหม่ซิ อิอิ"

	} else if msg == "#help" {
		result = "ควย เอ้ย! คอย"
	} else if isBotCommand(msg) {
		botCommandProcessing(msg)
	} else {
		result = getReplyMessageFromUser(msg)
		//result = msg
	}

	return result
}
func isTrainingCommand(msg string) bool {
	if len(msg) > 6 && msg[0:4] == "#ask" {
		return true
	} else {
		return false
	}
}
func messsageDeploy(msg string) {
	msg = strings.Trim(msg, "#ask ")
	msg = strings.Replace(msg, " #ans ", ":", 1)
	var msgArray = strings.Split(msg, ":")
	if checkNewMessage(msgArray[0]) == true {
		addNewMessageFromUser(msgArray[0], msgArray[1])
	} else {
		addReplyMessageFromUser(msgArray[0], msgArray[1])
	}
}
func isBotCommand(msg string) bool {
	if len(msg) > 6 && msg[0:4] == "#bot" {
		return true
	} else {
		return false
	}
}
func botCommandProcessing(msg string) {
	var result = ""
	msg = strings.Trim(msg, "#bot ")
	var msgArray = strings.Split(msg, " ")
	if msgArray[0] == "nof" {
		logNof = msgArray[1]
		if msgArray[1] == "close" {
			result = "close log notification"
		} else {
			result = "open log notification"
		}
	} else if msgArray[0] == "help" {
		// show help command list
		result = "#bot nof close -> ปิดแจ้งเตือน\n#bot nof open -> เปิดแจ้งเตือน\n#bot send <userId> <msg> -> ส่งข้อความให้คนอื่น\n#bot broadcast <msg> -> ประกาศ\n"
	} else if msgArray[0] == "send" {
		// send content to user
	} else if msgArray[0] == "broadcast" {
		// broadcast msg to all user
	} else if msgArray[0] == "get" {
		// send content to user
	} else {

	}
	bot.SendText([]string{eggyoID}, result)

}
<commit_msg>v.5.1.1<commit_after>package main

/*
func messageCheck(msg string) string {
	var result = ""
	if isTrainingCommand(msg) {
		// train
		messsageDeploy(msg)
		result = "ข้าจำได้แล้ว ลองทักใหม่ซิ อิอิ"

	} else if msg == "#help" {
		result = "ควย เอ้ย! คอย"
	} else if isBotCommand(msg) {
		botCommandProcessing(msg)
	} else {
		result = getReplyMessageFromUser(msg)
		//result = msg
	}

	return result
}
func isTrainingCommand(msg string) bool {
	if len(msg) > 6 && msg[0:4] == "#ask" {
		return true
	} else {
		return false
	}
}
func messsageDeploy(msg string) {
	msg = strings.Trim(msg, "#ask ")
	msg = strings.Replace(msg, " #ans ", ":", 1)
	var msgArray = strings.Split(msg, ":")
	if checkNewMessage(msgArray[0]) == true {
		addNewMessageFromUser(msgArray[0], msgArray[1])
	} else {
		addReplyMessageFromUser(msgArray[0], msgArray[1])
	}
}
func isBotCommand(msg string) bool {
	if len(msg) > 6 && msg[0:4] == "#bot" {
		return true
	} else {
		return false
	}
}
func botCommandProcessing(msg string) {
	var result = ""
	msg = strings.Trim(msg, "#bot ")
	var msgArray = strings.Split(msg, " ")
	if msgArray[0] == "nof" {
		logNof = msgArray[1]
		if msgArray[1] == "close" {
			result = "close log notification"
		} else {
			result = "open log notification"
		}
	} else if msgArray[0] == "help" {
		// show help command list
		result = "#bot nof close -> ปิดแจ้งเตือน\n#bot nof open -> เปิดแจ้งเตือน\n#bot send <userId> <msg> -> ส่งข้อความให้คนอื่น\n#bot broadcast <msg> -> ประกาศ\n"
	} else if msgArray[0] == "send" {
		// send content to user
	} else if msgArray[0] == "broadcast" {
		// broadcast msg to all user
	} else if msgArray[0] == "get" {
		// send content to user
	} else {

	}
	bot.SendText([]string{eggyoID}, result)

}

*/
<|endoftext|>


<commit_before>// Package tollbooth provides rate limiting logic for HTTP request handler.
package tollbooth

import (
	"fmt"
	"github.com/didip/tollbooth/libstring"
	"github.com/didip/tollbooth/storages"
	"net/http"
	"strings"
	"time"
)

// NewRequestLimit is a constructor for RequestLimit.
func NewRequestLimit(max int64, ttl time.Duration) *RequestLimit {
	return &RequestLimit{Max: max, TTL: ttl}
}

// RequestLimit is a config struct to limit a particular request handler.
type RequestLimit struct {
	// Maximum number of requests to limit per duration.
	Max int64

	// Duration of rate limiter.
	TTL time.Duration

	// List of HTTP Methods to limit (GET, POST, PUT, etc.).
	// Empty means limit all methods.
	Methods []string
}

// HTTPError is an error struct that returns both message and status code.
type HTTPError struct {
	Message    string
	StatusCode int
}

// Error returns error message.
func (httperror *HTTPError) Error() string {
	return fmt.Sprintf("%v: %v", httperror.StatusCode, httperror.Message)
}

// LimitByKeyParts keeps track number of request made by keyParts separated by pipe.
// It returns HTTPError when limit is exceeded.
func LimitByKeyParts(storage storages.ICounterStorage, reqLimit *RequestLimit, keyParts []string) *HTTPError {
	key := strings.Join(keyParts, "|")

	storage.IncrBy(key, int64(1), reqLimit.TTL)
	currentCount, _ := storage.Get(key)

	// Check if the returned counter exceeds our limit
	if currentCount > reqLimit.Max {
		return &HTTPError{Message: "You have reached maximum request limit.", StatusCode: 429}
	}
	return nil
}

// LimitByIP keeps track number of request made by REMOTE_ADDR and returns HTTPError when limit is exceeded.
func LimitByIP(storage storages.ICounterStorage, reqLimit *RequestLimit, r *http.Request) *HTTPError {
	remoteIP := r.Header.Get("REMOTE_ADDR")
	path := r.URL.Path
	return LimitByKeyParts(storage, reqLimit, []string{path, remoteIP})
}

// LimitByIPHandler is a middleware that limits by IP given http.Handler struct.
func LimitByIPHandler(storage storages.ICounterStorage, reqLimit *RequestLimit, next http.Handler) http.Handler {
	middle := func(w http.ResponseWriter, r *http.Request) {
		// 1. If reqLimit.Methods is not defined or empty, checks all HTTP methods.
		// 2. If request method is included in reqLimit.Methods, check it.
		if reqLimit.Methods == nil || len(reqLimit.Methods) == 0 || libstring.StringInSlice(reqLimit.Methods, r.Method) {
			httpError := LimitByIP(storage, reqLimit, r)

			if httpError != nil {
				http.Error(w, httpError.Message, httpError.StatusCode)
				return
			} else {
				next.ServeHTTP(w, r)
			}
		} else {
			next.ServeHTTP(w, r)
		}

	}
	return http.HandlerFunc(middle)
}

// LimitByIPFuncHandler is a middleware that limits by IP given request handler function.
func LimitByIPFuncHandler(storage storages.ICounterStorage, reqLimit *RequestLimit, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
	next := http.HandlerFunc(nextFunc)
	return LimitByIPHandler(storage, reqLimit, next)
}
<commit_msg>Refactor limit by HTTP method.<commit_after>// Package tollbooth provides rate limiting logic for HTTP request handler.
package tollbooth

import (
	"fmt"
	"github.com/didip/tollbooth/storages"
	"net/http"
	"strings"
	"time"
)

// NewRequestLimit is a constructor for RequestLimit.
func NewRequestLimit(max int64, ttl time.Duration) *RequestLimit {
	return &RequestLimit{Max: max, TTL: ttl}
}

// RequestLimit is a config struct to limit a particular request handler.
type RequestLimit struct {
	// Maximum number of requests to limit per duration.
	Max int64

	// Duration of rate limiter.
	TTL time.Duration

	// List of HTTP Methods to limit (GET, POST, PUT, etc.).
	// Empty means limit all methods.
	Methods []string
}

// HTTPError is an error struct that returns both message and status code.
type HTTPError struct {
	Message    string
	StatusCode int
}

// Error returns error message.
func (httperror *HTTPError) Error() string {
	return fmt.Sprintf("%v: %v", httperror.StatusCode, httperror.Message)
}

// LimitByKeyParts keeps track number of request made by keyParts separated by pipe.
// It returns HTTPError when limit is exceeded.
func LimitByKeyParts(storage storages.ICounterStorage, reqLimit *RequestLimit, keyParts []string) *HTTPError {
	key := strings.Join(keyParts, "|")

	storage.IncrBy(key, int64(1), reqLimit.TTL)
	currentCount, _ := storage.Get(key)

	// Check if the returned counter exceeds our limit
	if currentCount > reqLimit.Max {
		return &HTTPError{Message: "You have reached maximum request limit.", StatusCode: 429}
	}
	return nil
}

// LimitByIPHandler is a middleware that limits by IP given http.Handler struct.
// It keeps track number of request made by REMOTE_ADDR and returns HTTPError when limit is exceeded.
func LimitByIPHandler(storage storages.ICounterStorage, reqLimit *RequestLimit, next http.Handler) http.Handler {
	middle := func(w http.ResponseWriter, r *http.Request) {
		remoteIP := r.Header.Get("REMOTE_ADDR")
		path := r.URL.Path
		defaultKeyParts := []string{remoteIP, path}

		var httpError *HTTPError

		if reqLimit.Methods != nil {
			// Limit by HTTP methods.
			for _, method := range reqLimit.Methods {
				keyParts := append(defaultKeyParts, method)
				httpError = LimitByKeyParts(storage, reqLimit, keyParts)
				if httpError != nil {
					http.Error(w, httpError.Message, httpError.StatusCode)
					return
				}
			}
		} else {
			// Default limiter.
			httpError = LimitByKeyParts(storage, reqLimit, defaultKeyParts)
			if httpError != nil {
				http.Error(w, httpError.Message, httpError.StatusCode)
				return
			}
		}

		// There's no rate-limit error, serve the next handler.
		next.ServeHTTP(w, r)
	}
	return http.HandlerFunc(middle)
}

// LimitByIPFuncHandler is a middleware that limits by IP given request handler function.
func LimitByIPFuncHandler(storage storages.ICounterStorage, reqLimit *RequestLimit, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
	return LimitByIPHandler(storage, reqLimit, http.HandlerFunc(nextFunc))
}
<|endoftext|>


<commit_before>package trac

import (
	"bytes"
	"crypto/tls"
	"encoding/csv"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/net/publicsuffix"
)

type AuthType uint

const (
	AuthBasic AuthType = iota
	AuthForm
)

type Client struct {
	url      string
	authType AuthType
	client   *http.Client
}

// Ticket in Trac can come in any shape, so our representation is just a map of
// strings. There will always be a "_url" member in the hash being the URL to
// the ticket, the other fields depend of the Trac configuration.
type Ticket map[string]string

func ParseAuthType(s string) (AuthType, error) {
	s = strings.ToLower(s)

	switch s {
	case "basic":
		return AuthBasic, nil
	case "form":
		return AuthForm, nil
	default:
		return AuthBasic, errors.Errorf("Invalid AuthType string: %s", s)
	}
}

func NewClient(url string, authType AuthType, debug bool) (*Client, error) {
	jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})

	if err != nil {
		return nil, errors.Wrap(err, "Error while initializing public suffix list")
	}

	transport := &HTTPTransport{
		Log: debug,
	}

	return &Client{
		url:      url,
		authType: authType,
		client: &http.Client{
			Timeout:   10 * time.Second,
			Jar:       jar,
			Transport: transport,
		},
	}, nil
}

func (c *Client) SetInsecure(insecure bool) {
	c.client.Transport.(*HTTPTransport).TLSClientConfig = &tls.Config{InsecureSkipVerify: insecure}
}

func (c *Client) authenticateBasic(username, password string) error {
	req, err := http.NewRequest("GET", c.url+"/login", nil)

	if err != nil {
		return errors.Wrap(err, "Error while initializing request")
	}

	req.SetBasicAuth(username, password)

	resp, err := c.client.Do(req)

	if err != nil {
		return errors.Wrap(err, "Error while sending login request")
	}

	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusFound {
		return nil
	}

	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		return errors.New("Invalid username or password")
	}

	return errors.Errorf("Unexpected HTTP status: %d", resp.StatusCode)
}

var TOKEN_FORM_RE = regexp.MustCompile(`<input\s+type="hidden"\s+name="__FORM_TOKEN"\s+value="([a-z0-9]+)"\s+/>`)

func (c *Client) getLoginFormToken() (string, error) {
	resp, err := c.client.Get(c.url + "/login")

	if err != nil {
		return "", errors.Wrap(err, "Error while retrieving login page")
	}

	defer resp.Body.Close()

	loginHtml, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return "", errors.Wrap(err, "Error while reading login page")
	}

	// It seems we could also retrieve the value from the cookies
	match := TOKEN_FORM_RE.FindSubmatch(loginHtml)

	if match == nil {
		return "", errors.New("Cannot find form token in login page")
	}

	return string(match[1]), nil
}

func (c *Client) authenticateForm(username, password string) error {
	// First get the login page to get the form token
	formToken, err := c.getLoginFormToken()

	if err != nil {
		return errors.Wrap(err, "Error while loading form token")
	}

	resp, err := c.client.PostForm(c.url+"/login", url.Values{
		"user":         {username},
		"password":     {password},
		"referer":      {c.url},
		"__FORM_TOKEN": {formToken},
	})

	if err != nil {
		return errors.Wrap(err, "Error while sending login request")
	}

	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusFound {
		return nil
	}

	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		return errors.New("Invalid username or password")
	}

	return errors.Errorf("Unexpected HTTP status: %d", resp.StatusCode)
}

func (c *Client) Authenticate(username, password string) error {
	switch c.authType {
	case AuthBasic:
		return c.authenticateBasic(username, password)
	case AuthForm:
		return c.authenticateForm(username, password)
	default:
		panic("Unknown auth type")
	}
}

func (c *Client) GetTicket(id string) (Ticket, error) {
	ticketUrl := c.url + "/ticket/" + id
	csvTicketUrl := ticketUrl + "?format=csv"

	log.Printf("GET %s", csvTicketUrl)

	resp, err := c.client.Get(csvTicketUrl)

	if err != nil {
		return Ticket{}, errors.Wrap(err, "Error while sending ticket request")
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return Ticket{}, errors.Errorf("Unexpected HTTP status: %d", resp.StatusCode)
	}

	csvData, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return Ticket{}, errors.Wrap(err, "Error while reading response data")
	}

	// Trac seems to send a UTF8 BOM, strip it if present
	if bytes.HasPrefix(csvData, []byte{0xef, 0xbb, 0xbf}) {
		csvData = csvData[3:]
	}

	records, err := csv.NewReader(bytes.NewReader(csvData)).ReadAll()

	if err != nil {
		return Ticket{}, errors.Wrap(err, "Error while decoding CSV")
	}

	if len(records) != 2 || len(records[0]) != len(records[1]) {
		return Ticket{}, errors.New("Unexpected number of records in CSV")
	}

	ticket := map[string]string{}

	for idx, field := range records[0] {
		ticket[field] = records[1][idx]
	}

	ticket["_url"] = ticketUrl

	return ticket, nil
}
<commit_msg>Stop following Trac redirects once we get the auth cookie<commit_after>package trac

import (
	"bytes"
	"crypto/tls"
	"encoding/csv"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/net/publicsuffix"
)

type AuthType uint

const (
	AuthBasic AuthType = iota
	AuthForm
)

type Client struct {
	url      string
	authType AuthType
	client   *http.Client
}

// Ticket in Trac can come in any shape, so our representation is just a map of
// strings. There will always be a "_url" member in the hash being the URL to
// the ticket, the other fields depend of the Trac configuration.
type Ticket map[string]string

func ParseAuthType(s string) (AuthType, error) {
	s = strings.ToLower(s)

	switch s {
	case "basic":
		return AuthBasic, nil
	case "form":
		return AuthForm, nil
	default:
		return AuthBasic, errors.Errorf("Invalid AuthType string: %s", s)
	}
}

func NewClient(url string, authType AuthType, debug bool) (*Client, error) {
	jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})

	if err != nil {
		return nil, errors.Wrap(err, "Error while initializing public suffix list")
	}

	transport := &HTTPTransport{
		Log: debug,
	}

	return &Client{
		url:      url,
		authType: authType,
		client: &http.Client{
			Timeout:   10 * time.Second,
			Jar:       jar,
			Transport: transport,
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				if jarHasAuthToken(jar, req.URL) {
					return http.ErrUseLastResponse
				}

				if len(via) >= 10 {
					return errors.New("Too many redirects")
				}

				return nil
			},
		},
	}, nil
}

func jarHasAuthToken(jar *cookiejar.Jar, url *url.URL) bool {
	for _, cookie := range jar.Cookies(url) {
		if cookie.Name == "trac_auth" && len(cookie.Value) > 0 {
			return true
		}
	}

	return false
}

func (c *Client) SetInsecure(insecure bool) {
	c.client.Transport.(*HTTPTransport).TLSClientConfig = &tls.Config{InsecureSkipVerify: insecure}
}

func (c *Client) authenticateBasic(username, password string) error {
	req, err := http.NewRequest("GET", c.url+"/login", nil)

	if err != nil {
		return errors.Wrap(err, "Error while initializing request")
	}

	req.SetBasicAuth(username, password)

	resp, err := c.client.Do(req)

	if err != nil {
		return errors.Wrap(err, "Error while sending login request")
	}

	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusFound {
		return nil
	}

	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		return errors.New("Invalid username or password")
	}

	return errors.Errorf("Unexpected HTTP status: %d", resp.StatusCode)
}

var TOKEN_FORM_RE = regexp.MustCompile(`<input\s+type="hidden"\s+name="__FORM_TOKEN"\s+value="([a-z0-9]+)"\s+/>`)

func (c *Client) getLoginFormToken() (string, error) {
	resp, err := c.client.Get(c.url + "/login")

	if err != nil {
		return "", errors.Wrap(err, "Error while retrieving login page")
	}

	defer resp.Body.Close()

	loginHtml, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return "", errors.Wrap(err, "Error while reading login page")
	}

	// It seems we could also retrieve the value from the cookies
	match := TOKEN_FORM_RE.FindSubmatch(loginHtml)

	if match == nil {
		return "", errors.New("Cannot find form token in login page")
	}

	return string(match[1]), nil
}

func (c *Client) authenticateForm(username, password string) error {
	// First get the login page to get the form token
	formToken, err := c.getLoginFormToken()

	if err != nil {
		return errors.Wrap(err, "Error while loading form token")
	}

	resp, err := c.client.PostForm(c.url+"/login", url.Values{
		"user":         {username},
		"password":     {password},
		"referer":      {c.url},
		"__FORM_TOKEN": {formToken},
	})

	if err != nil {
		return errors.Wrap(err, "Error while sending login request")
	}

	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK, http.StatusFound, http.StatusSeeOther:
		return nil
	case http.StatusUnauthorized, http.StatusForbidden:
		return errors.New("Invalid username or password")
	default:
		return errors.Errorf("Unexpected HTTP status: %d", resp.StatusCode)
	}
}

func (c *Client) Authenticate(username, password string) error {
	switch c.authType {
	case AuthBasic:
		return c.authenticateBasic(username, password)
	case AuthForm:
		return c.authenticateForm(username, password)
	default:
		panic("Unknown auth type")
	}
}

func (c *Client) GetTicket(id string) (Ticket, error) {
	ticketUrl := c.url + "/ticket/" + id
	csvTicketUrl := ticketUrl + "?format=csv"

	log.Printf("GET %s", csvTicketUrl)

	resp, err := c.client.Get(csvTicketUrl)

	if err != nil {
		return Ticket{}, errors.Wrap(err, "Error while sending ticket request")
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return Ticket{}, errors.Errorf("Unexpected HTTP status: %d", resp.StatusCode)
	}

	csvData, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return Ticket{}, errors.Wrap(err, "Error while reading response data")
	}

	// Trac seems to send a UTF8 BOM, strip it if present
	if bytes.HasPrefix(csvData, []byte{0xef, 0xbb, 0xbf}) {
		csvData = csvData[3:]
	}

	records, err := csv.NewReader(bytes.NewReader(csvData)).ReadAll()

	if err != nil {
		return Ticket{}, errors.Wrap(err, "Error while decoding CSV")
	}

	if len(records) != 2 || len(records[0]) != len(records[1]) {
		return Ticket{}, errors.New("Unexpected number of records in CSV")
	}

	ticket := map[string]string{}

	for idx, field := range records[0] {
		ticket[field] = records[1][idx]
	}

	ticket["_url"] = ticketUrl

	return ticket, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// Custom error for 'couldn't transcode, but i renamed it, so don't move it to failed'.
type convertRenamedError struct {
	text string
}

func (e *convertRenamedError) Error() string {
	return "Something was wrong with this file that needs user intervention: " + e.text + ". Renamed the file so the user is forced to choose."
}

// Tries to convert the given video to hls.
func convertToHLSAppropriately(inPath string, outPath string, config Config) error {
	// Probe it to find out what needs doing.
	log.Println("Probing, this sometimes takes a while...")
	probeResult, probeErr := probe(inPath)
	if probeErr != nil {
		return errors.New("Couldn't probe " + inPath)
	}
	log.Printf("Probed, found %v streams", len(probeResult.Streams))
	log.Printf("Probe result: %+v", probeResult)

	// Find the streams
	audioStreams := probeResult.audioStreams()
	videoStreams := probeResult.videoStreams()
	if len(audioStreams) == 0 {
		return errors.New("No audio stream")
	}
	if len(videoStreams) == 0 {
		return errors.New("No video stream")
	}

	// Too many audio streams?
	if len(audioStreams) > 1 {
		TODO check if AudioStreamX exists in the filename?
		log.Printf("Too many audio streams, splitting them out and forcing the user to choose one.")
		for _, stream := range audioStreams {
			args := []string{
				"-ss", "60", // Start from 60s
				"-t", "60", // Only grab 60s
				"-i", inPath,
				"-map", fmt.Sprintf("0:%d", stream.Index),
				"-b:a", "128k", // CBR so it previews nicely on osx.
				inPath + fmt.Sprintf(".AudioStream%d preview.mp3", stream.Index),
			}
			exec.Command("ffmpeg", args...).CombinedOutput() // TODO handle errors one day. This *should* work if probing succeeded earlier however.
		}
		// Rename it.
		ext := filepath.Ext(inPath) // Eg '.vob'
		nameSansExt := strings.TrimSuffix(inPath, ext)
		newName := nameSansExt + ".AudioStreamX" + ext + ".please insert correct audio stream number then remove this"
		os.Rename(inPath, newName)
		return &convertRenamedError{text: "Too many audio streams"}
	}

	// Figure out what to do with the audio.
	audioStream := audioStreams[TODO choose the one as per the filename or the first]
	var audioCommand []string
	if audioStream.Channel_layout == "stereo" && audioStream.Codec_name == "aac" {
		audioCommand = []string{"-acodec", "copy"} // Best case, can leave as-is.
	} else if audioStream.Channel_layout == "stereo" {
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k"} // Transcode, same channels.
	} else if audioStream.Channel_layout == "5.1" { // FL+FR+FC+LFE+BL+BR
		// Tweak the 5.1 conversion, as by default it is quiet and drops the subwoofer.
		log.Println("Using custom downmix from 5.1 to stereo that preserves bass and speech")
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k", "-af", "pan=stereo|FL<FL+BL+FC+LFE|FR<FR+BR+FC+LFE"}
	} else if audioStream.Channel_layout == "5.1(side)" { // FL+FR+FC+LFE+SL+SR
		log.Println("Using custom downmix from 5.1 to stereo that preserves bass and speech")
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k", "-af", "pan=stereo|FL<FL+SL+FC+LFE|FR<FR+SR+FC+LFE"}
	} else {
		log.Println("Using `-ac 2` due to unexpected channel layout:", audioStream.Channel_layout)
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k", "-ac", "2"} // Lousy cover-all.
	}

	// Figure out what to do with the video.
	videoStream := videoStreams[0]
	var videoArgs []string
	if videoStream.Codec_name == "h264" && videoStream.Codec_tag_string != "avc1" {
		// Can only direct copy if not avc1, or it won't be a seekable video.
		log.Println("Eligible for video not being transcoded, so no quality loss :)")
		videoArgs = []string{"-vcodec", "copy"}
	} else if videoStream.Codec_name == "h264" && videoStream.Codec_tag_string == "avc1" {
		// Have to transcode avc1 or they can't seek when watching.
		log.Println("Video needs transcoding to be seekable HLS, because it's AVC1")
		videoArgs = nil
	} else {
		// Any other codec needs transcoding.
		log.Println("Video needs transcoding, original codec doesn't suit")
		videoArgs = nil
	}

	if config.DebugSkipHLS {
		// Skip conversion, this is good for debugging.
		log.Println("Not converting to HLS due to DebugSkipHLS flag")
		return nil
	}

	return runConvertToHLS(inPath, outPath, audioStream.Index, videoStream.Index, audioCommand, videoArgs)
}

/// Converts to HLS. If it gets back an error about h264_mp4toannexb, it retries with the appropriate command.
func runConvertToHLS(inPath string, outPath string, audioStreamIndex int, videoStreamIndex int, audioArgs []string, videoArgs []string) error {
	log.Printf("Converting to HLS with ffmpeg, audio: %+v; video: %+v\n", audioArgs, videoArgs)
	firstArgs := []string{
		"-i", inPath, // Select the input file.
		"-map", "0:v", // This copies all video channels, even though there's hopefully only one.
		"-map", "0:a", // This copies all audio channels, so if there's a commentary channel it won't copy only the commentary.
		// Can't copy subs, as ffmpeg segfaults with an error "Exactly one WebVTT stream is needed".
		// "-map", "0:s", // Copies all subs
		// "-c:s", "copy", // Copy subs, no transcode option for subs. It's a bit silly this needs to be specified.
	}
	lastArgs := []string{"-hls_list_size", "0", outPath}
	allArgs := append(append(append(firstArgs, audioArgs...), videoArgs...), lastArgs...)
	result, err := exec.Command("ffmpeg", allArgs...).CombinedOutput()

	// Print result if its an error.
	if err != nil {
		log.Println("Initial ffmpeg attempt failed, the output was as follows:")
		log.Println(string(result))
	}

	// Did it fail with the annex b issue? If so, retry.
	// You can't simply *always* have h264_mp4toannexb enabled, it fails if not needed.
	if err != nil && strings.Contains(string(result), "h264_mp4toannexb") {
		log.Println("Attempting to convert to HLS using h264_mp4toannexb option")
		allArgs := append(append(append(append(firstArgs, audioArgs...), videoArgs...), "-bsf:v", "h264_mp4toannexb"), lastArgs...)
		result2, err2 := exec.Command("ffmpeg", allArgs...).CombinedOutput()

		// Print result if its an error.
		if err2 != nil {
			log.Println("Second ffmpeg attempt failed, the output was as follows:")
			log.Println(string(result2))
		}

		return err2
	}
	return err
}
<commit_msg>Scan the filename<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// Custom error for 'couldn't transcode, but i renamed it, so don't move it to failed'.
type convertRenamedError struct {
	text string
}

func (e *convertRenamedError) Error() string {
	return "Something was wrong with this file that needs user intervention: " + e.text + ". Renamed the file so the user is forced to choose."
}

// Tries to convert the given video to hls.
func convertToHLSAppropriately(inPath string, outPath string, config Config) error {
	// Probe it to find out what needs doing.
	log.Println("Probing, this sometimes takes a while...")
	probeResult, probeErr := probe(inPath)
	if probeErr != nil {
		return errors.New("Couldn't probe " + inPath)
	}
	log.Printf("Probed, found %v streams", len(probeResult.Streams))
	log.Printf("Probe result: %+v", probeResult)

	// Find the streams
	audioStreams := probeResult.audioStreams()
	videoStreams := probeResult.videoStreams()
	if len(videoStreams) == 0 {
		return errors.New("No video stream")
	}

	// Figure out which audio stream.
	var audioStream ProbeStream
	if len(audioStreams) == 0 {
		return errors.New("No audio stream")
	} else if len(audioStreams) == 1 {
		// Easy case, just one to choose from.
		audioStream = audioStreams[0]
	} else {
		// More than one audio. Either need to make the user choose, or take their choice.
		indexFromFilename := audioStreamFromFile(inPath)
		if indexFromFilename == nil {
			// User hasn't made a selection.
			log.Printf("Too many audio streams, splitting them out and forcing the user to choose one.")
			for _, stream := range audioStreams {
				args := []string{
					"-ss", "60", // Start from 60s
					"-t", "60", // Only grab 60s
					"-i", inPath,
					"-map", fmt.Sprintf("0:%d", stream.Index),
					"-b:a", "128k", // CBR so it previews nicely on osx.
					inPath + fmt.Sprintf(".AudioStream%d preview.mp3", stream.Index),
				}
				exec.Command("ffmpeg", args...).CombinedOutput() // TODO handle errors one day. This *should* work if probing succeeded earlier however.
			}
			// Rename it.
			ext := filepath.Ext(inPath) // Eg '.vob'
			nameSansExt := strings.TrimSuffix(inPath, ext)
			newName := nameSansExt + ".AudioStreamX" + ext + ".please insert correct audio stream number then remove this"
			os.Rename(inPath, newName)
			return &convertRenamedError{text: "Too many audio streams"}
		} else {
			TODO choose the stream
		}
	}

	// Figure out what to do with the audio.
	var audioCommand []string
	if audioStream.Channel_layout == "stereo" && audioStream.Codec_name == "aac" {
		audioCommand = []string{"-acodec", "copy"} // Best case, can leave as-is.
	} else if audioStream.Channel_layout == "stereo" {
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k"} // Transcode, same channels.
	} else if audioStream.Channel_layout == "5.1" { // FL+FR+FC+LFE+BL+BR
		// Tweak the 5.1 conversion, as by default it is quiet and drops the subwoofer.
		log.Println("Using custom downmix from 5.1 to stereo that preserves bass and speech")
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k", "-af", "pan=stereo|FL<FL+BL+FC+LFE|FR<FR+BR+FC+LFE"}
	} else if audioStream.Channel_layout == "5.1(side)" { // FL+FR+FC+LFE+SL+SR
		log.Println("Using custom downmix from 5.1 to stereo that preserves bass and speech")
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k", "-af", "pan=stereo|FL<FL+SL+FC+LFE|FR<FR+SR+FC+LFE"}
	} else {
		log.Println("Using `-ac 2` due to unexpected channel layout:", audioStream.Channel_layout)
		audioCommand = []string{"-strict", "experimental", "-b:a", "192k", "-ac", "2"} // Lousy cover-all.
	}

	// Figure out what to do with the video.
	videoStream := videoStreams[0]
	var videoArgs []string
	if videoStream.Codec_name == "h264" && videoStream.Codec_tag_string != "avc1" {
		// Can only direct copy if not avc1, or it won't be a seekable video.
		log.Println("Eligible for video not being transcoded, so no quality loss :)")
		videoArgs = []string{"-vcodec", "copy"}
	} else if videoStream.Codec_name == "h264" && videoStream.Codec_tag_string == "avc1" {
		// Have to transcode avc1 or they can't seek when watching.
		log.Println("Video needs transcoding to be seekable HLS, because it's AVC1")
		videoArgs = nil
	} else {
		// Any other codec needs transcoding.
		log.Println("Video needs transcoding, original codec doesn't suit")
		videoArgs = nil
	}

	if config.DebugSkipHLS {
		// Skip conversion, this is good for debugging.
		log.Println("Not converting to HLS due to DebugSkipHLS flag")
		return nil
	}

	return runConvertToHLS(inPath, outPath, audioStream.Index, videoStream.Index, audioCommand, videoArgs)
}

/// Converts to HLS. If it gets back an error about h264_mp4toannexb, it retries with the appropriate command.
func runConvertToHLS(inPath string, outPath string, audioStreamIndex int, videoStreamIndex int, audioArgs []string, videoArgs []string) error {
	log.Printf("Converting to HLS with ffmpeg, audio: %+v; video: %+v\n", audioArgs, videoArgs)
	firstArgs := []string{
		"-i", inPath, // Select the input file.
		"-map", "0:v", // This copies all video channels, even though there's hopefully only one.
		"-map", "0:a", // This copies all audio channels, so if there's a commentary channel it won't copy only the commentary.
		// Can't copy subs, as ffmpeg segfaults with an error "Exactly one WebVTT stream is needed".
		// "-map", "0:s", // Copies all subs
		// "-c:s", "copy", // Copy subs, no transcode option for subs. It's a bit silly this needs to be specified.
	}
	lastArgs := []string{"-hls_list_size", "0", outPath}
	allArgs := append(append(append(firstArgs, audioArgs...), videoArgs...), lastArgs...)
	result, err := exec.Command("ffmpeg", allArgs...).CombinedOutput()

	// Print result if its an error.
	if err != nil {
		log.Println("Initial ffmpeg attempt failed, the output was as follows:")
		log.Println(string(result))
	}

	// Did it fail with the annex b issue? If so, retry.
	// You can't simply *always* have h264_mp4toannexb enabled, it fails if not needed.
	if err != nil && strings.Contains(string(result), "h264_mp4toannexb") {
		log.Println("Attempting to convert to HLS using h264_mp4toannexb option")
		allArgs := append(append(append(append(firstArgs, audioArgs...), videoArgs...), "-bsf:v", "h264_mp4toannexb"), lastArgs...)
		result2, err2 := exec.Command("ffmpeg", allArgs...).CombinedOutput()

		// Print result if its an error.
		if err2 != nil {
			log.Println("Second ffmpeg attempt failed, the output was as follows:")
			log.Println(string(result2))
		}

		return err2
	}
	return err
}
<|endoftext|>


<commit_before>package gothumb

import (
	"fmt"
)

type Transformer interface {
	None() error
	FlipH() error
	FlipV() error
	Transpose() error
	Rotate90() error
	Rotate180() error
	Rotate270() error
	Transverse() error
}

func Transform(orientation int, transformer Transformer) error {
	switch orientation {
	case 1:
		return transformer.None()
	case 2:
		return transformer.FlipH()
	case 3:
		return transformer.Rotate180()
	case 4:
		return transformer.FlipV()
	case 5:
		return transformer.Transpose()
	case 6:
		return transformer.Rotate270()
	case 7:
		return transformer.Transverse()
	case 8:
		return transformer.Rotate90()
	}

	return fmt.Errorf("Invalid orientation: %d", orientation)
}
<commit_msg>Fixed orientation bug<commit_after>package gothumb

import (
	"fmt"
)

type Transformer interface {
	None() error
	FlipH() error
	FlipV() error
	Transpose() error
	Rotate90() error
	Rotate180() error
	Rotate270() error
	Transverse() error
}

func Transform(orientation int, transformer Transformer) error {
	switch orientation {
	case 1:
		return transformer.None()
	case 2:
		return transformer.FlipH()
	case 3:
		return transformer.Rotate180()
	case 4:
		return transformer.FlipV()
	case 5:
		return transformer.Transpose()
	case 6:
		return transformer.Rotate90()
	case 7:
		return transformer.Transverse()
	case 8:
		return transformer.Rotate270()
	}

	return fmt.Errorf("Invalid orientation: %d", orientation)
}
<|endoftext|>