<commit_before>package main

import (
	"context"
	"crypto/tls"
	"flag"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/fnproject/fn/fnlb/lb"
)

const VERSION = "0.0.4"

func main() {
	// XXX (reed): normalize
	fnodes := flag.String("nodes", "", "comma separated list of functions nodes")

	var conf lb.Config
	flag.StringVar(&conf.DBurl, "db", "sqlite3://:memory:", "backend to store nodes, default to in memory")
	flag.StringVar(&conf.Listen, "listen", ":8081", "port to run on")
	flag.IntVar(&conf.HealthcheckInterval, "hc-interval", 3, "how often to check f(x) nodes, in seconds")
	flag.StringVar(&conf.HealthcheckEndpoint, "hc-path", "/version", "endpoint to determine node health")
	flag.IntVar(&conf.HealthcheckUnhealthy, "hc-unhealthy", 2, "threshold of failed checks to declare node unhealthy")
	flag.IntVar(&conf.HealthcheckTimeout, "hc-timeout", 5, "timeout of healthcheck endpoint, in seconds")
	flag.Parse()

	if len(*fnodes) > 0 {
		// starting w/o nodes is fine too
		conf.Nodes = strings.Split(*fnodes, ",")
	}

	conf.Transport = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 120 * time.Second,
		}).Dial,
		MaxIdleConnsPerHost: 512,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig: &tls.Config{
			ClientSessionCache: tls.NewLRUClientSessionCache(4096),
		},
	}

	g, err := lb.NewAllGrouper(conf)
	if err != nil {
		logrus.WithError(err).Fatal("error setting up grouper")
	}

	r := lb.NewConsistentRouter(conf)
	k := func(r *http.Request) (string, error) {
		return r.URL.Path, nil
	}

	h := lb.NewProxy(k, g, r, conf)
	h = g.Wrap(h) // add/del/list endpoints
	h = r.Wrap(h) // stats / dash endpoint

	err = serve(conf.Listen, h)
	if err != nil {
		logrus.WithError(err).Fatal("server error")
	}
}

func serve(addr string, handler http.Handler) error {
	server := &http.Server{Addr: addr, Handler: handler}

	var wg sync.WaitGroup
	wg.Add(1)
	defer wg.Wait()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGQUIT, syscall.SIGINT)
	go func() {
		defer wg.Done()
		for sig := range ch {
			logrus.WithFields(logrus.Fields{"signal": sig}).Info("received signal")
			server.Shutdown(context.Background()) // safe shutdown
			return
		}
	}()
	return server.ListenAndServe()
}
<commit_msg>fnlb: 0.0.5 release [skip ci]<commit_after>package main

import (
	"context"
	"crypto/tls"
	"flag"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/fnproject/fn/fnlb/lb"
)

const VERSION = "0.0.5"

func main() {
	// XXX (reed): normalize
	fnodes := flag.String("nodes", "", "comma separated list of functions nodes")

	var conf lb.Config
	flag.StringVar(&conf.DBurl, "db", "sqlite3://:memory:", "backend to store nodes, default to in memory")
	flag.StringVar(&conf.Listen, "listen", ":8081", "port to run on")
	flag.IntVar(&conf.HealthcheckInterval, "hc-interval", 3, "how often to check f(x) nodes, in seconds")
	flag.StringVar(&conf.HealthcheckEndpoint, "hc-path", "/version", "endpoint to determine node health")
	flag.IntVar(&conf.HealthcheckUnhealthy, "hc-unhealthy", 2, "threshold of failed checks to declare node unhealthy")
	flag.IntVar(&conf.HealthcheckTimeout, "hc-timeout", 5, "timeout of healthcheck endpoint, in seconds")
	flag.Parse()

	if len(*fnodes) > 0 {
		// starting w/o nodes is fine too
		conf.Nodes = strings.Split(*fnodes, ",")
	}

	conf.Transport = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 120 * time.Second,
		}).Dial,
		MaxIdleConnsPerHost: 512,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig: &tls.Config{
			ClientSessionCache: tls.NewLRUClientSessionCache(4096),
		},
	}

	g, err := lb.NewAllGrouper(conf)
	if err != nil {
		logrus.WithError(err).Fatal("error setting up grouper")
	}

	r := lb.NewConsistentRouter(conf)
	k := func(r *http.Request) (string, error) {
		return r.URL.Path, nil
	}

	h := lb.NewProxy(k, g, r, conf)
	h = g.Wrap(h) // add/del/list endpoints
	h = r.Wrap(h) // stats / dash endpoint

	err = serve(conf.Listen, h)
	if err != nil {
		logrus.WithError(err).Fatal("server error")
	}
}

func serve(addr string, handler http.Handler) error {
	server := &http.Server{Addr: addr, Handler: handler}

	var wg sync.WaitGroup
	wg.Add(1)
	defer wg.Wait()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGQUIT, syscall.SIGINT)
	go func() {
		defer wg.Done()
		for sig := range ch {
			logrus.WithFields(logrus.Fields{"signal": sig}).Info("received signal")
			server.Shutdown(context.Background()) // safe shutdown
			return
		}
	}()
	return server.ListenAndServe()
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"crypto/tls"
	"flag"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/coreos/go-semver/semver"
	"github.com/fnproject/fn/fnlb/lb"
	"github.com/sirupsen/logrus"
)

const VERSION = "0.0.119"

func main() {
	// XXX (reed): normalize
	fnodes := flag.String("nodes", "", "comma separated list of functions nodes")
	minAPIVersion := flag.String("min-api-version", "0.0.86", "minimal node API to accept")

	var conf lb.Config
	flag.StringVar(&conf.DBurl, "db", "sqlite3://:memory:", "backend to store nodes, default to in memory")
	flag.StringVar(&conf.Listen, "listen", ":8081", "port to run on")
	flag.IntVar(&conf.HealthcheckInterval, "hc-interval", 3, "how often to check f(x) nodes, in seconds")
	flag.StringVar(&conf.HealthcheckEndpoint, "hc-path", "/version", "endpoint to determine node health")
	flag.IntVar(&conf.HealthcheckUnhealthy, "hc-unhealthy", 2, "threshold of failed checks to declare node unhealthy")
	flag.IntVar(&conf.HealthcheckTimeout, "hc-timeout", 5, "timeout of healthcheck endpoint, in seconds")
	flag.StringVar(&conf.ZipkinURL, "zipkin", "", "zipkin endpoint to send traces")
	flag.Parse()

	conf.MinAPIVersion = semver.New(*minAPIVersion)

	if len(*fnodes) > 0 {
		// starting w/o nodes is fine too
		conf.Nodes = strings.Split(*fnodes, ",")
	}

	conf.Transport = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 120 * time.Second,
		}).Dial,
		MaxIdleConnsPerHost: 512,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig: &tls.Config{
			ClientSessionCache: tls.NewLRUClientSessionCache(4096),
		},
	}

	g, err := lb.NewAllGrouper(conf)
	if err != nil {
		logrus.WithError(err).Fatal("error setting up grouper")
	}

	r := lb.NewConsistentRouter(conf)
	k := func(r *http.Request) (string, error) {
		return r.URL.Path, nil
	}

	h := lb.NewProxy(k, g, r, conf)
	h = g.Wrap(h) // add/del/list endpoints
	h = r.Wrap(h) // stats / dash endpoint

	err = serve(conf.Listen, h)
	if err != nil {
		logrus.WithError(err).Fatal("server error")
	}
}

func serve(addr string, handler http.Handler) error {
	server := &http.Server{Addr: addr, Handler: handler}

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGQUIT, syscall.SIGINT)
	go func() {
		for sig := range ch {
			logrus.WithFields(logrus.Fields{"signal": sig}).Info("received signal")
			server.Shutdown(context.Background()) // safe shutdown
			return
		}
	}()
	return server.ListenAndServe()
}
<commit_msg>fnlb: 0.0.120 release [skip ci]<commit_after>package main

import (
	"context"
	"crypto/tls"
	"flag"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/coreos/go-semver/semver"
	"github.com/fnproject/fn/fnlb/lb"
	"github.com/sirupsen/logrus"
)

const VERSION = "0.0.120"

func main() {
	// XXX (reed): normalize
	fnodes := flag.String("nodes", "", "comma separated list of functions nodes")
	minAPIVersion := flag.String("min-api-version", "0.0.87", "minimal node API to accept")

	var conf lb.Config
	flag.StringVar(&conf.DBurl, "db", "sqlite3://:memory:", "backend to store nodes, default to in memory")
	flag.StringVar(&conf.Listen, "listen", ":8081", "port to run on")
	flag.IntVar(&conf.HealthcheckInterval, "hc-interval", 3, "how often to check f(x) nodes, in seconds")
	flag.StringVar(&conf.HealthcheckEndpoint, "hc-path", "/version", "endpoint to determine node health")
	flag.IntVar(&conf.HealthcheckUnhealthy, "hc-unhealthy", 2, "threshold of failed checks to declare node unhealthy")
	flag.IntVar(&conf.HealthcheckTimeout, "hc-timeout", 5, "timeout of healthcheck endpoint, in seconds")
	flag.StringVar(&conf.ZipkinURL, "zipkin", "", "zipkin endpoint to send traces")
	flag.Parse()

	conf.MinAPIVersion = semver.New(*minAPIVersion)

	if len(*fnodes) > 0 {
		// starting w/o nodes is fine too
		conf.Nodes = strings.Split(*fnodes, ",")
	}

	conf.Transport = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 120 * time.Second,
		}).Dial,
		MaxIdleConnsPerHost: 512,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig: &tls.Config{
			ClientSessionCache: tls.NewLRUClientSessionCache(4096),
		},
	}

	g, err := lb.NewAllGrouper(conf)
	if err != nil {
		logrus.WithError(err).Fatal("error setting up grouper")
	}

	r := lb.NewConsistentRouter(conf)
	k := func(r *http.Request) (string, error) {
		return r.URL.Path, nil
	}

	h := lb.NewProxy(k, g, r, conf)
	h = g.Wrap(h) // add/del/list endpoints
	h = r.Wrap(h) // stats / dash endpoint

	err = serve(conf.Listen, h)
	if err != nil {
		logrus.WithError(err).Fatal("server error")
	}
}

func serve(addr string, handler http.Handler) error {
	server := &http.Server{Addr: addr, Handler: handler}

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGQUIT, syscall.SIGINT)
	go func() {
		for sig := range ch {
			logrus.WithFields(logrus.Fields{"signal": sig}).Info("received signal")
			server.Shutdown(context.Background()) // safe shutdown
			return
		}
	}()
	return server.ListenAndServe()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/hatofmonkeys/cloudfocker/focker"
	"log"
	"os"
)

func main() {
	app := cli.NewApp()
	app.Name = "fock"
	app.Usage = "Cloud Focker - fock the Cloud, run apps locally!"
	app.Action = func(c *cli.Context) {
		cli.ShowAppHelp(c)
	}

	app.Commands = []cli.Command{
		{
			Name:  "docker",
			Usage: "print the local Docker info",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.DockerVersion(os.Stdout)
			},
		},
		{
			Name:  "this",
			Usage: "download the Cloud Foundry base image",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.ImportRootfsImage(os.Stdout)
			},
		},
		{
			Name:  "up",
			Usage: "stage and run the application",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				pwd, err := os.Getwd()
				if err != nil {
					log.Fatalf(" %s", err)
				} else {
					if err := focker.RunStager(os.Stdout, pwd); err != nil {
						log.Fatalf(" %s", err)
					}
				}
				focker.RunRuntime(os.Stdout)
			},
		},
		{
			Name:  "off",
			Usage: "stop the application container and remove it",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.StopRuntime(os.Stdout)
			},
		},
		{
			Name:  "buildpacks",
			Usage: "show the buildpacks installed on the local system",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.ListBuildpacks(os.Stdout)
			},
		},
		{
			Name:  "add-buildpack",
			Usage: "add-buildpack [URL] - add a buildpack from a GitHub URL to the local system",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				if url := c.Args().First(); url != "" {
					focker.AddBuildpack(os.Stdout, url)
				} else {
					fmt.Println("Please supply a GitHub URL to download")
				}
			},
		},
		{
			Name:  "delete-buildpack",
			Usage: "delete-buildpack [BUILDPACK] - delete a buildpack from the local system",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				if buildpack := c.Args().First(); buildpack != "" {
					focker.DeleteBuildpack(os.Stdout, buildpack)
				} else {
					fmt.Println("Please supply a buildpack to delete")
				}
			},
		},
		{
			Name:  "stage",
			Usage: "only execute the staging phase for the application",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				if internal := c.Args().First(); internal == "internal" {
					//this is focker being called inside the staging container
					focker.StageApp(os.Stdout)
				} else {
					//this is focker being called by the user, outside of the staging container
					pwd, err := os.Getwd()
					if err != nil {
						log.Fatalf(" %s", err)
					} else {
						if err := focker.RunStager(os.Stdout, pwd); err != nil {
							log.Fatalf(" %s", err)
						}
					}
				}
			},
		},
		{
			Name:  "run",
			Usage: "only run the current staged application",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.RunRuntime(os.Stdout)
			},
		},
	}

	app.Run(os.Args)
}
<commit_msg>Version 0.0.1<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/hatofmonkeys/cloudfocker/focker"
	"log"
	"os"
)

func main() {
	app := cli.NewApp()
	app.Name = "fock"
	app.Version = "0.0.1"
	app.Usage = "Cloud Focker - fock the Cloud, run apps locally!"
	app.Action = func(c *cli.Context) {
		cli.ShowAppHelp(c)
	}

	app.Commands = []cli.Command{
		{
			Name:  "docker",
			Usage: "print the local Docker info",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.DockerVersion(os.Stdout)
			},
		},
		{
			Name:  "this",
			Usage: "download the Cloud Foundry base image",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.ImportRootfsImage(os.Stdout)
			},
		},
		{
			Name:  "up",
			Usage: "stage and run the application",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				pwd, err := os.Getwd()
				if err != nil {
					log.Fatalf(" %s", err)
				} else {
					if err := focker.RunStager(os.Stdout, pwd); err != nil {
						log.Fatalf(" %s", err)
					}
				}
				focker.RunRuntime(os.Stdout)
			},
		},
		{
			Name:  "off",
			Usage: "stop the application container and remove it",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.StopRuntime(os.Stdout)
			},
		},
		{
			Name:  "buildpacks",
			Usage: "show the buildpacks installed on the local system",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.ListBuildpacks(os.Stdout)
			},
		},
		{
			Name:  "add-buildpack",
			Usage: "add-buildpack [URL] - add a buildpack from a GitHub URL to the local system",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				if url := c.Args().First(); url != "" {
					focker.AddBuildpack(os.Stdout, url)
				} else {
					fmt.Println("Please supply a GitHub URL to download")
				}
			},
		},
		{
			Name:  "delete-buildpack",
			Usage: "delete-buildpack [BUILDPACK] - delete a buildpack from the local system",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				if buildpack := c.Args().First(); buildpack != "" {
					focker.DeleteBuildpack(os.Stdout, buildpack)
				} else {
					fmt.Println("Please supply a buildpack to delete")
				}
			},
		},
		{
			Name:  "stage",
			Usage: "only execute the staging phase for the application",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				if internal := c.Args().First(); internal == "internal" {
					//this is focker being called inside the staging container
					focker.StageApp(os.Stdout)
				} else {
					//this is focker being called by the user, outside of the staging container
					pwd, err := os.Getwd()
					if err != nil {
						log.Fatalf(" %s", err)
					} else {
						if err := focker.RunStager(os.Stdout, pwd); err != nil {
							log.Fatalf(" %s", err)
						}
					}
				}
			},
		},
		{
			Name:  "run",
			Usage: "only run the current staged application",
			Action: func(c *cli.Context) {
				focker := focker.NewFocker()
				focker.RunRuntime(os.Stdout)
			},
		},
	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package tournaments

import (
	"github.com/m4rw3r/uuid"
	"sort"
	"time"
)

type YellowPeriod struct {
	From   time.Time `json:"from"`
	To     time.Time `json:"to"`
	Player uuid.UUID `json:"uuid"`
	Active bool      `json:"active"`
}

type MonthStats struct {
	Year  int        `json:"year"`
	Month time.Month `json:"month"`
	Best  uuid.UUID  `json:"best"`
	Worst uuid.UUID  `json:"worst"`
}

type PeriodStats struct {
	YellowPeriods []YellowPeriod `json:"yellowPeriods"`
	MonthStats    []*MonthStats  `json:"monthStats"`
}

type SeasonTitles struct {
	Season   int `json:"season"`
	Champion struct {
		Uuid     uuid.UUID `json:"uuid"`
		Winnings int       `json:"winnings"`
	} `json:"champion"`
	AvgPlaceWinner struct {
		Uuid     uuid.UUID `json:"uuid"`
		AvgPlace float64   `json:"avgPlace"`
	} `json:"avgPlaceWinner"`
	PointsWinner struct {
		Uuid   uuid.UUID `json:"uuid"`
		Points int       `json:"points"`
	} `json:"pointsWinner"`
	MostYellowDays struct {
		Uuid uuid.UUID `json:"uuid"`
		Days int       `json:"days"`
	} `json:"mostYellowDays"`
	PlayerOfTheYear struct {
		Uuid   uuid.UUID `json:"uuid"`
		Months int       `json:"months"`
	} `json:"playerOfTheYear"`
	LoserOfTheYear struct {
		Uuid   uuid.UUID `json:"uuid"`
		Months int       `json:"months"`
	} `json:"loserOfTheYear"`
}

func BestPlayer(tournaments Tournaments) (uuid.UUID, bool) {
	standings := NewStandings(tournaments)
	standings.ByBestPlayer()

	if standings[0].Results.Equals(standings[1].Results) {
		// It's still a tie
		return standings[0].Player, true
	}
	return standings[0].Player, false
}

func WorstPlayer(tournaments Tournaments) (uuid.UUID, bool) {
	standings := NewStandings(tournaments)
	standings.ByWorstPlayer()
	if standings[0].Results.Equals(standings[1].Results) {
		// It's still a tie
		return standings[0].Player, true
	}
	return standings[0].Player, false
}

func YellowPeriods(tournaments Tournaments) []YellowPeriod {
	var periods []YellowPeriod
	var currentPeriod *YellowPeriod
	var season, seasonIndex int

	sort.Stable(tournaments)
	for i := range tournaments {
		if !tournaments[i].Played {
			continue
		}
		// Leader is based on results for the season, so start from "scratch" on new seasons
		if tournaments[i].Info.Season != season {
			season = tournaments[i].Info.Season
			seasonIndex = i
		}
		standings := NewStandings(tournaments[seasonIndex : i+1])
		standings.ByWinnings(season < 2013)
		if currentPeriod == nil {
			currentPeriod = &YellowPeriod{
				From:   tournaments[i].Info.Scheduled,
				To:     tournaments[i].Info.Scheduled,
				Player: standings[0].Player,
				Active: true,
			}
		} else if currentPeriod.Player == standings[0].Player {
			currentPeriod.To = tournaments[i].Info.Scheduled
		} else {
			currentPeriod.Active = false
			currentPeriod.To = tournaments[i].Info.Scheduled
			periods = append(periods, *currentPeriod)
			currentPeriod = &YellowPeriod{
				From:   tournaments[i].Info.Scheduled,
				To:     tournaments[i].Info.Scheduled,
				Player: standings[0].Player,
				Active: true,
			}
		}
	}
	periods = append(periods, *currentPeriod)
	return periods
}

func YellowDaysInSeason(yellowPeriods []YellowPeriod, season int) map[uuid.UUID]int {
	seasonStartDate := time.Date(season, 1, 1, 0, 0, 0, 0, time.Local)
	seasonEndDate := seasonStartDate.AddDate(1, 0, 0)

	daysByPlayer := make(map[uuid.UUID]int)
	for _, p := range yellowPeriods {

		// Skip entries that don't concert the given season
		if p.To.Before(seasonStartDate) || p.From.After(seasonEndDate) {
			continue
		}

		// If entry starts last season, adjust it so it starts at season start date
		if p.From.Before(seasonStartDate) {
			p.From = seasonStartDate
		}

		// If entry ends next season or is active, adjust it so it ends at season end date
		if p.To.After(seasonEndDate) || p.Active {
			p.To = seasonEndDate
		}

		days := int(p.To.Sub(p.From).Hours() / 24)
		daysByPlayer[p.Player] += days
	}
	return daysByPlayer
}

func mostYellowDaysInSeason(yellowPeriods []YellowPeriod, season int) (uuid.UUID, int) {
	daysByPlayer := YellowDaysInSeason(yellowPeriods, season)
	max := 0
	var maxPlayer uuid.UUID
	for p, d := range daysByPlayer {
		if d > max {
			max = d
			maxPlayer = p
		}
	}

	return maxPlayer, max
}

func loserOfTheYear(monthPeriods []*MonthStats, season int) ([]uuid.UUID, int) {
	lastPlacesByPlayer := make(map[uuid.UUID]int)
	playersByPlace := make(map[int][]uuid.UUID)

	for _, mp := range monthPeriods {
		if mp.Year != season {
			continue
		}
		lastPlacesByPlayer[mp.Worst] += 1
	}

	max := 0
	for player, count := range lastPlacesByPlayer {
		playersByPlace[count] = append(playersByPlace[count], player)
		if count > max {
			max = count
		}
	}

	return playersByPlace[max], max
}

func playerOfTheYear(monthPeriods []*MonthStats, season int) ([]uuid.UUID, int) {
	topPlacesByPlayer := make(map[uuid.UUID]int)
	playersByPlace := make(map[int][]uuid.UUID)

	for _, mp := range monthPeriods {
		if mp.Year != season {
			continue
		}
		topPlacesByPlayer[mp.Best] += 1
	}

	max := 0
	for player, count := range topPlacesByPlayer {
		playersByPlace[count] = append(playersByPlace[count], player)
		if count > max {
			max = count
		}
	}

	return playersByPlace[max], max
}

func Titles(seasons []int) []*SeasonTitles {

	var titleList []*SeasonTitles

	seasonStats := SeasonStats(seasons)
	for _, season := range seasons {
		titles := &SeasonTitles{Season: season}
		t, _ := TournamentsBySeason(season)
		seasonStandings := NewStandings(t)

		seasonStandings.ByWinnings(season < 2013)

		for i := range seasonStandings {
			if seasonStandings[i].Enough {
				p, w := seasonStandings[i].Player, seasonStandings[i].Winnings
				titles.Champion.Uuid = p
				titles.Champion.Winnings = w
				break
			}
		}

		seasonStandings.ByAvgPlace()
		for i := range seasonStandings {
			if seasonStandings[i].Enough {
				p, ap := seasonStandings[i].Player, seasonStandings[i].AvgPlace
				titles.AvgPlaceWinner.Uuid = p
				titles.AvgPlaceWinner.AvgPlace = ap
				break
			}
		}

		players, c := playerOfTheYear(seasonStats.MonthStats, season)
		if len(players) == 1 {
			titles.PlayerOfTheYear.Uuid = players[0]
		} else {
		POTYLoop:
			for _, p := range players {
				for _, s := range seasonStandings {
					if s.Player == p && s.Enough {
						titles.PlayerOfTheYear.Uuid = p
						break POTYLoop
					}
				}
			}
		}
		titles.PlayerOfTheYear.Months = c

		players, c = loserOfTheYear(seasonStats.MonthStats, season)
		if len(players) == 1 {
			titles.LoserOfTheYear.Uuid = players[0]
		} else {
		LOTYLoop:
			for _, p := range players {
				for i := len(seasonStandings) - 1; i >= 0; i-- {
					if seasonStandings[i].Player == p && seasonStandings[i].Enough {
						titles.LoserOfTheYear.Uuid = p
						break LOTYLoop
					}
				}
			}
		}

		titles.LoserOfTheYear.Months = c

		seasonStandings.ByPoints()
		for i := range seasonStandings {
			if seasonStandings[i].Enough {
				p, pnts := seasonStandings[i].Player, seasonStandings[i].Points
				titles.PointsWinner.Uuid = p
				titles.PointsWinner.Points = pnts
				break
			}
		}

		// FIXME: This is missing tie breaks..
		p, d := mostYellowDaysInSeason(seasonStats.YellowPeriods, season)
		titles.MostYellowDays.Uuid = p
		titles.MostYellowDays.Days = d

		titleList = append(titleList, titles)
	}
	return titleList
}

func SeasonStats(seasons []int) *PeriodStats {
	stats := new(PeriodStats)
	all, err := AllTournaments()
	if err != nil {
		// TODO
	}

	var t Tournaments
	for _, event := range all {
		for _, s := range seasons {
			if event.Info.Season == s {
				t = append(t, event)
			}
		}
	}

	yellows := YellowPeriods(t)
	stats.YellowPeriods = yellows

	for _, season := range seasons {
		byMonth := t.GroupByMonths(season)
		var sortedMonths []int
		for k := range byMonth {
			sortedMonths = append(sortedMonths, int(k))
		}
		sort.Ints(sortedMonths)
		for i := range sortedMonths {
			v := byMonth[time.Month(i)]
			monthStats := new(MonthStats)

			played := v.Played()

			if len(played) == 0 {
				continue
			}

			sort.Stable(v)
			monthStats.Year = season
			monthStats.Month = time.Month(i)

			best, tie := BestPlayer(v)
			if tie {
				var tiebreakTournaments Tournaments
				for j := 1; j <= i; j++ {
					tiebreakTournaments = append(tiebreakTournaments, byMonth[time.Month(j)]...)
				}
				best, tie = BestPlayer(tiebreakTournaments)
				if tie {
					println("    Warning: Tied for best player for month", i, "in year", season)
				}

			}
			monthStats.Best = best
			worst, tie := WorstPlayer(v)
			if tie {
				var tiebreakTournaments Tournaments
				for j := 1; j <= int(i); j++ {
					tiebreakTournaments = append(tiebreakTournaments, byMonth[time.Month(j)]...)
				}

				worst, tie = WorstPlayer(tiebreakTournaments)
				if tie {
					println("    Warning: Tied for worst player for month", i, "in year", season)
				}
			}
			monthStats.Worst = worst

			stats.MonthStats = append(stats.MonthStats, monthStats)
		}
	}
	return stats
}
<commit_msg>Fix ugly bug where using array index insted of items lead to incorrect months calculated in stats.<commit_after>package tournaments

import (
	"github.com/m4rw3r/uuid"
	"sort"
	"time"
)

type YellowPeriod struct {
	From   time.Time `json:"from"`
	To     time.Time `json:"to"`
	Player uuid.UUID `json:"uuid"`
	Active bool      `json:"active"`
}

type MonthStats struct {
	Year  int        `json:"year"`
	Month time.Month `json:"month"`
	Best  uuid.UUID  `json:"best"`
	Worst uuid.UUID  `json:"worst"`
}

type PeriodStats struct {
	YellowPeriods []YellowPeriod `json:"yellowPeriods"`
	MonthStats    []*MonthStats  `json:"monthStats"`
}

type SeasonTitles struct {
	Season   int `json:"season"`
	Champion struct {
		Uuid     uuid.UUID `json:"uuid"`
		Winnings int       `json:"winnings"`
	} `json:"champion"`
	AvgPlaceWinner struct {
		Uuid     uuid.UUID `json:"uuid"`
		AvgPlace float64   `json:"avgPlace"`
	} `json:"avgPlaceWinner"`
	PointsWinner struct {
		Uuid   uuid.UUID `json:"uuid"`
		Points int       `json:"points"`
	} `json:"pointsWinner"`
	MostYellowDays struct {
		Uuid uuid.UUID `json:"uuid"`
		Days int       `json:"days"`
	} `json:"mostYellowDays"`
	PlayerOfTheYear struct {
		Uuid   uuid.UUID `json:"uuid"`
		Months int       `json:"months"`
	} `json:"playerOfTheYear"`
	LoserOfTheYear struct {
		Uuid   uuid.UUID `json:"uuid"`
		Months int       `json:"months"`
	} `json:"loserOfTheYear"`
}

func BestPlayer(tournaments Tournaments) (uuid.UUID, bool) {
	standings := NewStandings(tournaments)
	standings.ByBestPlayer()

	if standings[0].Results.Equals(standings[1].Results) {
		// It's still a tie
		return standings[0].Player, true
	}
	return standings[0].Player, false
}

func WorstPlayer(tournaments Tournaments) (uuid.UUID, bool) {
	standings := NewStandings(tournaments)
	standings.ByWorstPlayer()
	if standings[0].Results.Equals(standings[1].Results) {
		// It's still a tie
		return standings[0].Player, true
	}
	return standings[0].Player, false
}

func YellowPeriods(tournaments Tournaments) []YellowPeriod {
	var periods []YellowPeriod
	var currentPeriod *YellowPeriod
	var season, seasonIndex int

	sort.Stable(tournaments)
	for i := range tournaments {
		if !tournaments[i].Played {
			continue
		}
		// Leader is based on results for the season, so start from "scratch" on new seasons
		if tournaments[i].Info.Season != season {
			season = tournaments[i].Info.Season
			seasonIndex = i
		}
		standings := NewStandings(tournaments[seasonIndex : i+1])
		standings.ByWinnings(season < 2013)
		if currentPeriod == nil {
			currentPeriod = &YellowPeriod{
				From:   tournaments[i].Info.Scheduled,
				To:     tournaments[i].Info.Scheduled,
				Player: standings[0].Player,
				Active: true,
			}
		} else if currentPeriod.Player == standings[0].Player {
			currentPeriod.To = tournaments[i].Info.Scheduled
		} else {
			currentPeriod.Active = false
			currentPeriod.To = tournaments[i].Info.Scheduled
			periods = append(periods, *currentPeriod)
			currentPeriod = &YellowPeriod{
				From:   tournaments[i].Info.Scheduled,
				To:     tournaments[i].Info.Scheduled,
				Player: standings[0].Player,
				Active: true,
			}
		}
	}
	periods = append(periods, *currentPeriod)
	return periods
}

func YellowDaysInSeason(yellowPeriods []YellowPeriod, season int) map[uuid.UUID]int {
	seasonStartDate := time.Date(season, 1, 1, 0, 0, 0, 0, time.Local)
	seasonEndDate := seasonStartDate.AddDate(1, 0, 0)

	daysByPlayer := make(map[uuid.UUID]int)
	for _, p := range yellowPeriods {

		// Skip entries that don't concert the given season
		if p.To.Before(seasonStartDate) || p.From.After(seasonEndDate) {
			continue
		}

		// If entry starts last season, adjust it so it starts at season start date
		if p.From.Before(seasonStartDate) {
			p.From = seasonStartDate
		}

		// If entry ends next season or is active, adjust it so it ends at season end date
		if p.To.After(seasonEndDate) || p.Active {
			p.To = seasonEndDate
		}

		days := int(p.To.Sub(p.From).Hours() / 24)
		daysByPlayer[p.Player] += days
	}
	return daysByPlayer
}

func mostYellowDaysInSeason(yellowPeriods []YellowPeriod, season int) (uuid.UUID, int) {
	daysByPlayer := YellowDaysInSeason(yellowPeriods, season)
	max := 0
	var maxPlayer uuid.UUID
	for p, d := range daysByPlayer {
		if d > max {
			max = d
			maxPlayer = p
		}
	}

	return maxPlayer, max
}

func loserOfTheYear(monthPeriods []*MonthStats, season int) ([]uuid.UUID, int) {
	lastPlacesByPlayer := make(map[uuid.UUID]int)
	playersByPlace := make(map[int][]uuid.UUID)

	for _, mp := range monthPeriods {
		if mp.Year != season {
			continue
		}
		lastPlacesByPlayer[mp.Worst] += 1
	}

	max := 0
	for player, count := range lastPlacesByPlayer {
		playersByPlace[count] = append(playersByPlace[count], player)
		if count > max {
			max = count
		}
	}

	return playersByPlace[max], max
}

func playerOfTheYear(monthPeriods []*MonthStats, season int) ([]uuid.UUID, int) {
	topPlacesByPlayer := make(map[uuid.UUID]int)
	playersByPlace := make(map[int][]uuid.UUID)

	for _, mp := range monthPeriods {
		if mp.Year != season {
			continue
		}
		topPlacesByPlayer[mp.Best] += 1
	}

	max := 0
	for player, count := range topPlacesByPlayer {
		playersByPlace[count] = append(playersByPlace[count], player)
		if count > max {
			max = count
		}
	}

	return playersByPlace[max], max
}

func Titles(seasons []int) []*SeasonTitles {

	var titleList []*SeasonTitles

	seasonStats := SeasonStats(seasons)
	for _, season := range seasons {
		titles := &SeasonTitles{Season: season}
		t, _ := TournamentsBySeason(season)
		seasonStandings := NewStandings(t)

		seasonStandings.ByWinnings(season < 2013)

		for i := range seasonStandings {
			if seasonStandings[i].Enough {
				p, w := seasonStandings[i].Player, seasonStandings[i].Winnings
				titles.Champion.Uuid = p
				titles.Champion.Winnings = w
				break
			}
		}

		seasonStandings.ByAvgPlace()
		for i := range seasonStandings {
			if seasonStandings[i].Enough {
				p, ap := seasonStandings[i].Player, seasonStandings[i].AvgPlace
				titles.AvgPlaceWinner.Uuid = p
				titles.AvgPlaceWinner.AvgPlace = ap
				break
			}
		}

		players, c := playerOfTheYear(seasonStats.MonthStats, season)
		if len(players) == 1 {
			titles.PlayerOfTheYear.Uuid = players[0]
		} else {
		POTYLoop:
			for _, p := range players {
				for _, s := range seasonStandings {
					if s.Player == p && s.Enough {
						titles.PlayerOfTheYear.Uuid = p
						break POTYLoop
					}
				}
			}
		}
		titles.PlayerOfTheYear.Months = c

		players, c = loserOfTheYear(seasonStats.MonthStats, season)
		if len(players) == 1 {
			titles.LoserOfTheYear.Uuid = players[0]
		} else {
		LOTYLoop:
			for _, p := range players {
				for i := len(seasonStandings) - 1; i >= 0; i-- {
					if seasonStandings[i].Player == p && seasonStandings[i].Enough {
						titles.LoserOfTheYear.Uuid = p
						break LOTYLoop
					}
				}
			}
		}

		titles.LoserOfTheYear.Months = c

		seasonStandings.ByPoints()
		for i := range seasonStandings {
			if seasonStandings[i].Enough {
				p, pnts := seasonStandings[i].Player, seasonStandings[i].Points
				titles.PointsWinner.Uuid = p
				titles.PointsWinner.Points = pnts
				break
			}
		}

		// FIXME: This is missing tie breaks..
		p, d := mostYellowDaysInSeason(seasonStats.YellowPeriods, season)
		titles.MostYellowDays.Uuid = p
		titles.MostYellowDays.Days = d

		titleList = append(titleList, titles)
	}
	return titleList
}

func SeasonStats(seasons []int) *PeriodStats {
	stats := new(PeriodStats)
	all, err := AllTournaments()
	if err != nil {
		// TODO
	}

	var t Tournaments
	for _, event := range all {
		for _, s := range seasons {
			if event.Info.Season == s {
				t = append(t, event)
			}
		}
	}

	yellows := YellowPeriods(t)
	stats.YellowPeriods = yellows

	for _, season := range seasons {
		byMonth := t.GroupByMonths(season)
		var sortedMonths []int
		for k := range byMonth {
			sortedMonths = append(sortedMonths, int(k))
		}
		sort.Ints(sortedMonths)
		for _,i := range sortedMonths {
			v := byMonth[time.Month(i)]
			monthStats := new(MonthStats)

			played := v.Played()

			if len(played) == 0 {
				continue
			}

			sort.Stable(v)
			monthStats.Year = season
			monthStats.Month = time.Month(i)

			best, tie := BestPlayer(v)
			if tie {
				var tiebreakTournaments Tournaments
				for j := 1; j <= i; j++ {
					tiebreakTournaments = append(tiebreakTournaments, byMonth[time.Month(j)]...)
				}
				best, tie = BestPlayer(tiebreakTournaments)
				if tie {
					println("    Warning: Tied for best player for month", i, "in year", season)
				}

			}
			monthStats.Best = best
			worst, tie := WorstPlayer(v)
			if tie {
				var tiebreakTournaments Tournaments
				for j := 1; j <= int(i); j++ {
					tiebreakTournaments = append(tiebreakTournaments, byMonth[time.Month(j)]...)
				}

				worst, tie = WorstPlayer(tiebreakTournaments)
				if tie {
					println("    Warning: Tied for worst player for month", i, "in year", season)
				}
			}
			monthStats.Worst = worst

			stats.MonthStats = append(stats.MonthStats, monthStats)
		}
	}
	return stats
}
<|endoftext|>


<commit_before>// Copyright 2017 The Prometheus Authors
// 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 labels

import (
	"bytes"
	"encoding/json"
	"sort"
	"strconv"

	"github.com/cespare/xxhash/v2"
)

// Well-known label names used by Prometheus components.
const (
	MetricName   = "__name__"
	AlertName    = "alertname"
	BucketLabel  = "le"
	InstanceName = "instance"

	labelSep = '\xfe'
)

var seps = []byte{'\xff'}

// Label is a key/value pair of strings.
type Label struct {
	Name, Value string
}

// Labels is a sorted set of labels. Order has to be guaranteed upon
// instantiation.
type Labels []Label

func (ls Labels) Len() int           { return len(ls) }
func (ls Labels) Swap(i, j int)      { ls[i], ls[j] = ls[j], ls[i] }
func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name }

func (ls Labels) String() string {
	var b bytes.Buffer

	b.WriteByte('{')
	for i, l := range ls {
		if i > 0 {
			b.WriteByte(',')
			b.WriteByte(' ')
		}
		b.WriteString(l.Name)
		b.WriteByte('=')
		b.WriteString(strconv.Quote(l.Value))
	}
	b.WriteByte('}')
	return b.String()
}

// Bytes returns ls as a byte slice.
// It uses an byte invalid character as a separator and so should not be used for printing.
func (ls Labels) Bytes(buf []byte) []byte {
	b := bytes.NewBuffer(buf[:0])
	b.WriteByte(labelSep)
	for i, l := range ls {
		if i > 0 {
			b.WriteByte(seps[0])
		}
		b.WriteString(l.Name)
		b.WriteByte(seps[0])
		b.WriteString(l.Value)
	}
	return b.Bytes()
}

// MarshalJSON implements json.Marshaler.
func (ls Labels) MarshalJSON() ([]byte, error) {
	return json.Marshal(ls.Map())
}

// UnmarshalJSON implements json.Unmarshaler.
func (ls *Labels) UnmarshalJSON(b []byte) error {
	var m map[string]string

	if err := json.Unmarshal(b, &m); err != nil {
		return err
	}

	*ls = FromMap(m)
	return nil
}

// MarshalYAML implements yaml.Marshaler.
func (ls Labels) MarshalYAML() (interface{}, error) {
	return ls.Map(), nil
}

// UnmarshalYAML implements yaml.Unmarshaler.
func (ls *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var m map[string]string

	if err := unmarshal(&m); err != nil {
		return err
	}

	*ls = FromMap(m)
	return nil
}

// MatchLabels returns a subset of Labels that matches/does not match with the provided label names based on the 'on' boolean.
// If on is set to true, it returns the subset of labels that match with the provided label names and its inverse when 'on' is set to false.
func (ls Labels) MatchLabels(on bool, names ...string) Labels {
	matchedLabels := Labels{}

	nameSet := make(map[string]struct{}, len(names))
	for _, n := range names {
		nameSet[n] = struct{}{}
	}

	for _, v := range ls {
		if _, ok := nameSet[v.Name]; on == ok && (on || v.Name != MetricName) {
			matchedLabels = append(matchedLabels, v)
		}
	}

	return matchedLabels
}

// Hash returns a hash value for the label set.
func (ls Labels) Hash() uint64 {
	// Use xxhash.Sum64(b) for fast path as it's faster.
	b := make([]byte, 0, 1024)
	for i, v := range ls {
		if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
			// If labels entry is 1KB+ do not allocate whole entry.
			h := xxhash.New()
			_, _ = h.Write(b)
			for _, v := range ls[i:] {
				_, _ = h.WriteString(v.Name)
				_, _ = h.Write(seps)
				_, _ = h.WriteString(v.Value)
				_, _ = h.Write(seps)
			}
			return h.Sum64()
		}

		b = append(b, v.Name...)
		b = append(b, seps[0])
		b = append(b, v.Value...)
		b = append(b, seps[0])
	}
	return xxhash.Sum64(b)
}

// HashForLabels returns a hash value for the labels matching the provided names.
// 'names' have to be sorted in ascending order.
func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
	b = b[:0]
	i, j := 0, 0
	for i < len(ls) && j < len(names) {
		if names[j] < ls[i].Name {
			j++
		} else if ls[i].Name < names[j] {
			i++
		} else {
			b = append(b, ls[i].Name...)
			b = append(b, seps[0])
			b = append(b, ls[i].Value...)
			b = append(b, seps[0])
			i++
			j++
		}
	}
	return xxhash.Sum64(b), b
}

// HashWithoutLabels returns a hash value for all labels except those matching
// the provided names.
// 'names' have to be sorted in ascending order.
func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
	b = b[:0]
	j := 0
	for i := range ls {
		for j < len(names) && names[j] < ls[i].Name {
			j++
		}
		if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
			continue
		}
		b = append(b, ls[i].Name...)
		b = append(b, seps[0])
		b = append(b, ls[i].Value...)
		b = append(b, seps[0])
	}
	return xxhash.Sum64(b), b
}

// WithLabels returns a new labels.Labels from ls that only contains labels matching names.
// 'names' have to be sorted in ascending order.
func (ls Labels) WithLabels(names ...string) Labels {
	ret := make([]Label, 0, len(ls))

	i, j := 0, 0
	for i < len(ls) && j < len(names) {
		if names[j] < ls[i].Name {
			j++
		} else if ls[i].Name < names[j] {
			i++
		} else {
			ret = append(ret, ls[i])
			i++
			j++
		}
	}
	return ret
}

// WithoutLabels returns a new labels.Labels from ls that contains labels not matching names.
// 'names' have to be sorted in ascending order.
func (ls Labels) WithoutLabels(names ...string) Labels {
	ret := make([]Label, 0, len(ls))

	j := 0
	for i := range ls {
		for j < len(names) && names[j] < ls[i].Name {
			j++
		}
		if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
			continue
		}
		ret = append(ret, ls[i])
	}
	return ret
}

// Copy returns a copy of the labels.
func (ls Labels) Copy() Labels {
	res := make(Labels, len(ls))
	copy(res, ls)
	return res
}

// Get returns the value for the label with the given name.
// Returns an empty string if the label doesn't exist.
func (ls Labels) Get(name string) string {
	for _, l := range ls {
		if l.Name == name {
			return l.Value
		}
	}
	return ""
}

// Has returns true if the label with the given name is present.
func (ls Labels) Has(name string) bool {
	for _, l := range ls {
		if l.Name == name {
			return true
		}
	}
	return false
}

// HasDuplicateLabelNames returns whether ls has duplicate label names.
// It assumes that the labelset is sorted.
func (ls Labels) HasDuplicateLabelNames() (string, bool) {
	for i, l := range ls {
		if i == 0 {
			continue
		}
		if l.Name == ls[i-1].Name {
			return l.Name, true
		}
	}
	return "", false
}

// WithoutEmpty returns the labelset without empty labels.
// May return the same labelset.
func (ls Labels) WithoutEmpty() Labels {
	for _, v := range ls {
		if v.Value != "" {
			continue
		}
		// Do not copy the slice until it's necessary.
		els := make(Labels, 0, len(ls)-1)
		for _, v := range ls {
			if v.Value != "" {
				els = append(els, v)
			}
		}
		return els
	}
	return ls
}

// Equal returns whether the two label sets are equal.
func Equal(ls, o Labels) bool {
	if len(ls) != len(o) {
		return false
	}
	for i, l := range ls {
		if l != o[i] {
			return false
		}
	}
	return true
}

// Map returns a string map of the labels.
func (ls Labels) Map() map[string]string {
	m := make(map[string]string, len(ls))
	for _, l := range ls {
		m[l.Name] = l.Value
	}
	return m
}

// New returns a sorted Labels from the given labels.
// The caller has to guarantee that all label names are unique.
func New(ls ...Label) Labels {
	set := make(Labels, 0, len(ls))
	for _, l := range ls {
		set = append(set, l)
	}
	sort.Sort(set)

	return set
}

// FromMap returns new sorted Labels from the given map.
func FromMap(m map[string]string) Labels {
	l := make([]Label, 0, len(m))
	for k, v := range m {
		l = append(l, Label{Name: k, Value: v})
	}
	return New(l...)
}

// FromStrings creates new labels from pairs of strings.
func FromStrings(ss ...string) Labels {
	if len(ss)%2 != 0 {
		panic("invalid number of strings")
	}
	res := make(Labels, 0, len(ss)/2)
	for i := 0; i < len(ss); i += 2 {
		res = append(res, Label{Name: ss[i], Value: ss[i+1]})
	}

	sort.Sort(res)
	return res
}

// Compare compares the two label sets.
// The result will be 0 if a==b, <0 if a < b, and >0 if a > b.
func Compare(a, b Labels) int {
	l := len(a)
	if len(b) < l {
		l = len(b)
	}

	for i := 0; i < l; i++ {
		if a[i].Name != b[i].Name {
			if a[i].Name < b[i].Name {
				return -1
			}
			return 1
		}
		if a[i].Value != b[i].Value {
			if a[i].Value < b[i].Value {
				return -1
			}
			return 1
		}
	}
	// If all labels so far were in common, the set with fewer labels comes first.
	return len(a) - len(b)
}

// Builder allows modifying Labels.
type Builder struct {
	base Labels
	del  []string
	add  []Label
}

// NewBuilder returns a new LabelsBuilder.
func NewBuilder(base Labels) *Builder {
	b := &Builder{
		del: make([]string, 0, 5),
		add: make([]Label, 0, 5),
	}
	b.Reset(base)
	return b
}

// Reset clears all current state for the builder.
func (b *Builder) Reset(base Labels) {
	b.base = base
	b.del = b.del[:0]
	b.add = b.add[:0]
	for _, l := range b.base {
		if l.Value == "" {
			b.del = append(b.del, l.Name)
		}
	}
}

// Del deletes the label of the given name.
func (b *Builder) Del(ns ...string) *Builder {
	for _, n := range ns {
		for i, a := range b.add {
			if a.Name == n {
				b.add = append(b.add[:i], b.add[i+1:]...)
			}
		}
		b.del = append(b.del, n)
	}
	return b
}

// Set the name/value pair as a label.
func (b *Builder) Set(n, v string) *Builder {
	if v == "" {
		// Empty labels are the same as missing labels.
		return b.Del(n)
	}
	for i, a := range b.add {
		if a.Name == n {
			b.add[i].Value = v
			return b
		}
	}
	b.add = append(b.add, Label{Name: n, Value: v})

	return b
}

// Labels returns the labels from the builder. If no modifications
// were made, the original labels are returned.
func (b *Builder) Labels() Labels {
	if len(b.del) == 0 && len(b.add) == 0 {
		return b.base
	}

	// In the general case, labels are removed, modified or moved
	// rather than added.
	res := make(Labels, 0, len(b.base)+len(b.add))
Outer:
	for _, l := range b.base {
		for _, n := range b.del {
			if l.Name == n {
				continue Outer
			}
		}
		for _, la := range b.add {
			if l.Name == la.Name {
				continue Outer
			}
		}
		res = append(res, l)
	}
	res = append(res, b.add...)
	sort.Sort(res)

	return res
}
<commit_msg>Don't count added labels when initializing slice (#10756)<commit_after>// Copyright 2017 The Prometheus Authors
// 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 labels

import (
	"bytes"
	"encoding/json"
	"sort"
	"strconv"

	"github.com/cespare/xxhash/v2"
)

// Well-known label names used by Prometheus components.
const (
	MetricName   = "__name__"
	AlertName    = "alertname"
	BucketLabel  = "le"
	InstanceName = "instance"

	labelSep = '\xfe'
)

var seps = []byte{'\xff'}

// Label is a key/value pair of strings.
type Label struct {
	Name, Value string
}

// Labels is a sorted set of labels. Order has to be guaranteed upon
// instantiation.
type Labels []Label

func (ls Labels) Len() int           { return len(ls) }
func (ls Labels) Swap(i, j int)      { ls[i], ls[j] = ls[j], ls[i] }
func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name }

func (ls Labels) String() string {
	var b bytes.Buffer

	b.WriteByte('{')
	for i, l := range ls {
		if i > 0 {
			b.WriteByte(',')
			b.WriteByte(' ')
		}
		b.WriteString(l.Name)
		b.WriteByte('=')
		b.WriteString(strconv.Quote(l.Value))
	}
	b.WriteByte('}')
	return b.String()
}

// Bytes returns ls as a byte slice.
// It uses an byte invalid character as a separator and so should not be used for printing.
func (ls Labels) Bytes(buf []byte) []byte {
	b := bytes.NewBuffer(buf[:0])
	b.WriteByte(labelSep)
	for i, l := range ls {
		if i > 0 {
			b.WriteByte(seps[0])
		}
		b.WriteString(l.Name)
		b.WriteByte(seps[0])
		b.WriteString(l.Value)
	}
	return b.Bytes()
}

// MarshalJSON implements json.Marshaler.
func (ls Labels) MarshalJSON() ([]byte, error) {
	return json.Marshal(ls.Map())
}

// UnmarshalJSON implements json.Unmarshaler.
func (ls *Labels) UnmarshalJSON(b []byte) error {
	var m map[string]string

	if err := json.Unmarshal(b, &m); err != nil {
		return err
	}

	*ls = FromMap(m)
	return nil
}

// MarshalYAML implements yaml.Marshaler.
func (ls Labels) MarshalYAML() (interface{}, error) {
	return ls.Map(), nil
}

// UnmarshalYAML implements yaml.Unmarshaler.
func (ls *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var m map[string]string

	if err := unmarshal(&m); err != nil {
		return err
	}

	*ls = FromMap(m)
	return nil
}

// MatchLabels returns a subset of Labels that matches/does not match with the provided label names based on the 'on' boolean.
// If on is set to true, it returns the subset of labels that match with the provided label names and its inverse when 'on' is set to false.
func (ls Labels) MatchLabels(on bool, names ...string) Labels {
	matchedLabels := Labels{}

	nameSet := make(map[string]struct{}, len(names))
	for _, n := range names {
		nameSet[n] = struct{}{}
	}

	for _, v := range ls {
		if _, ok := nameSet[v.Name]; on == ok && (on || v.Name != MetricName) {
			matchedLabels = append(matchedLabels, v)
		}
	}

	return matchedLabels
}

// Hash returns a hash value for the label set.
func (ls Labels) Hash() uint64 {
	// Use xxhash.Sum64(b) for fast path as it's faster.
	b := make([]byte, 0, 1024)
	for i, v := range ls {
		if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
			// If labels entry is 1KB+ do not allocate whole entry.
			h := xxhash.New()
			_, _ = h.Write(b)
			for _, v := range ls[i:] {
				_, _ = h.WriteString(v.Name)
				_, _ = h.Write(seps)
				_, _ = h.WriteString(v.Value)
				_, _ = h.Write(seps)
			}
			return h.Sum64()
		}

		b = append(b, v.Name...)
		b = append(b, seps[0])
		b = append(b, v.Value...)
		b = append(b, seps[0])
	}
	return xxhash.Sum64(b)
}

// HashForLabels returns a hash value for the labels matching the provided names.
// 'names' have to be sorted in ascending order.
func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
	b = b[:0]
	i, j := 0, 0
	for i < len(ls) && j < len(names) {
		if names[j] < ls[i].Name {
			j++
		} else if ls[i].Name < names[j] {
			i++
		} else {
			b = append(b, ls[i].Name...)
			b = append(b, seps[0])
			b = append(b, ls[i].Value...)
			b = append(b, seps[0])
			i++
			j++
		}
	}
	return xxhash.Sum64(b), b
}

// HashWithoutLabels returns a hash value for all labels except those matching
// the provided names.
// 'names' have to be sorted in ascending order.
func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
	b = b[:0]
	j := 0
	for i := range ls {
		for j < len(names) && names[j] < ls[i].Name {
			j++
		}
		if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
			continue
		}
		b = append(b, ls[i].Name...)
		b = append(b, seps[0])
		b = append(b, ls[i].Value...)
		b = append(b, seps[0])
	}
	return xxhash.Sum64(b), b
}

// WithLabels returns a new labels.Labels from ls that only contains labels matching names.
// 'names' have to be sorted in ascending order.
func (ls Labels) WithLabels(names ...string) Labels {
	ret := make([]Label, 0, len(ls))

	i, j := 0, 0
	for i < len(ls) && j < len(names) {
		if names[j] < ls[i].Name {
			j++
		} else if ls[i].Name < names[j] {
			i++
		} else {
			ret = append(ret, ls[i])
			i++
			j++
		}
	}
	return ret
}

// WithoutLabels returns a new labels.Labels from ls that contains labels not matching names.
// 'names' have to be sorted in ascending order.
func (ls Labels) WithoutLabels(names ...string) Labels {
	ret := make([]Label, 0, len(ls))

	j := 0
	for i := range ls {
		for j < len(names) && names[j] < ls[i].Name {
			j++
		}
		if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
			continue
		}
		ret = append(ret, ls[i])
	}
	return ret
}

// Copy returns a copy of the labels.
func (ls Labels) Copy() Labels {
	res := make(Labels, len(ls))
	copy(res, ls)
	return res
}

// Get returns the value for the label with the given name.
// Returns an empty string if the label doesn't exist.
func (ls Labels) Get(name string) string {
	for _, l := range ls {
		if l.Name == name {
			return l.Value
		}
	}
	return ""
}

// Has returns true if the label with the given name is present.
func (ls Labels) Has(name string) bool {
	for _, l := range ls {
		if l.Name == name {
			return true
		}
	}
	return false
}

// HasDuplicateLabelNames returns whether ls has duplicate label names.
// It assumes that the labelset is sorted.
func (ls Labels) HasDuplicateLabelNames() (string, bool) {
	for i, l := range ls {
		if i == 0 {
			continue
		}
		if l.Name == ls[i-1].Name {
			return l.Name, true
		}
	}
	return "", false
}

// WithoutEmpty returns the labelset without empty labels.
// May return the same labelset.
func (ls Labels) WithoutEmpty() Labels {
	for _, v := range ls {
		if v.Value != "" {
			continue
		}
		// Do not copy the slice until it's necessary.
		els := make(Labels, 0, len(ls)-1)
		for _, v := range ls {
			if v.Value != "" {
				els = append(els, v)
			}
		}
		return els
	}
	return ls
}

// Equal returns whether the two label sets are equal.
func Equal(ls, o Labels) bool {
	if len(ls) != len(o) {
		return false
	}
	for i, l := range ls {
		if l != o[i] {
			return false
		}
	}
	return true
}

// Map returns a string map of the labels.
func (ls Labels) Map() map[string]string {
	m := make(map[string]string, len(ls))
	for _, l := range ls {
		m[l.Name] = l.Value
	}
	return m
}

// New returns a sorted Labels from the given labels.
// The caller has to guarantee that all label names are unique.
func New(ls ...Label) Labels {
	set := make(Labels, 0, len(ls))
	for _, l := range ls {
		set = append(set, l)
	}
	sort.Sort(set)

	return set
}

// FromMap returns new sorted Labels from the given map.
func FromMap(m map[string]string) Labels {
	l := make([]Label, 0, len(m))
	for k, v := range m {
		l = append(l, Label{Name: k, Value: v})
	}
	return New(l...)
}

// FromStrings creates new labels from pairs of strings.
func FromStrings(ss ...string) Labels {
	if len(ss)%2 != 0 {
		panic("invalid number of strings")
	}
	res := make(Labels, 0, len(ss)/2)
	for i := 0; i < len(ss); i += 2 {
		res = append(res, Label{Name: ss[i], Value: ss[i+1]})
	}

	sort.Sort(res)
	return res
}

// Compare compares the two label sets.
// The result will be 0 if a==b, <0 if a < b, and >0 if a > b.
func Compare(a, b Labels) int {
	l := len(a)
	if len(b) < l {
		l = len(b)
	}

	for i := 0; i < l; i++ {
		if a[i].Name != b[i].Name {
			if a[i].Name < b[i].Name {
				return -1
			}
			return 1
		}
		if a[i].Value != b[i].Value {
			if a[i].Value < b[i].Value {
				return -1
			}
			return 1
		}
	}
	// If all labels so far were in common, the set with fewer labels comes first.
	return len(a) - len(b)
}

// Builder allows modifying Labels.
type Builder struct {
	base Labels
	del  []string
	add  []Label
}

// NewBuilder returns a new LabelsBuilder.
func NewBuilder(base Labels) *Builder {
	b := &Builder{
		del: make([]string, 0, 5),
		add: make([]Label, 0, 5),
	}
	b.Reset(base)
	return b
}

// Reset clears all current state for the builder.
func (b *Builder) Reset(base Labels) {
	b.base = base
	b.del = b.del[:0]
	b.add = b.add[:0]
	for _, l := range b.base {
		if l.Value == "" {
			b.del = append(b.del, l.Name)
		}
	}
}

// Del deletes the label of the given name.
func (b *Builder) Del(ns ...string) *Builder {
	for _, n := range ns {
		for i, a := range b.add {
			if a.Name == n {
				b.add = append(b.add[:i], b.add[i+1:]...)
			}
		}
		b.del = append(b.del, n)
	}
	return b
}

// Set the name/value pair as a label.
func (b *Builder) Set(n, v string) *Builder {
	if v == "" {
		// Empty labels are the same as missing labels.
		return b.Del(n)
	}
	for i, a := range b.add {
		if a.Name == n {
			b.add[i].Value = v
			return b
		}
	}
	b.add = append(b.add, Label{Name: n, Value: v})

	return b
}

// Labels returns the labels from the builder. If no modifications
// were made, the original labels are returned.
func (b *Builder) Labels() Labels {
	if len(b.del) == 0 && len(b.add) == 0 {
		return b.base
	}

	// In the general case, labels are removed, modified or moved
	// rather than added.
	res := make(Labels, 0, len(b.base))
Outer:
	for _, l := range b.base {
		for _, n := range b.del {
			if l.Name == n {
				continue Outer
			}
		}
		for _, la := range b.add {
			if l.Name == la.Name {
				continue Outer
			}
		}
		res = append(res, l)
	}
	res = append(res, b.add...)
	sort.Sort(res)

	return res
}
<|endoftext|>


<commit_before>package state

import (
	"fmt"
	"sync"
	"time"
)

// TombstoneGC is used to track creation of tombstones so that they can be
// garbage collected after their TTL expires. The tombstones allow queries to
// provide monotonic index values within the TTL window. The GC is used to
// prevent monotonic growth in storage usage. This is a trade off between the
// length of the TTL and the storage overhead.
//
// In practice, this is required to fix the issue of delete visibility. When
// data is deleted from the KV store, the "latest" row can go backwards if the
// newest row is removed. The tombstones provide a way to ensure time doesn't
// move backwards within some interval.
//
type TombstoneGC struct {
	// ttl sets the TTL for tombstones.
	ttl time.Duration

	// granularity determines how we bin TTLs into timers.
	granularity time.Duration

	// enabled controls if we actually setup any timers.
	enabled bool

	// expires maps the time of expiration to the highest tombstone value
	// that should be expired.
	expires map[time.Time]*expireInterval

	// expireCh is used to stream expiration to the leader for processing.
	expireCh chan uint64

	sync.Mutex
}

// expireInterval is used to track the maximum index to expire in a given
// interval with a timer.
type expireInterval struct {
	// maxIndex has the highest tombstone index that should be GC-d.
	maxIndex uint64

	// timer is the timer tracking this bin.
	timer *time.Timer
}

// NewTombstoneGC is used to construct a new TombstoneGC given a TTL for
// tombstones and a tracking granularity. Longer TTLs ensure correct behavior
// for more time, but use more storage. A shorter granularity increases the
// number of Raft transactions and reduce how far past the TTL we perform GC.
func NewTombstoneGC(ttl, granularity time.Duration) (*TombstoneGC, error) {
	// Sanity check the inputs
	if ttl <= 0 || granularity <= 0 {
		return nil, fmt.Errorf("Tombstone TTL and granularity must be positive")
	}

	t := &TombstoneGC{
		ttl:         ttl,
		granularity: granularity,
		enabled:     false,
		expires:     make(map[time.Time]*expireInterval),
		expireCh:    make(chan uint64, 1),
	}
	return t, nil
}

// ExpireCh is used to return a channel that streams the next index that should
// be expired.
func (t *TombstoneGC) ExpireCh() <-chan uint64 {
	return t.expireCh
}

// SetEnabled is used to control if the tombstone GC is
// enabled. Should only be enabled by the leader node.
func (t *TombstoneGC) SetEnabled(enabled bool) {
	t.Lock()
	defer t.Unlock()
	if enabled == t.enabled {
		return
	}

	// Stop all the timers and clear
	if !enabled {
		for _, exp := range t.expires {
			exp.timer.Stop()
		}
		t.expires = make(map[time.Time]*expireInterval)
	}

	// Update the status
	t.enabled = enabled
}

// Hint is used to indicate that keys at the given index have been
// deleted, and that their GC should be scheduled.
func (t *TombstoneGC) Hint(index uint64) {
	expires := t.nextExpires()

	t.Lock()
	defer t.Unlock()
	if !t.enabled {
		return
	}

	// Check for an existing expiration timer and bump its index if we
	// find one.
	exp, ok := t.expires[expires]
	if ok {
		if index > exp.maxIndex {
			exp.maxIndex = index
		}
		return
	}

	// Create a new expiration timer.
	t.expires[expires] = &expireInterval{
		maxIndex: index,
		timer: time.AfterFunc(expires.Sub(time.Now()), func() {
			t.expireTime(expires)
		}),
	}
}

// PendingExpiration is used to check if any expirations are pending.
func (t *TombstoneGC) PendingExpiration() bool {
	t.Lock()
	defer t.Unlock()

	return len(t.expires) > 0
}

// nextExpires is used to calculate the next expiration time, based on the
// granularity that is set. This allows us to bin expirations and avoid a ton
// of timers.
func (t *TombstoneGC) nextExpires() time.Time {
	expires := time.Now().Add(t.ttl)
	remain := expires.UnixNano() % int64(t.granularity)
	adj := expires.Add(t.granularity - time.Duration(remain))
	return adj
}

// expireTime is used to expire the entries at the given time.
func (t *TombstoneGC) expireTime(expires time.Time) {
	t.Lock()
	defer t.Unlock()

	// Get the maximum index and clear the entry. It's possible that the GC
	// has been shut down while this timer fired and got blocked on the lock,
	// so if there's nothing in the map for us we just exit out since there
	// is no work to do.
	exp, ok := t.expires[expires]
	if !ok {
		return
	}
	delete(t.expires, expires)
	t.expireCh <- exp.maxIndex
}
<commit_msg>Gets rid of zero-value initializer.<commit_after>package state

import (
	"fmt"
	"sync"
	"time"
)

// TombstoneGC is used to track creation of tombstones so that they can be
// garbage collected after their TTL expires. The tombstones allow queries to
// provide monotonic index values within the TTL window. The GC is used to
// prevent monotonic growth in storage usage. This is a trade off between the
// length of the TTL and the storage overhead.
//
// In practice, this is required to fix the issue of delete visibility. When
// data is deleted from the KV store, the "latest" row can go backwards if the
// newest row is removed. The tombstones provide a way to ensure time doesn't
// move backwards within some interval.
//
type TombstoneGC struct {
	// ttl sets the TTL for tombstones.
	ttl time.Duration

	// granularity determines how we bin TTLs into timers.
	granularity time.Duration

	// enabled controls if we actually setup any timers.
	enabled bool

	// expires maps the time of expiration to the highest tombstone value
	// that should be expired.
	expires map[time.Time]*expireInterval

	// expireCh is used to stream expiration to the leader for processing.
	expireCh chan uint64

	sync.Mutex
}

// expireInterval is used to track the maximum index to expire in a given
// interval with a timer.
type expireInterval struct {
	// maxIndex has the highest tombstone index that should be GC-d.
	maxIndex uint64

	// timer is the timer tracking this bin.
	timer *time.Timer
}

// NewTombstoneGC is used to construct a new TombstoneGC given a TTL for
// tombstones and a tracking granularity. Longer TTLs ensure correct behavior
// for more time, but use more storage. A shorter granularity increases the
// number of Raft transactions and reduce how far past the TTL we perform GC.
func NewTombstoneGC(ttl, granularity time.Duration) (*TombstoneGC, error) {
	// Sanity check the inputs
	if ttl <= 0 || granularity <= 0 {
		return nil, fmt.Errorf("Tombstone TTL and granularity must be positive")
	}

	t := &TombstoneGC{
		ttl:         ttl,
		granularity: granularity,
		expires:     make(map[time.Time]*expireInterval),
		expireCh:    make(chan uint64, 1),
	}
	return t, nil
}

// ExpireCh is used to return a channel that streams the next index that should
// be expired.
func (t *TombstoneGC) ExpireCh() <-chan uint64 {
	return t.expireCh
}

// SetEnabled is used to control if the tombstone GC is
// enabled. Should only be enabled by the leader node.
func (t *TombstoneGC) SetEnabled(enabled bool) {
	t.Lock()
	defer t.Unlock()
	if enabled == t.enabled {
		return
	}

	// Stop all the timers and clear
	if !enabled {
		for _, exp := range t.expires {
			exp.timer.Stop()
		}
		t.expires = make(map[time.Time]*expireInterval)
	}

	// Update the status
	t.enabled = enabled
}

// Hint is used to indicate that keys at the given index have been
// deleted, and that their GC should be scheduled.
func (t *TombstoneGC) Hint(index uint64) {
	expires := t.nextExpires()

	t.Lock()
	defer t.Unlock()
	if !t.enabled {
		return
	}

	// Check for an existing expiration timer and bump its index if we
	// find one.
	exp, ok := t.expires[expires]
	if ok {
		if index > exp.maxIndex {
			exp.maxIndex = index
		}
		return
	}

	// Create a new expiration timer.
	t.expires[expires] = &expireInterval{
		maxIndex: index,
		timer: time.AfterFunc(expires.Sub(time.Now()), func() {
			t.expireTime(expires)
		}),
	}
}

// PendingExpiration is used to check if any expirations are pending.
func (t *TombstoneGC) PendingExpiration() bool {
	t.Lock()
	defer t.Unlock()

	return len(t.expires) > 0
}

// nextExpires is used to calculate the next expiration time, based on the
// granularity that is set. This allows us to bin expirations and avoid a ton
// of timers.
func (t *TombstoneGC) nextExpires() time.Time {
	expires := time.Now().Add(t.ttl)
	remain := expires.UnixNano() % int64(t.granularity)
	adj := expires.Add(t.granularity - time.Duration(remain))
	return adj
}

// expireTime is used to expire the entries at the given time.
func (t *TombstoneGC) expireTime(expires time.Time) {
	t.Lock()
	defer t.Unlock()

	// Get the maximum index and clear the entry. It's possible that the GC
	// has been shut down while this timer fired and got blocked on the lock,
	// so if there's nothing in the map for us we just exit out since there
	// is no work to do.
	exp, ok := t.expires[expires]
	if !ok {
		return
	}
	delete(t.expires, expires)
	t.expireCh <- exp.maxIndex
}
<|endoftext|>


<commit_before>package modjulian

import "time"

func DayNumber(t time.Time) int64 {
	if t.Sub(time.Unix(0, 0)) >= 0 {
		return dnUnix(t)
	} else {
		return dnGregorian(t)
	}
}

func dnGregorian(t time.Time) int64 {
	y := int64(t.Year())
	m := int64(t.Month())
	if m < 3 {
		y -= 1
		m += 9
	} else {
		m -= 3
	}
	d := int64(t.Day()) - 1
	return (1461*y)/4 + y/400 - y/100 + (153*m+2)/5 + d - 678882
}

func dnUnix(t time.Time) int64 {
	const (
		onday   = int64(86400) //seconds
		baseDay = int64(40587) //Modified Julian Date at January 1, 1970
	)
	return (t.Unix())/onday + baseDay
}
<commit_msg>Update modjulian.go<commit_after>package modjulian

import "time"

func DayNumber(t time.Time) int64 {
	if t.Sub(time.Unix(0, 0)) >= 0 {
		return dnUnix(t)
	} else {
		return dnGregorian(t)
	}
}

func dnGregorian(t time.Time) int64 {
	y := int64(t.Year())
	m := int64(t.Month())
	if m < 3 {
		y -= 1
		m += 9
	} else {
		m -= 3
	}
	d := int64(t.Day()) - 1
	return (1461*y)/4 + y/400 - y/100 + (153*m+2)/5 + d - 678881
}

func dnUnix(t time.Time) int64 {
	const (
		onday   = int64(86400) //seconds
		baseDay = int64(40587) //Modified Julian Date at January 1, 1970
	)
	return (t.Unix())/onday + baseDay
}
<|endoftext|>


<commit_before>package xlog

import (
	"fmt"
	"strings"
	"time"
	"regexp"
)

// Formatter is an interface that provides methods that format log messages.
type Formatter interface {
	SetMessageFormat(messageFormat string)
	SetName(name string)
	Format(level Level, v ...interface{}) string
}

// DefaultFormatter is the default implementation of the Formatter interface.
type DefaultFormatter struct {
	messageFormat string
	dateFormat    string
	name          string
}

// NewDefaultFormatter creates and returns a new DefaultFormatter instance.
func NewDefaultFormatter(messageFormat string) *DefaultFormatter {
	messageFormat, dateFormat := SanitizeForDate(messageFormat)
	return &DefaultFormatter{messageFormat, dateFormat, ""}
}

// SetMessageFormat changes the set message format.
func (f *DefaultFormatter) SetMessageFormat(messageFormat string) {
	f.messageFormat, f.dateFormat = SanitizeForDate(messageFormat)
}

// SetName sets the name of the formatter. The name is available in messages
// using the {name} placeholder.
func (f *DefaultFormatter) SetName(name string) {
	f.name = name
}

// Format formats a log message for the given level.
func (f *DefaultFormatter) Format(level Level, v ...interface{}) string {
	placeholders := map[string]string{
		"{date}": (time.Now()).Format(f.dateFormat),
		"{level}": Levels[level],
		"{message}": fmt.Sprint(v...),
		"{name}": f.name,
	}

	formatted := f.messageFormat
	for placeholder, value := range placeholders {
		formatted = strings.Replace(formatted, placeholder, value, -1)
	}

	return formatted
}

// SanitizeForDate replaces date placeholders containing a date format with
// a plain {date} placeholder. The altered message format is returned, along
// with the found date format.
func SanitizeForDate(messageFormat string) (string, string) {
	regex := regexp.MustCompile(`{date\|([^}]+)}`)
	dateFormat := DefaultDateFormat
	captured := regex.FindStringSubmatch(messageFormat)
	if len(captured) == 2 {
		dateFormat = captured[1]
		messageFormat = regex.ReplaceAllString(messageFormat, "{date}")
	}

	return messageFormat, dateFormat
}

<commit_msg>Added name argument to NewDefaultFormatter<commit_after>package xlog

import (
	"fmt"
	"strings"
	"time"
	"regexp"
)

// Formatter is an interface that provides methods that format log messages.
type Formatter interface {
	SetMessageFormat(messageFormat string)
	SetName(name string)
	Format(level Level, v ...interface{}) string
}

// DefaultFormatter is the default implementation of the Formatter interface.
type DefaultFormatter struct {
	messageFormat string
	dateFormat    string
	name          string
}

// NewDefaultFormatter creates and returns a new DefaultFormatter instance.
func NewDefaultFormatter(messageFormat, name string) *DefaultFormatter {
	messageFormat, dateFormat := SanitizeForDate(messageFormat)
	return &DefaultFormatter{messageFormat, dateFormat, name}
}

// SetMessageFormat changes the set message format.
func (f *DefaultFormatter) SetMessageFormat(messageFormat string) {
	f.messageFormat, f.dateFormat = SanitizeForDate(messageFormat)
}

// SetName sets the name of the formatter. The name is available in messages
// using the {name} placeholder.
func (f *DefaultFormatter) SetName(name string) {
	f.name = name
}

// Format formats a log message for the given level.
func (f *DefaultFormatter) Format(level Level, v ...interface{}) string {
	placeholders := map[string]string{
		"{date}": (time.Now()).Format(f.dateFormat),
		"{level}": Levels[level],
		"{message}": fmt.Sprint(v...),
		"{name}": f.name,
	}

	formatted := f.messageFormat
	for placeholder, value := range placeholders {
		formatted = strings.Replace(formatted, placeholder, value, -1)
	}

	return formatted
}

// SanitizeForDate replaces date placeholders containing a date format with
// a plain {date} placeholder. The altered message format is returned, along
// with the found date format.
func SanitizeForDate(messageFormat string) (string, string) {
	regex := regexp.MustCompile(`{date\|([^}]+)}`)
	dateFormat := DefaultDateFormat
	captured := regex.FindStringSubmatch(messageFormat)
	if len(captured) == 2 {
		dateFormat = captured[1]
		messageFormat = regex.ReplaceAllString(messageFormat, "{date}")
	}

	return messageFormat, dateFormat
}

<|endoftext|>


<commit_before>package models

import (
	"database/sql"
	"errors"
	"time"
)

// ReleaseStatus is a type alias which will be used to create an enum of acceptable release status states.
type ReleaseStatus string

// ReleaseStatus pseudo-enum values
const (
	RStatusReleased ReleaseStatus = "released"
	RStatusDraft    ReleaseStatus = "draft"
)

var (
	RStatuses = []ReleaseStatus{RStatusReleased, RStatusDraft}
)

// Errors pertaining to the data in a Release or operations on Releases.
var (
	ErrInvalidReleaseStatus = errors.New("Invalid release status.")
	ErrNoSuchRelease        = errors.New("Could not find release.")
)

// Database queries for operations on Releases.
const (
	QInitTableReleases string = `create table if not exists releases (
		id int not null auto_increment,
		chapter varchar(255),
		version int,
		status varchar(255),
		checksum varchar(255),
		released_on timestamp,
		project_id int,
		foreign key(project_id) references projects(id),
		primary key(id)
);`

	QSaveRelease string = `insert into releases (
		chapter, version, status, checksum, released_on, project_id
) values (
		?, ?, ?, ?, ?, ?
);`

	QUpdateRelease string = `update releases set
chapter = ?, version = ?, status = ?, checksum = ?, released_on = ?
where id = ?;`

	QDeleteRelease string = `delete from releases where id = ?;`

	QListReleasesDesc string = `select
id, chapter, version, status, checksum, released_on
from releases
where project_id = ?
order by released_on desc;`

	QListReleasesAsc string = `select
id, chapter, version, status, checksum, released_on
from releases
where project_id = ?
order by released_on asc;`

	QFindRelease string = `select
chapter, version, status, checksum, released_on, project_id
from releases
where id = ?;`
)

// Release contains information about a release, which there are many of under a given Project.  It contains information
// about which chapter of manga the release was created for, which version of the release of said chapter it is for, and
// the status of the release of the chapter itself, which may not be final right away.
type Release struct {
	Id         int           `json:"id"`
	Chapter    string        `json:"chapter"`
	Version    int           `json:"version"`
	Status     ReleaseStatus `json:"status"`
	Checksum   string        `json:"checksum"`
	ReleasedOn time.Time     `json:"releasedOn"`
	ProjectID  int           `json:"projectId"`
}

// NewRelease constructs a brand new Release instance, with a default state lacking information its (future) position in
// a database.
func NewRelease(projectId, version int, chapterName string) Release {
	return Release{
		0,
		chapterName,
		version,
		RStatusDraft,
		"",
		time.Now(),
		projectId,
	}
}

// FindRelease attempts to lookup a release by ID.
func FindRelease(id int, db *sql.DB) (Release, error) {
	r := Release{}
	status := ""
	row := db.QueryRow(QFindRelease, id)
	if row == nil {
		return Release{}, ErrNoSuchRelease
	}
	err := row.Scan(&r.Chapter, &r.Version, &status, &r.Checksum, &r.ReleasedOn, &r.ProjectID)
	if err != nil {
		return Release{}, err
	}
	// TODO - Check if the release has a checksum and, if not, compute it.
	r.Id = id
	r.Status = ReleaseStatus(status)
	return r, nil
}

// ListReleases attempts to obtain a list of all of the releases in the database.
func ListReleases(projectId int, ordering string, db *sql.DB) ([]Release, error) {
	releases := []Release{}
	query := QListReleasesDesc
	if ordering == "oldest" {
		query = QListReleasesAsc
	}
	rows, err := db.Query(query, projectId)
	if err != nil {
		return []Release{}, err
	}
	defer rows.Close()
	for rows.Next() {
		var id, version int
		var chapter, status, checksum string
		var released time.Time
		scanErr := rows.Scan(&id, &chapter, &version, &status, &checksum, &released)
		if scanErr != nil {
			err = scanErr
		}
		releases = append(releases, Release{
			id,
			chapter,
			version,
			ReleaseStatus(status),
			checksum,
			released,
			projectId,
		})
	}
	return releases, err
}

// Validate checks that the "status" of the project is one of the accepted ReleaseStatus values.
func (r *Release) Validate() error {
	for _, status := range RStatuses {
		if r.Status == status {
			return nil
		}
	}
	return ErrInvalidReleaseStatus
}

// Save inserts the release into the database and updates its Id field.
func (r *Release) Save(db *sql.DB) error {
	validErr := r.Validate()
	if validErr != nil {
		return validErr
	}
	// TODO - Where should we compute checksums?
	_, err := db.Exec(QSaveRelease, r.Chapter, r.Version, string(r.Status), r.Checksum, r.ReleasedOn, r.ProjectID)
	if err != nil {
		return err
	}
	row := db.QueryRow(QLastInsertID)
	if row == nil {
		return ErrCouldNotGetID
	}
	return row.Scan(&r.Id)
}

// Update modifies all of the fields of a Release in place with whatever is currently in the struct.
func (r *Release) Update(db *sql.DB) error {
	now := time.Now()
	_, err := db.Exec(QUpdateRelease, r.Chapter, r.Version, string(r.Status), r.Checksum, now, r.Id)
	r.ReleasedOn = now
	return err
}

// Delete removes the Release and all associated pages from the database.
func (r *Release) Delete(db *sql.DB) error {
	pages, listErr := ListPages(r.Id, db)
	var deleteErr error
	for _, page := range pages {
		dErr := page.Delete(db)
		if dErr != nil {
			deleteErr = dErr
		}
	}
	_, err := db.Exec(QDeleteRelease, r.Id)
	if err != nil {
		return err
	}
	if listErr != nil {
		return listErr
	}
	return deleteErr
}
<commit_msg>Validate the new fields of a release before applying updates<commit_after>package models

import (
	"database/sql"
	"errors"
	"time"
)

// ReleaseStatus is a type alias which will be used to create an enum of acceptable release status states.
type ReleaseStatus string

// ReleaseStatus pseudo-enum values
const (
	RStatusReleased ReleaseStatus = "released"
	RStatusDraft    ReleaseStatus = "draft"
)

var (
	RStatuses = []ReleaseStatus{RStatusReleased, RStatusDraft}
)

// Errors pertaining to the data in a Release or operations on Releases.
var (
	ErrInvalidReleaseStatus = errors.New("Invalid release status.")
	ErrNoSuchRelease        = errors.New("Could not find release.")
)

// Database queries for operations on Releases.
const (
	QInitTableReleases string = `create table if not exists releases (
		id int not null auto_increment,
		chapter varchar(255),
		version int,
		status varchar(255),
		checksum varchar(255),
		released_on timestamp,
		project_id int,
		foreign key(project_id) references projects(id),
		primary key(id)
);`

	QSaveRelease string = `insert into releases (
		chapter, version, status, checksum, released_on, project_id
) values (
		?, ?, ?, ?, ?, ?
);`

	QUpdateRelease string = `update releases set
chapter = ?, version = ?, status = ?, checksum = ?, released_on = ?
where id = ?;`

	QDeleteRelease string = `delete from releases where id = ?;`

	QListReleasesDesc string = `select
id, chapter, version, status, checksum, released_on
from releases
where project_id = ?
order by released_on desc;`

	QListReleasesAsc string = `select
id, chapter, version, status, checksum, released_on
from releases
where project_id = ?
order by released_on asc;`

	QFindRelease string = `select
chapter, version, status, checksum, released_on, project_id
from releases
where id = ?;`
)

// Release contains information about a release, which there are many of under a given Project.  It contains information
// about which chapter of manga the release was created for, which version of the release of said chapter it is for, and
// the status of the release of the chapter itself, which may not be final right away.
type Release struct {
	Id         int           `json:"id"`
	Chapter    string        `json:"chapter"`
	Version    int           `json:"version"`
	Status     ReleaseStatus `json:"status"`
	Checksum   string        `json:"checksum"`
	ReleasedOn time.Time     `json:"releasedOn"`
	ProjectID  int           `json:"projectId"`
}

// NewRelease constructs a brand new Release instance, with a default state lacking information its (future) position in
// a database.
func NewRelease(projectId, version int, chapterName string) Release {
	return Release{
		0,
		chapterName,
		version,
		RStatusDraft,
		"",
		time.Now(),
		projectId,
	}
}

// FindRelease attempts to lookup a release by ID.
func FindRelease(id int, db *sql.DB) (Release, error) {
	r := Release{}
	status := ""
	row := db.QueryRow(QFindRelease, id)
	if row == nil {
		return Release{}, ErrNoSuchRelease
	}
	err := row.Scan(&r.Chapter, &r.Version, &status, &r.Checksum, &r.ReleasedOn, &r.ProjectID)
	if err != nil {
		return Release{}, err
	}
	// TODO - Check if the release has a checksum and, if not, compute it.
	r.Id = id
	r.Status = ReleaseStatus(status)
	return r, nil
}

// ListReleases attempts to obtain a list of all of the releases in the database.
func ListReleases(projectId int, ordering string, db *sql.DB) ([]Release, error) {
	releases := []Release{}
	query := QListReleasesDesc
	if ordering == "oldest" {
		query = QListReleasesAsc
	}
	rows, err := db.Query(query, projectId)
	if err != nil {
		return []Release{}, err
	}
	defer rows.Close()
	for rows.Next() {
		var id, version int
		var chapter, status, checksum string
		var released time.Time
		scanErr := rows.Scan(&id, &chapter, &version, &status, &checksum, &released)
		if scanErr != nil {
			err = scanErr
		}
		releases = append(releases, Release{
			id,
			chapter,
			version,
			ReleaseStatus(status),
			checksum,
			released,
			projectId,
		})
	}
	return releases, err
}

// Validate checks that the "status" of the project is one of the accepted ReleaseStatus values.
func (r *Release) Validate() error {
	for _, status := range RStatuses {
		if r.Status == status {
			return nil
		}
	}
	return ErrInvalidReleaseStatus
}

// Save inserts the release into the database and updates its Id field.
func (r *Release) Save(db *sql.DB) error {
	validErr := r.Validate()
	if validErr != nil {
		return validErr
	}
	// TODO - Where should we compute checksums?
	_, err := db.Exec(QSaveRelease, r.Chapter, r.Version, string(r.Status), r.Checksum, r.ReleasedOn, r.ProjectID)
	if err != nil {
		return err
	}
	row := db.QueryRow(QLastInsertID)
	if row == nil {
		return ErrCouldNotGetID
	}
	return row.Scan(&r.Id)
}

// Update modifies all of the fields of a Release in place with whatever is currently in the struct.
func (r *Release) Update(db *sql.DB) error {
	validErr := r.Validate()
	if validErr != nil {
		return validErr
	}
	now := time.Now()
	_, err := db.Exec(QUpdateRelease, r.Chapter, r.Version, string(r.Status), r.Checksum, now, r.Id)
	r.ReleasedOn = now
	return err
}

// Delete removes the Release and all associated pages from the database.
func (r *Release) Delete(db *sql.DB) error {
	pages, listErr := ListPages(r.Id, db)
	var deleteErr error
	for _, page := range pages {
		dErr := page.Delete(db)
		if dErr != nil {
			deleteErr = dErr
		}
	}
	_, err := db.Exec(QDeleteRelease, r.Id)
	if err != nil {
		return err
	}
	if listErr != nil {
		return listErr
	}
	return deleteErr
}
<|endoftext|>


<commit_before>package api

import (
	"archive/zip"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"mime/multipart"
	gonet "net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/cloudfoundry/cli/cf/appfiles"
	"github.com/cloudfoundry/cli/cf/configuration/coreconfig"
	"github.com/cloudfoundry/cli/cf/errors"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/net"
	"github.com/cloudfoundry/gofileutils/fileutils"
)

//go:generate counterfeiter . BuildpackBitsRepository

type BuildpackBitsRepository interface {
	UploadBuildpack(buildpack models.Buildpack, dir string) (apiErr error)
}

type CloudControllerBuildpackBitsRepository struct {
	config       coreconfig.Reader
	gateway      net.Gateway
	zipper       appfiles.Zipper
	TrustedCerts []tls.Certificate
}

func NewCloudControllerBuildpackBitsRepository(config coreconfig.Reader, gateway net.Gateway, zipper appfiles.Zipper) (repo CloudControllerBuildpackBitsRepository) {
	repo.config = config
	repo.gateway = gateway
	repo.zipper = zipper
	return
}

func (repo CloudControllerBuildpackBitsRepository) UploadBuildpack(buildpack models.Buildpack, buildpackLocation string) (apiErr error) {
	fileutils.TempFile("buildpack-upload", func(zipFileToUpload *os.File, err error) {
		if err != nil {
			apiErr = fmt.Errorf("%s: %s", T("Couldn't create temp file for upload"), err.Error())
			return
		}

		var buildpackFileName string
		if isWebURL(buildpackLocation) {
			buildpackFileName = path.Base(buildpackLocation)
			repo.downloadBuildpack(buildpackLocation, func(downloadFile *os.File, downloadErr error) {
				if downloadErr != nil {
					err = downloadErr
					return
				}

				err = normalizeBuildpackArchive(downloadFile, zipFileToUpload)
			})
		} else {
			buildpackFileName = filepath.Base(buildpackLocation)

			stats, statError := os.Stat(buildpackLocation)
			if statError != nil {
				apiErr = fmt.Errorf("%s: %s", T("Error opening buildpack file"), statError.Error())
				err = statError
				return
			}

			if stats.IsDir() {
				buildpackFileName += ".zip" // FIXME: remove once #71167394 is fixed
				err = repo.zipper.Zip(buildpackLocation, zipFileToUpload)
			} else {
				specifiedFile, openError := os.Open(buildpackLocation)
				if openError != nil {
					apiErr = fmt.Errorf("%s: %s", T("Couldn't open buildpack file"), openError.Error())
					err = openError
					return
				}
				err = normalizeBuildpackArchive(specifiedFile, zipFileToUpload)
			}
		}

		if err != nil {
			apiErr = fmt.Errorf("%s: %s", T("Couldn't write zip file"), err.Error())
			return
		}

		apiErr = repo.uploadBits(buildpack, zipFileToUpload, buildpackFileName)
	})

	return
}

func normalizeBuildpackArchive(inputFile *os.File, outputFile *os.File) error {
	stats, toplevelErr := inputFile.Stat()
	if toplevelErr != nil {
		return toplevelErr
	}

	reader, toplevelErr := zip.NewReader(inputFile, stats.Size())
	if toplevelErr != nil {
		return toplevelErr
	}

	contents := reader.File

	parentPath, hasBuildpack := findBuildpackPath(contents)

	if !hasBuildpack {
		return errors.New(T("Zip archive does not contain a buildpack"))
	}

	writer := zip.NewWriter(outputFile)

	for _, file := range contents {
		name := file.Name
		if strings.HasPrefix(name, parentPath) {
			relativeFilename := strings.TrimPrefix(name, parentPath+"/")
			if relativeFilename == "" {
				continue
			}

			fileInfo := file.FileInfo()
			header, err := zip.FileInfoHeader(fileInfo)
			if err != nil {
				return err
			}
			header.Name = relativeFilename

			w, err := writer.CreateHeader(header)
			if err != nil {
				return err
			}

			r, err := file.Open()
			if err != nil {
				return err
			}

			_, err = io.Copy(w, r)
			if err != nil {
				return err
			}

			err = r.Close()
			if err != nil {
				return err
			}
		}
	}

	toplevelErr = writer.Close()
	if toplevelErr != nil {
		return toplevelErr
	}

	_, toplevelErr = outputFile.Seek(0, 0)
	if toplevelErr != nil {
		return toplevelErr
	}

	return nil
}

func findBuildpackPath(zipFiles []*zip.File) (parentPath string, foundBuildpack bool) {
	needle := "bin/compile"

	for _, file := range zipFiles {
		if strings.HasSuffix(file.Name, needle) {
			foundBuildpack = true
			parentPath = path.Join(file.Name, "..", "..")
			if parentPath == "." {
				parentPath = ""
			}
			return
		}
	}
	return
}

func isWebURL(path string) bool {
	return strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://")
}

func (repo CloudControllerBuildpackBitsRepository) downloadBuildpack(url string, cb func(*os.File, error)) {
	fileutils.TempFile("buildpack-download", func(tempfile *os.File, err error) {
		if err != nil {
			cb(nil, err)
			return
		}

		var certPool *x509.CertPool
		if len(repo.TrustedCerts) > 0 {
			certPool = x509.NewCertPool()
			for _, tlsCert := range repo.TrustedCerts {
				cert, _ := x509.ParseCertificate(tlsCert.Certificate[0])
				certPool.AddCert(cert)
			}
		}

		client := &http.Client{
			Transport: &http.Transport{
				Dial:            (&gonet.Dialer{Timeout: 5 * time.Second}).Dial,
				TLSClientConfig: &tls.Config{RootCAs: certPool},
				Proxy:           http.ProxyFromEnvironment,
			},
		}

		response, err := client.Get(url)
		if err != nil {
			cb(nil, err)
			return
		}
		defer response.Body.Close()

		_, err = io.Copy(tempfile, response.Body)
		if err != nil {
			cb(nil, err)
			return
		}

		_, err = tempfile.Seek(0, 0)
		if err != nil {
			cb(nil, err)
			return
		}

		cb(tempfile, nil)
	})
}

func (repo CloudControllerBuildpackBitsRepository) uploadBits(buildpack models.Buildpack, body io.Reader, buildpackName string) error {
	return repo.performMultiPartUpload(
		fmt.Sprintf("%s/v2/buildpacks/%s/bits", repo.config.APIEndpoint(), buildpack.GUID),
		"buildpack",
		buildpackName,
		body)
}

func (repo CloudControllerBuildpackBitsRepository) performMultiPartUpload(url string, fieldName string, fileName string, body io.Reader) error {
	var capturedErr error

	fileutils.TempFile("requests", func(requestFile *os.File, err error) {
		if err != nil {
			capturedErr = err
			return
		}

		writer := multipart.NewWriter(requestFile)
		part, err := writer.CreateFormFile(fieldName, fileName)

		if err != nil {
			_ = writer.Close()
			capturedErr = err
			return
		}

		_, err = io.Copy(part, body)
		_ = writer.Close()

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

		var request *net.Request
		request, err = repo.gateway.NewRequestForFile("PUT", url, repo.config.AccessToken(), requestFile)
		contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())
		request.HTTPReq.Header.Set("Content-Type", contentType)
		if err != nil {
			capturedErr = err
			return
		}

		_, err = repo.gateway.PerformRequest(request)
		if err != nil {
			capturedErr = err
		}
	})

	return capturedErr
}
<commit_msg>Handle writer.Close errors in cf/api<commit_after>package api

import (
	"archive/zip"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"mime/multipart"
	gonet "net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/cloudfoundry/cli/cf/appfiles"
	"github.com/cloudfoundry/cli/cf/configuration/coreconfig"
	"github.com/cloudfoundry/cli/cf/errors"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/net"
	"github.com/cloudfoundry/gofileutils/fileutils"
)

//go:generate counterfeiter . BuildpackBitsRepository

type BuildpackBitsRepository interface {
	UploadBuildpack(buildpack models.Buildpack, dir string) (apiErr error)
}

type CloudControllerBuildpackBitsRepository struct {
	config       coreconfig.Reader
	gateway      net.Gateway
	zipper       appfiles.Zipper
	TrustedCerts []tls.Certificate
}

func NewCloudControllerBuildpackBitsRepository(config coreconfig.Reader, gateway net.Gateway, zipper appfiles.Zipper) (repo CloudControllerBuildpackBitsRepository) {
	repo.config = config
	repo.gateway = gateway
	repo.zipper = zipper
	return
}

func (repo CloudControllerBuildpackBitsRepository) UploadBuildpack(buildpack models.Buildpack, buildpackLocation string) (apiErr error) {
	fileutils.TempFile("buildpack-upload", func(zipFileToUpload *os.File, err error) {
		if err != nil {
			apiErr = fmt.Errorf("%s: %s", T("Couldn't create temp file for upload"), err.Error())
			return
		}

		var buildpackFileName string
		if isWebURL(buildpackLocation) {
			buildpackFileName = path.Base(buildpackLocation)
			repo.downloadBuildpack(buildpackLocation, func(downloadFile *os.File, downloadErr error) {
				if downloadErr != nil {
					err = downloadErr
					return
				}

				err = normalizeBuildpackArchive(downloadFile, zipFileToUpload)
			})
		} else {
			buildpackFileName = filepath.Base(buildpackLocation)

			stats, statError := os.Stat(buildpackLocation)
			if statError != nil {
				apiErr = fmt.Errorf("%s: %s", T("Error opening buildpack file"), statError.Error())
				err = statError
				return
			}

			if stats.IsDir() {
				buildpackFileName += ".zip" // FIXME: remove once #71167394 is fixed
				err = repo.zipper.Zip(buildpackLocation, zipFileToUpload)
			} else {
				specifiedFile, openError := os.Open(buildpackLocation)
				if openError != nil {
					apiErr = fmt.Errorf("%s: %s", T("Couldn't open buildpack file"), openError.Error())
					err = openError
					return
				}
				err = normalizeBuildpackArchive(specifiedFile, zipFileToUpload)
			}
		}

		if err != nil {
			apiErr = fmt.Errorf("%s: %s", T("Couldn't write zip file"), err.Error())
			return
		}

		apiErr = repo.uploadBits(buildpack, zipFileToUpload, buildpackFileName)
	})

	return
}

func normalizeBuildpackArchive(inputFile *os.File, outputFile *os.File) error {
	stats, toplevelErr := inputFile.Stat()
	if toplevelErr != nil {
		return toplevelErr
	}

	reader, toplevelErr := zip.NewReader(inputFile, stats.Size())
	if toplevelErr != nil {
		return toplevelErr
	}

	contents := reader.File

	parentPath, hasBuildpack := findBuildpackPath(contents)

	if !hasBuildpack {
		return errors.New(T("Zip archive does not contain a buildpack"))
	}

	writer := zip.NewWriter(outputFile)

	for _, file := range contents {
		name := file.Name
		if strings.HasPrefix(name, parentPath) {
			relativeFilename := strings.TrimPrefix(name, parentPath+"/")
			if relativeFilename == "" {
				continue
			}

			fileInfo := file.FileInfo()
			header, err := zip.FileInfoHeader(fileInfo)
			if err != nil {
				return err
			}
			header.Name = relativeFilename

			w, err := writer.CreateHeader(header)
			if err != nil {
				return err
			}

			r, err := file.Open()
			if err != nil {
				return err
			}

			_, err = io.Copy(w, r)
			if err != nil {
				return err
			}

			err = r.Close()
			if err != nil {
				return err
			}
		}
	}

	toplevelErr = writer.Close()
	if toplevelErr != nil {
		return toplevelErr
	}

	_, toplevelErr = outputFile.Seek(0, 0)
	if toplevelErr != nil {
		return toplevelErr
	}

	return nil
}

func findBuildpackPath(zipFiles []*zip.File) (parentPath string, foundBuildpack bool) {
	needle := "bin/compile"

	for _, file := range zipFiles {
		if strings.HasSuffix(file.Name, needle) {
			foundBuildpack = true
			parentPath = path.Join(file.Name, "..", "..")
			if parentPath == "." {
				parentPath = ""
			}
			return
		}
	}
	return
}

func isWebURL(path string) bool {
	return strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://")
}

func (repo CloudControllerBuildpackBitsRepository) downloadBuildpack(url string, cb func(*os.File, error)) {
	fileutils.TempFile("buildpack-download", func(tempfile *os.File, err error) {
		if err != nil {
			cb(nil, err)
			return
		}

		var certPool *x509.CertPool
		if len(repo.TrustedCerts) > 0 {
			certPool = x509.NewCertPool()
			for _, tlsCert := range repo.TrustedCerts {
				cert, _ := x509.ParseCertificate(tlsCert.Certificate[0])
				certPool.AddCert(cert)
			}
		}

		client := &http.Client{
			Transport: &http.Transport{
				Dial:            (&gonet.Dialer{Timeout: 5 * time.Second}).Dial,
				TLSClientConfig: &tls.Config{RootCAs: certPool},
				Proxy:           http.ProxyFromEnvironment,
			},
		}

		response, err := client.Get(url)
		if err != nil {
			cb(nil, err)
			return
		}
		defer response.Body.Close()

		_, err = io.Copy(tempfile, response.Body)
		if err != nil {
			cb(nil, err)
			return
		}

		_, err = tempfile.Seek(0, 0)
		if err != nil {
			cb(nil, err)
			return
		}

		cb(tempfile, nil)
	})
}

func (repo CloudControllerBuildpackBitsRepository) uploadBits(buildpack models.Buildpack, body io.Reader, buildpackName string) error {
	return repo.performMultiPartUpload(
		fmt.Sprintf("%s/v2/buildpacks/%s/bits", repo.config.APIEndpoint(), buildpack.GUID),
		"buildpack",
		buildpackName,
		body)
}

func (repo CloudControllerBuildpackBitsRepository) performMultiPartUpload(url string, fieldName string, fileName string, body io.Reader) error {
	var capturedErr error

	fileutils.TempFile("requests", func(requestFile *os.File, err error) {
		if err != nil {
			capturedErr = err
			return
		}

		writer := multipart.NewWriter(requestFile)
		part, err := writer.CreateFormFile(fieldName, fileName)

		if err != nil {
			_ = writer.Close()
			capturedErr = err
			return
		}

		_, err = io.Copy(part, body)
		if err != nil {
			capturedErr = fmt.Errorf("%s: %s", T("Error creating upload"), err.Error())
			return
		}

		err = writer.Close()
		if err != nil {
			capturedErr = err
			return
		}

		var request *net.Request
		request, err = repo.gateway.NewRequestForFile("PUT", url, repo.config.AccessToken(), requestFile)
		if err != nil {
			capturedErr = err
			return
		}

		contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())
		request.HTTPReq.Header.Set("Content-Type", contentType)

		_, err = repo.gateway.PerformRequest(request)
		if err != nil {
			capturedErr = err
		}
	})

	return capturedErr
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/animenotifier/mal"

	"github.com/animenotifier/arn"
	"github.com/animenotifier/mal/parser"
	"github.com/fatih/color"
)

func main() {
	color.Yellow("Importing MAL anime")

	defer color.Green("Finished.")
	defer arn.Node.Close()

	// readFile("../mal-download/files/anime-31240.html")

	filepath.Walk("../mal-download/files", func(name string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Println(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		if !strings.HasSuffix(name, ".html") {
			return nil
		}

		return readFile(name)
	})
}

func readFile(name string) error {
	file, err := os.Open(name)
	defer file.Close()

	if err != nil {
		fmt.Println(err)
		return err
	}

	anime, characters, err := malparser.ParseAnime(file)

	if err != nil {
		fmt.Println(err)
		return err
	}

	if anime.ID == "" {
		return errors.New("Empty ID")
	}

	for _, character := range characters {
		obj, err := arn.MAL.Get("Character", character.ID)

		if err != nil {
			arn.MAL.Set("Character", character.ID, character)
			continue
		}

		existing := obj.(*mal.Character)
		modified := false

		if existing.Name != character.Name {
			existing.Name = character.Name
			modified = true
		}

		if existing.ImagePath != character.ImagePath {
			existing.ImagePath = character.ImagePath
			modified = true
		}

		if modified {
			arn.MAL.Set("Character", existing.ID, existing)
		}
	}

	for _, character := range anime.Characters {
		fmt.Println(character)
	}

	// fmt.Println(anime.ID, anime.Title)
	arn.MAL.Set("Anime", anime.ID, anime)
	return nil
}

// prettyPrint prints the object as indented JSON data on the console.
func prettyPrint(obj interface{}) {
	pretty, _ := json.MarshalIndent(obj, "", "\t")
	fmt.Println(string(pretty))
}
<commit_msg>Reduced import log<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/animenotifier/mal"

	"github.com/animenotifier/arn"
	"github.com/animenotifier/mal/parser"
	"github.com/fatih/color"
)

func main() {
	color.Yellow("Importing MAL anime")

	defer color.Green("Finished.")
	defer arn.Node.Close()

	// readFile("../mal-download/files/anime-31240.html")

	filepath.Walk("../mal-download/files", func(name string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Println(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		if !strings.HasSuffix(name, ".html") {
			return nil
		}

		return readFile(name)
	})
}

func readFile(name string) error {
	file, err := os.Open(name)
	defer file.Close()

	if err != nil {
		fmt.Println(err)
		return err
	}

	anime, characters, err := malparser.ParseAnime(file)

	if err != nil {
		fmt.Println(err)
		return err
	}

	if anime.ID == "" {
		return errors.New("Empty ID")
	}

	for _, character := range characters {
		obj, err := arn.MAL.Get("Character", character.ID)

		if err != nil {
			arn.MAL.Set("Character", character.ID, character)
			continue
		}

		existing := obj.(*mal.Character)
		modified := false

		if existing.Name != character.Name {
			existing.Name = character.Name
			modified = true
		}

		if existing.ImagePath != character.ImagePath {
			existing.ImagePath = character.ImagePath
			modified = true
		}

		if modified {
			arn.MAL.Set("Character", existing.ID, existing)
		}
	}

	fmt.Println(anime.ID, anime.Title)
	arn.MAL.Set("Anime", anime.ID, anime)
	return nil
}

// prettyPrint prints the object as indented JSON data on the console.
func prettyPrint(obj interface{}) {
	pretty, _ := json.MarshalIndent(obj, "", "\t")
	fmt.Println(string(pretty))
}
<|endoftext|>