<commit_before>package main

import (
	"fmt"
	"math"
	"net/http"
	"net/url"
	"os"
	"rais/src/iiif"
	"rais/src/magick"
	"rais/src/openjpeg"
	"rais/src/plugins"
	"rais/src/version"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/hashicorp/golang-lru"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/uoregon-libraries/gopkg/interrupts"
	"github.com/uoregon-libraries/gopkg/logger"
)

var tilePath string
var infoCache *lru.Cache
var tileCache *lru.TwoQueueCache

// Logger is the server's central logger.Logger instance
var Logger *logger.Logger

const defaultAddress = ":12415"
const defaultInfoCacheLen = 10000

// cacheHits and cacheMisses allow some rudimentary tracking of cache value
var cacheHits, cacheMisses int64

var defaultLogLevel = logger.Debug.String()

func main() {
	// Defaults
	viper.SetDefault("Address", defaultAddress)
	viper.SetDefault("InfoCacheLen", defaultInfoCacheLen)
	viper.SetDefault("LogLevel", defaultLogLevel)

	// Allow all configuration to be in environment variables
	viper.SetEnvPrefix("RAIS")
	viper.AutomaticEnv()

	// Config file options
	viper.SetConfigName("rais")
	viper.AddConfigPath("/etc")
	viper.AddConfigPath(".")
	viper.ReadInConfig()

	// CLI flags
	pflag.String("iiif-url", "", `Base URL for serving IIIF requests, e.g., "http://example.com/images/iiif"`)
	viper.BindPFlag("IIIFURL", pflag.CommandLine.Lookup("iiif-url"))
	pflag.String("address", defaultAddress, "http service address")
	viper.BindPFlag("Address", pflag.CommandLine.Lookup("address"))
	pflag.String("tile-path", "", "Base path for images")
	viper.BindPFlag("TilePath", pflag.CommandLine.Lookup("tile-path"))
	pflag.Int("iiif-info-cache-size", defaultInfoCacheLen, "Maximum cached image info entries (IIIF only)")
	viper.BindPFlag("InfoCacheLen", pflag.CommandLine.Lookup("iiif-info-cache-size"))
	pflag.String("capabilities-file", "", "TOML file describing capabilities, rather than everything RAIS supports")
	viper.BindPFlag("CapabilitiesFile", pflag.CommandLine.Lookup("capabilities-file"))
	pflag.String("log-level", defaultLogLevel, "Log level: the server will only log notifications at "+
		"this level and above (must be DEBUG, INFO, WARN, ERROR, or CRIT)")
	viper.BindPFlag("LogLevel", pflag.CommandLine.Lookup("log-level"))
	pflag.Int64("image-max-area", math.MaxInt64, "Maximum area (w x h) of images to be served")
	viper.BindPFlag("ImageMaxArea", pflag.CommandLine.Lookup("image-max-area"))
	pflag.Int("image-max-width", math.MaxInt32, "Maximum width of images to be served")
	viper.BindPFlag("ImageMaxWidth", pflag.CommandLine.Lookup("image-max-width"))
	pflag.Int("image-max-height", math.MaxInt32, "Maximum height of images to be served")
	viper.BindPFlag("ImageMaxHeight", pflag.CommandLine.Lookup("image-max-height"))

	pflag.Parse()

	// Make sure required values exist
	if !viper.IsSet("TilePath") {
		fmt.Println("ERROR: --tile-path is required")
		pflag.Usage()
		os.Exit(1)
	}

	// Make sure we have a valid log level
	var level = logger.LogLevelFromString(viper.GetString("LogLevel"))
	if level == logger.Invalid {
		fmt.Println("ERROR: --log-level must be DEBUG, INFO, WARN, ERROR, or CRIT")
		pflag.Usage()
		os.Exit(1)
	}
	Logger = logger.New(level)
	openjpeg.Logger = Logger
	magick.Logger = Logger

	LoadPlugins(Logger)

	// Pull all values we need for all cases
	tilePath = viper.GetString("TilePath")
	address := viper.GetString("Address")

	ih := NewImageHandler(tilePath)
	ih.Maximums.Area = viper.GetInt64("ImageMaxArea")
	ih.Maximums.Width = viper.GetInt("ImageMaxWidth")
	ih.Maximums.Height = viper.GetInt("ImageMaxHeight")

	// Handle IIIF data only if we have a IIIF URL
	iiifURL := viper.GetString("IIIFURL")
	if iiifURL == "" {
		fmt.Println("ERROR: --iiif-url must be set to the server's public URL")
		pflag.Usage()
		os.Exit(1)
	}

	Logger.Debugf("Attempting to start up IIIF at %s", viper.GetString("IIIFURL"))
	iiifBase, err := url.Parse(iiifURL)
	if err == nil && iiifBase.Scheme == "" {
		err = fmt.Errorf("empty scheme")
	}
	if err == nil && iiifBase.Host == "" {
		err = fmt.Errorf("empty host")
	}
	if err == nil && iiifBase.Path == "" {
		err = fmt.Errorf("empty path")
	}
	if err != nil {
		Logger.Fatalf("Invalid IIIF URL (%s) specified: %s", iiifURL, err)
	}

	icl := viper.GetInt("InfoCacheLen")
	if icl > 0 {
		infoCache, err = lru.New(icl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	tcl := viper.GetInt("TileCacheLen")
	if tcl > 0 {
		Logger.Debugf("Creating a tile cache to hold up to %d tiles", tcl)
		tileCache, err = lru.New2Q(tcl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	Logger.Infof("IIIF enabled at %s", iiifBase.String())
	ih.EnableIIIF(iiifBase)

	capfile := viper.GetString("CapabilitiesFile")
	if capfile != "" {
		ih.FeatureSet = &iiif.FeatureSet{}
		_, err := toml.DecodeFile(capfile, &ih.FeatureSet)
		if err != nil {
			Logger.Fatalf("Invalid file or formatting in capabilities file '%s'", capfile)
		}
		Logger.Debugf("Setting IIIF capabilities from file '%s'", capfile)
	}

	handle(ih.IIIFBase.Path+"/", http.HandlerFunc(ih.IIIFRoute))
	handle("/images/dzi/", http.HandlerFunc(ih.DZIRoute))
	handle("/version", http.HandlerFunc(VersionHandler))

	if tileCache != nil {
		go func() {
			for {
				time.Sleep(time.Minute * 10)
				Logger.Infof("Cache hits: %d; cache misses: %d", cacheHits, cacheMisses)
			}
		}()
	}

	Logger.Infof("RAIS v%s starting...", version.Version)
	var srv = &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 30 * time.Second,
		Addr:         address,
	}

	interrupts.TrapIntTerm(func() {
		Logger.Infof("Stopping RAIS...")
		srv.Shutdown(nil)

		if len(teardownPlugins) > 0 {
			Logger.Infof("Tearing down plugins")
			for _, plug := range teardownPlugins {
				plug()
			}
			Logger.Infof("Plugin teardown complete")
		}

		Logger.Infof("Stopped")
	})

	if err := srv.ListenAndServe(); err != nil {
		// Don't report a fatal error when we close the server
		if err != http.ErrServerClosed {
			Logger.Fatalf("Error starting listener: %s", err)
		}
	}
}

// handle sends the pattern and raw handler to plugins, and sets up routing on
// whatever is returned (if anything).  All plugins which wrap handlers are
// allowed to run, but the behavior could definitely get weird depending on
// what a given plugin does.  Ye be warned.
func handle(pattern string, handler http.Handler) {
	for _, plug := range wrapHandlerPlugins {
		var h2, err = plug(pattern, handler)
		if err != plugins.ErrSkipped {
			logger.Fatalf("Error trying to wrap handler %q: %s", pattern, err)
		}
		if err == nil {
			handler = h2
		}
	}
	http.Handle(pattern, handler)
}

// VersionHandler spits out the raw version string to the browser
func VersionHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(version.Version))
}
<commit_msg>rais-server: fix handle wrapper plugin loop<commit_after>package main

import (
	"fmt"
	"math"
	"net/http"
	"net/url"
	"os"
	"rais/src/iiif"
	"rais/src/magick"
	"rais/src/openjpeg"
	"rais/src/plugins"
	"rais/src/version"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/hashicorp/golang-lru"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/uoregon-libraries/gopkg/interrupts"
	"github.com/uoregon-libraries/gopkg/logger"
)

var tilePath string
var infoCache *lru.Cache
var tileCache *lru.TwoQueueCache

// Logger is the server's central logger.Logger instance
var Logger *logger.Logger

const defaultAddress = ":12415"
const defaultInfoCacheLen = 10000

// cacheHits and cacheMisses allow some rudimentary tracking of cache value
var cacheHits, cacheMisses int64

var defaultLogLevel = logger.Debug.String()

func main() {
	// Defaults
	viper.SetDefault("Address", defaultAddress)
	viper.SetDefault("InfoCacheLen", defaultInfoCacheLen)
	viper.SetDefault("LogLevel", defaultLogLevel)

	// Allow all configuration to be in environment variables
	viper.SetEnvPrefix("RAIS")
	viper.AutomaticEnv()

	// Config file options
	viper.SetConfigName("rais")
	viper.AddConfigPath("/etc")
	viper.AddConfigPath(".")
	viper.ReadInConfig()

	// CLI flags
	pflag.String("iiif-url", "", `Base URL for serving IIIF requests, e.g., "http://example.com/images/iiif"`)
	viper.BindPFlag("IIIFURL", pflag.CommandLine.Lookup("iiif-url"))
	pflag.String("address", defaultAddress, "http service address")
	viper.BindPFlag("Address", pflag.CommandLine.Lookup("address"))
	pflag.String("tile-path", "", "Base path for images")
	viper.BindPFlag("TilePath", pflag.CommandLine.Lookup("tile-path"))
	pflag.Int("iiif-info-cache-size", defaultInfoCacheLen, "Maximum cached image info entries (IIIF only)")
	viper.BindPFlag("InfoCacheLen", pflag.CommandLine.Lookup("iiif-info-cache-size"))
	pflag.String("capabilities-file", "", "TOML file describing capabilities, rather than everything RAIS supports")
	viper.BindPFlag("CapabilitiesFile", pflag.CommandLine.Lookup("capabilities-file"))
	pflag.String("log-level", defaultLogLevel, "Log level: the server will only log notifications at "+
		"this level and above (must be DEBUG, INFO, WARN, ERROR, or CRIT)")
	viper.BindPFlag("LogLevel", pflag.CommandLine.Lookup("log-level"))
	pflag.Int64("image-max-area", math.MaxInt64, "Maximum area (w x h) of images to be served")
	viper.BindPFlag("ImageMaxArea", pflag.CommandLine.Lookup("image-max-area"))
	pflag.Int("image-max-width", math.MaxInt32, "Maximum width of images to be served")
	viper.BindPFlag("ImageMaxWidth", pflag.CommandLine.Lookup("image-max-width"))
	pflag.Int("image-max-height", math.MaxInt32, "Maximum height of images to be served")
	viper.BindPFlag("ImageMaxHeight", pflag.CommandLine.Lookup("image-max-height"))

	pflag.Parse()

	// Make sure required values exist
	if !viper.IsSet("TilePath") {
		fmt.Println("ERROR: --tile-path is required")
		pflag.Usage()
		os.Exit(1)
	}

	// Make sure we have a valid log level
	var level = logger.LogLevelFromString(viper.GetString("LogLevel"))
	if level == logger.Invalid {
		fmt.Println("ERROR: --log-level must be DEBUG, INFO, WARN, ERROR, or CRIT")
		pflag.Usage()
		os.Exit(1)
	}
	Logger = logger.New(level)
	openjpeg.Logger = Logger
	magick.Logger = Logger

	LoadPlugins(Logger)

	// Pull all values we need for all cases
	tilePath = viper.GetString("TilePath")
	address := viper.GetString("Address")

	ih := NewImageHandler(tilePath)
	ih.Maximums.Area = viper.GetInt64("ImageMaxArea")
	ih.Maximums.Width = viper.GetInt("ImageMaxWidth")
	ih.Maximums.Height = viper.GetInt("ImageMaxHeight")

	// Handle IIIF data only if we have a IIIF URL
	iiifURL := viper.GetString("IIIFURL")
	if iiifURL == "" {
		fmt.Println("ERROR: --iiif-url must be set to the server's public URL")
		pflag.Usage()
		os.Exit(1)
	}

	Logger.Debugf("Attempting to start up IIIF at %s", viper.GetString("IIIFURL"))
	iiifBase, err := url.Parse(iiifURL)
	if err == nil && iiifBase.Scheme == "" {
		err = fmt.Errorf("empty scheme")
	}
	if err == nil && iiifBase.Host == "" {
		err = fmt.Errorf("empty host")
	}
	if err == nil && iiifBase.Path == "" {
		err = fmt.Errorf("empty path")
	}
	if err != nil {
		Logger.Fatalf("Invalid IIIF URL (%s) specified: %s", iiifURL, err)
	}

	icl := viper.GetInt("InfoCacheLen")
	if icl > 0 {
		infoCache, err = lru.New(icl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	tcl := viper.GetInt("TileCacheLen")
	if tcl > 0 {
		Logger.Debugf("Creating a tile cache to hold up to %d tiles", tcl)
		tileCache, err = lru.New2Q(tcl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	Logger.Infof("IIIF enabled at %s", iiifBase.String())
	ih.EnableIIIF(iiifBase)

	capfile := viper.GetString("CapabilitiesFile")
	if capfile != "" {
		ih.FeatureSet = &iiif.FeatureSet{}
		_, err := toml.DecodeFile(capfile, &ih.FeatureSet)
		if err != nil {
			Logger.Fatalf("Invalid file or formatting in capabilities file '%s'", capfile)
		}
		Logger.Debugf("Setting IIIF capabilities from file '%s'", capfile)
	}

	handle(ih.IIIFBase.Path+"/", http.HandlerFunc(ih.IIIFRoute))
	handle("/images/dzi/", http.HandlerFunc(ih.DZIRoute))
	handle("/version", http.HandlerFunc(VersionHandler))

	if tileCache != nil {
		go func() {
			for {
				time.Sleep(time.Minute * 10)
				Logger.Infof("Cache hits: %d; cache misses: %d", cacheHits, cacheMisses)
			}
		}()
	}

	Logger.Infof("RAIS v%s starting...", version.Version)
	var srv = &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 30 * time.Second,
		Addr:         address,
	}

	interrupts.TrapIntTerm(func() {
		Logger.Infof("Stopping RAIS...")
		srv.Shutdown(nil)

		if len(teardownPlugins) > 0 {
			Logger.Infof("Tearing down plugins")
			for _, plug := range teardownPlugins {
				plug()
			}
			Logger.Infof("Plugin teardown complete")
		}

		Logger.Infof("Stopped")
	})

	if err := srv.ListenAndServe(); err != nil {
		// Don't report a fatal error when we close the server
		if err != http.ErrServerClosed {
			Logger.Fatalf("Error starting listener: %s", err)
		}
	}
}

// handle sends the pattern and raw handler to plugins, and sets up routing on
// whatever is returned (if anything).  All plugins which wrap handlers are
// allowed to run, but the behavior could definitely get weird depending on
// what a given plugin does.  Ye be warned.
func handle(pattern string, handler http.Handler) {
	for _, plug := range wrapHandlerPlugins {
		var h2, err = plug(pattern, handler)
		if err == nil {
			handler = h2
		} else if err != plugins.ErrSkipped {
			logger.Fatalf("Error trying to wrap handler %q: %s", pattern, err)
		}
	}
	http.Handle(pattern, handler)
}

// VersionHandler spits out the raw version string to the browser
func VersionHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(version.Version))
}
<|endoftext|>


<commit_before>package berlingo

import (
	"errors"
	"io"
)

// AI is the primary interface that must be implemented by an author to use the berlingo framework
type AI interface {
	GameStart(*Game)
	Turn(*Game)
	GameOver(*Game)
	Ping(*Game)
}

type Game struct {

	// The AI implementation that will play the game
	Ai AI

	// The json-parsed request received for the game.  Should normally not be needed by an AI author
	Request *Request
	// The pending response that will be returned for the current move
	Response *Response

	// General information on the game
	Id                      string
	Number_Of_Players       int
	Maximum_Number_Of_Turns int
	Player_Id               int
	Time_Limit_Per_Turn     int

	// Information on the current turn
	Current_Turn int
	Turns_Left   int

	// The game's parsed map
	Map *Map
}

func NewGame(ai AI, r io.Reader) (game *Game, err error) {

	request, err := NewRequest(r)
	if err != nil {
		return nil, err
	}

	response, err := NewResponse()
	if err != nil {
		return nil, err
	}

	game = &Game{
		Ai:                      ai,
		Request:                 request,
		Response:                response,
		Id:                      request.Infos.Game_Id,
		Number_Of_Players:       request.Infos.Number_Of_Players,
		Maximum_Number_Of_Turns: request.Infos.Maximum_Number_Of_Turns,
		Player_Id:               request.Infos.Player_Id,
		Time_Limit_Per_Turn:     request.Infos.Time_Limit_Per_Turn,
		Current_Turn:            request.Infos.Current_Turn,
		Turns_Left:              request.Infos.Maximum_Number_Of_Turns - request.Infos.Current_Turn,
	}

	m, err := NewMap(game)
	if err != nil {
		return nil, err
	}
	game.Map = m

	return game, nil

}

// Do invokes the appropriate AI method based on the Request Action
func (game *Game) Do() {
	switch game.Request.Action {
	case "game_start":
		game.Ai.GameStart(game)
	case "turn":
		game.Ai.Turn(game)
	case "game_over":
		game.Ai.GameOver(game)
	case "ping":
		game.Ai.Ping(game)
	}
}

// AddMove adds to the response queue the requested move characteristics
func (game *Game) AddMove(from_node *Node, to_node *Node, num_soldiers int) (err error) {
	if from_node.Available_Soldiers < num_soldiers {
		return errors.New("Not enough available soldiers")
	}
	from_node.Available_Soldiers -= num_soldiers
	to_node.Incoming_Soldiers += num_soldiers
	game.Response.AddMove(from_node, to_node, num_soldiers)
	return nil
}
<commit_msg>Game#AddMove validation<commit_after>package berlingo

import (
	"errors"
	"io"
)

// AI is the primary interface that must be implemented by an author to use the berlingo framework
type AI interface {
	GameStart(*Game)
	Turn(*Game)
	GameOver(*Game)
	Ping(*Game)
}

type Game struct {

	// The AI implementation that will play the game
	Ai AI

	// The json-parsed request received for the game.  Should normally not be needed by an AI author
	Request *Request
	// The pending response that will be returned for the current move
	Response *Response

	// General information on the game
	Id                      string
	Number_Of_Players       int
	Maximum_Number_Of_Turns int
	Player_Id               int
	Time_Limit_Per_Turn     int

	// Information on the current turn
	Current_Turn int
	Turns_Left   int

	// The game's parsed map
	Map *Map
}

func NewGame(ai AI, r io.Reader) (game *Game, err error) {

	request, err := NewRequest(r)
	if err != nil {
		return nil, err
	}

	response, err := NewResponse()
	if err != nil {
		return nil, err
	}

	game = &Game{
		Ai:                      ai,
		Request:                 request,
		Response:                response,
		Id:                      request.Infos.Game_Id,
		Number_Of_Players:       request.Infos.Number_Of_Players,
		Maximum_Number_Of_Turns: request.Infos.Maximum_Number_Of_Turns,
		Player_Id:               request.Infos.Player_Id,
		Time_Limit_Per_Turn:     request.Infos.Time_Limit_Per_Turn,
		Current_Turn:            request.Infos.Current_Turn,
		Turns_Left:              request.Infos.Maximum_Number_Of_Turns - request.Infos.Current_Turn,
	}

	m, err := NewMap(game)
	if err != nil {
		return nil, err
	}
	game.Map = m

	return game, nil

}

// Do invokes the appropriate AI method based on the Request Action
func (game *Game) Do() {
	switch game.Request.Action {
	case "game_start":
		game.Ai.GameStart(game)
	case "turn":
		game.Ai.Turn(game)
	case "game_over":
		game.Ai.GameOver(game)
	case "ping":
		game.Ai.Ping(game)
	}
}

// AddMove adds to the response queue the requested move characteristics
func (game *Game) AddMove(from_node *Node, to_node *Node, num_soldiers int) (err error) {
	if !from_node.IsOwned() {
		return errors.New("Cannot move soldiers from a node you don't own")
	} else if from_node.Available_Soldiers < num_soldiers {
		return errors.New("Not enough available soldiers")
	}
	from_node.Available_Soldiers -= num_soldiers
	to_node.Incoming_Soldiers += num_soldiers
	game.Response.AddMove(from_node, to_node, num_soldiers)
	return nil
}
<|endoftext|>


<commit_before>package gamedayapi

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	s "strings"
	"time"
)

// Game is the top-level abstraction, the starting point for clients.
// A game is obtained using the GameFor function.
// From a game, clients can navigate to all data: Innings, Boxscore, etc.
type Game struct {
	AwayAPMP        string `xml:"away_ampm,attr"`
	AwayCode        string `xml:"away_code,attr"`
	AwayLoss        string `xml:"away_loss,attr"`
	AwayTeamCity    string `xml:"away_team_city,attr"`
	AwayTeamID      string `xml:"away_team_id,attr"`
	AwayTeamName    string `xml:"away_team_name,attr"`
	AwayTime        string `xml:"away_time,attr"`
	AwayTimezone    string `xml:"away_time_zone,attr"`
	AwayWin         string `xml:"away_win,attr"`
	CalendarEventID string `xml:"calendar_event_id,attr"`
	HomeAMPM        string `xml:"home_ampm,attr"`
	HomeCode        string `xml:"home_code,attr"`
	HomeLoss        string `xml:"home_loss,attr"`
	HomeTeamCity    string `xml:"home_team_city,attr"`
	HomeTeamID      string `xml:"home_team_id,attr"`
	HomeTeamName    string `xml:"home_team_name,attr"`
	HomeTime        string `xml:"home_time,attr"`
	HomeTimezone    string `xml:"home_time_zone,attr"`
	HomeWin         string `xml:"home_win,attr"`
	ID              string `xml:"id,attr"`
	Gameday         string `xml:"gameday,attr"`
	GamePk          string `xml:"game_pk,attr"`
	GameType        string `xml:"game_type,attr"`
	Status          string `xml:"status,attr"`
	TimeDate        string `xml:"time_date,attr"`
	Timezone        string `xml:"time_zone,attr"`
	Venue           string `xml:"venue,attr"`

	// GameDataDirectory does not always point to where the files are.
	// Use FetchableDataDirectory for building requests to gameday servers.
	GameDataDirectory string `xml:"game_data_directory,attr"`

	allInnings AllInnings
	boxscore   Boxscore
	hitChart   HitChart
	players    Players

	year int
}

// GameFor will return a pointer to a game instance for the team code and date provided.
// This is the place to start for interacting with a game.
// Does not account for doubleheaders. Use GamesFor if doubleheader support is needed.
func GameFor(teamCode string, date time.Time) (*Game, error) {
	epg := EpgFor(date)
	game, err := epg.GameForTeam(teamCode)
	if err != nil {
		return &Game{}, err
	}
	return game, nil
}

// GamesFor will return a collection of pointers to games for the team code and date provided.
// Accounts for doubleheaders. In most cases, the collection will only have one game in it.
func GamesFor(teamCode string, date time.Time) ([]*Game, error) {
	epg := EpgFor(date)
	games, err := epg.GamesForTeam(teamCode)
	if err != nil {
		return games, err
	}
	return games, nil
}

// IsFinal returns true if the game status is Final.
func (game *Game) IsFinal() bool {
	return game.Status == "Final"
}

// AllInnings fetches the inning/innings_all.xml file from gameday servers and fills in all the
// structs beneath, all the way down to the pitches.
func (game *Game) AllInnings() *AllInnings {
	if game.IsFinal() && len(game.allInnings.AtBat) == 0 {
		game.load("/inning/inning_all.xml", &game.allInnings)
	}
	return &game.allInnings
}

// Boxscore fetches the boxscore.xml file from the gameday servers and fills in all the structs beneath.
func (game *Game) Boxscore() *Boxscore {
	if game.IsFinal() && len(game.boxscore.GameID) == 0 {
		game.load("/boxscore.xml", &game.boxscore)
	}
	return &game.boxscore
}

// HitChart fetches the inning/inning_hit.xml file from gameday servers
func (game *Game) HitChart() *HitChart {
	if game.IsFinal() && len(game.hitChart.Hips) == 0 {
		game.load("/inning/inning_hit.xml", &game.hitChart)
	}
	return &game.hitChart
}

// Players fetches the players.xml file from gameday servers
func (game *Game) Players() *Players {
	if game.IsFinal() && len(game.players.Date) == 0 {
		game.load("/players.xml", &game.players)
	}
	return &game.players
}

//func (game *Game) InningScores() *InningScores {}

// EagerLoad will eagerly load all of the files that the library pulls from the MLB gameday servers.
// Otherwise, files are lazily loaded as clients interact with the API.
func (game *Game) EagerLoad() {
	game.AllInnings()
	game.Boxscore()
	game.HitChart()
	game.Players()
}

// Year returns the year in which the game was played.
// Convenience method. Not a direct Gameday attribute.
func (game *Game) Year() int {
	if game.year == 0 {
		idPieces := s.Split(game.ID, "/")
		year, _ := strconv.Atoi(idPieces[0])
		game.year = year
	}
	return game.year
}

func (game Game) load(fileName string, val interface{}) {
	filePath := game.FetchableDataDirectory() + fileName
	localFilePath := BaseCachePath() + filePath
	if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
		log.Println("Cache miss on " + localFilePath)
		fetchAndCache(filePath, val)
	} else {
		log.Println("Cache hit on " + localFilePath)
		body, _ := ioutil.ReadFile(localFilePath)
		xml.Unmarshal(body, val)
	}
}

// FetchableDataDirectory builds a data directory to the game files using the game's ID.
// GameDataDirectory is not always reliable (see epg on 2013-04-11 vs 2013-04-10 as an example).
func (game Game) FetchableDataDirectory() string {
	idPieces := s.Split(game.ID, "/")
	var buffer bytes.Buffer
	buffer.WriteString(GamedayBasePath)
	buffer.WriteString(fmt.Sprintf("/year_%s/month_%s/day_%s/gid_", idPieces[0], idPieces[1], idPieces[2]))
	buffer.WriteString(game.Gameday)
	return buffer.String()
}

func fetchAndCache(filePath string, val interface{}) {
	urlToFetch := GamedayHostname + filePath
	log.Println("Fetching " + urlToFetch)
	resp, err := http.Get(urlToFetch)
	check(err)
	if resp.StatusCode != 200 {
		log.Fatal(resp.Status + " fetching " + urlToFetch)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	check(err)
	xml.Unmarshal(body, val)
	cacheFile(filePath, body)
}

func cacheFile(filePath string, body []byte) {
	localCachePath := BaseCachePath() + filePath[0:s.LastIndex(filePath, "/")]
	os.MkdirAll(localCachePath, (os.FileMode)(0775))
	f, err := os.Create(localCachePath + filePath[s.LastIndex(filePath, "/"):])
	f.Write(body)
	check(err)
	defer f.Close()
}
<commit_msg>Adding a couple attributes to the game.<commit_after>package gamedayapi

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	s "strings"
	"time"
)

// Game is the top-level abstraction, the starting point for clients.
// A game is obtained using the GameFor function.
// From a game, clients can navigate to all data: Innings, Boxscore, etc.
type Game struct {
	AwayAPMP        string `xml:"away_ampm,attr"`
	AwayCode        string `xml:"away_code,attr"`
	AwayFileCode	string `xml:"away_file_code,attr"`
	AwayLoss        string `xml:"away_loss,attr"`
	AwayTeamCity    string `xml:"away_team_city,attr"`
	AwayTeamID      string `xml:"away_team_id,attr"`
	AwayTeamName    string `xml:"away_team_name,attr"`
	AwayTime        string `xml:"away_time,attr"`
	AwayTimezone    string `xml:"away_time_zone,attr"`
	AwayWin         string `xml:"away_win,attr"`
	CalendarEventID string `xml:"calendar_event_id,attr"`
	HomeAMPM        string `xml:"home_ampm,attr"`
	HomeCode        string `xml:"home_code,attr"`
	HomeFileCode	string `xml:"home_file_code,attr"`
	HomeLoss        string `xml:"home_loss,attr"`
	HomeTeamCity    string `xml:"home_team_city,attr"`
	HomeTeamID      string `xml:"home_team_id,attr"`
	HomeTeamName    string `xml:"home_team_name,attr"`
	HomeTime        string `xml:"home_time,attr"`
	HomeTimezone    string `xml:"home_time_zone,attr"`
	HomeWin         string `xml:"home_win,attr"`
	ID              string `xml:"id,attr"`
	Gameday         string `xml:"gameday,attr"`
	GamePk          string `xml:"game_pk,attr"`
	GameType        string `xml:"game_type,attr"`
	Status          string `xml:"status,attr"`
	TimeDate        string `xml:"time_date,attr"`
	Timezone        string `xml:"time_zone,attr"`
	Venue           string `xml:"venue,attr"`

	// GameDataDirectory does not always point to where the files are.
	// Use FetchableDataDirectory for building requests to gameday servers.
	GameDataDirectory string `xml:"game_data_directory,attr"`

	allInnings AllInnings
	boxscore   Boxscore
	hitChart   HitChart
	players    Players

	year int
}

// GameFor will return a pointer to a game instance for the team code and date provided.
// This is the place to start for interacting with a game.
// Does not account for doubleheaders. Use GamesFor if doubleheader support is needed.
func GameFor(teamCode string, date time.Time) (*Game, error) {
	epg := EpgFor(date)
	game, err := epg.GameForTeam(teamCode)
	if err != nil {
		return &Game{}, err
	}
	return game, nil
}

// GamesFor will return a collection of pointers to games for the team code and date provided.
// Accounts for doubleheaders. In most cases, the collection will only have one game in it.
func GamesFor(teamCode string, date time.Time) ([]*Game, error) {
	epg := EpgFor(date)
	games, err := epg.GamesForTeam(teamCode)
	if err != nil {
		return games, err
	}
	return games, nil
}

// IsFinal returns true if the game status is Final.
func (game *Game) IsFinal() bool {
	return game.Status == "Final"
}

// AllInnings fetches the inning/innings_all.xml file from gameday servers and fills in all the
// structs beneath, all the way down to the pitches.
func (game *Game) AllInnings() *AllInnings {
	if game.IsFinal() && len(game.allInnings.AtBat) == 0 {
		game.load("/inning/inning_all.xml", &game.allInnings)
	}
	return &game.allInnings
}

// Boxscore fetches the boxscore.xml file from the gameday servers and fills in all the structs beneath.
func (game *Game) Boxscore() *Boxscore {
	if game.IsFinal() && len(game.boxscore.GameID) == 0 {
		game.load("/boxscore.xml", &game.boxscore)
	}
	return &game.boxscore
}

// HitChart fetches the inning/inning_hit.xml file from gameday servers
func (game *Game) HitChart() *HitChart {
	if game.IsFinal() && len(game.hitChart.Hips) == 0 {
		game.load("/inning/inning_hit.xml", &game.hitChart)
	}
	return &game.hitChart
}

// Players fetches the players.xml file from gameday servers
func (game *Game) Players() *Players {
	if game.IsFinal() && len(game.players.Date) == 0 {
		game.load("/players.xml", &game.players)
	}
	return &game.players
}

//func (game *Game) InningScores() *InningScores {}

// EagerLoad will eagerly load all of the files that the library pulls from the MLB gameday servers.
// Otherwise, files are lazily loaded as clients interact with the API.
func (game *Game) EagerLoad() {
	game.AllInnings()
	game.Boxscore()
	game.HitChart()
	game.Players()
}

// Year returns the year in which the game was played.
// Convenience method. Not a direct Gameday attribute.
func (game *Game) Year() int {
	if game.year == 0 {
		idPieces := s.Split(game.ID, "/")
		year, _ := strconv.Atoi(idPieces[0])
		game.year = year
	}
	return game.year
}

func (game Game) load(fileName string, val interface{}) {
	filePath := game.FetchableDataDirectory() + fileName
	localFilePath := BaseCachePath() + filePath
	if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
		log.Println("Cache miss on " + localFilePath)
		fetchAndCache(filePath, val)
	} else {
		log.Println("Cache hit on " + localFilePath)
		body, _ := ioutil.ReadFile(localFilePath)
		xml.Unmarshal(body, val)
	}
}

// FetchableDataDirectory builds a data directory to the game files using the game's ID.
// GameDataDirectory is not always reliable (see epg on 2013-04-11 vs 2013-04-10 as an example).
func (game Game) FetchableDataDirectory() string {
	idPieces := s.Split(game.ID, "/")
	var buffer bytes.Buffer
	buffer.WriteString(GamedayBasePath)
	buffer.WriteString(fmt.Sprintf("/year_%s/month_%s/day_%s/gid_", idPieces[0], idPieces[1], idPieces[2]))
	buffer.WriteString(game.Gameday)
	return buffer.String()
}

func fetchAndCache(filePath string, val interface{}) {
	urlToFetch := GamedayHostname + filePath
	log.Println("Fetching " + urlToFetch)
	resp, err := http.Get(urlToFetch)
	check(err)
	if resp.StatusCode != 200 {
		log.Fatal(resp.Status + " fetching " + urlToFetch)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	check(err)
	xml.Unmarshal(body, val)
	cacheFile(filePath, body)
}

func cacheFile(filePath string, body []byte) {
	localCachePath := BaseCachePath() + filePath[0:s.LastIndex(filePath, "/")]
	os.MkdirAll(localCachePath, (os.FileMode)(0775))
	f, err := os.Create(localCachePath + filePath[s.LastIndex(filePath, "/"):])
	f.Write(body)
	check(err)
	defer f.Close()
}
<|endoftext|>


<commit_before>// Copyright 2016-2019 Authors of Cilium
//
// 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 main

import (
	"fmt"

	"github.com/cilium/cilium/api/v1/models"
	. "github.com/cilium/cilium/api/v1/server/restapi/service"
	"github.com/cilium/cilium/pkg/api"
	"github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/service"

	"github.com/go-openapi/runtime/middleware"
)

// SVCAdd is the public method to add services. We assume the ID provided is not in
// sync with the KVStore. If that's the, case the service won't be used and an error is
// returned to the caller.
//
// Returns true if service was created.
func (d *Daemon) SVCAdd(feL3n4Addr loadbalancer.L3n4AddrID, be []loadbalancer.LBBackEnd) (bool, error) {
	log.WithField(logfields.ServiceID, feL3n4Addr.String()).Debug("adding service")
	if feL3n4Addr.ID == 0 {
		return false, fmt.Errorf("invalid service ID 0")
	}

	created, id, err := d.svc.UpsertService(feL3n4Addr, be, service.TypeClusterIP)
	if err == nil && id != feL3n4Addr.ID {
		return false,
			fmt.Errorf("the service provided is already registered with ID %d, please use that ID instead of %d",
				id, feL3n4Addr.ID)
	}

	return created, err
}

type putServiceID struct {
	d *Daemon
}

func NewPutServiceIDHandler(d *Daemon) PutServiceIDHandler {
	return &putServiceID{d: d}
}

func (h *putServiceID) Handle(params PutServiceIDParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("PUT /service/{id} request")

	f, err := loadbalancer.NewL3n4AddrFromModel(params.Config.FrontendAddress)
	if err != nil {
		return api.Error(PutServiceIDInvalidFrontendCode, err)
	}

	frontend := loadbalancer.L3n4AddrID{
		L3n4Addr: *f,
		ID:       loadbalancer.ID(params.Config.ID),
	}

	backends := []loadbalancer.LBBackEnd{}
	for _, v := range params.Config.BackendAddresses {
		b, err := loadbalancer.NewLBBackEndFromBackendModel(v)
		if err != nil {
			return api.Error(PutServiceIDInvalidBackendCode, err)
		}
		backends = append(backends, *b)
	}

	if created, err := h.d.SVCAdd(frontend, backends); err != nil {
		return api.Error(PutServiceIDFailureCode, err)
	} else if created {
		return NewPutServiceIDCreated()
	} else {
		return NewPutServiceIDOK()
	}
}

type deleteServiceID struct {
	d *Daemon
}

func NewDeleteServiceIDHandler(d *Daemon) DeleteServiceIDHandler {
	return &deleteServiceID{d: d}
}

func (h *deleteServiceID) Handle(params DeleteServiceIDParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("DELETE /service/{id} request")

	found, err := h.d.svc.DeleteServiceByID(loadbalancer.ServiceID(params.ID))
	switch {
	case err != nil:
		log.WithError(err).WithField(logfields.ServiceID, params.ID).
			Warn("DELETE /service/{id}: error deleting service")
		return api.Error(DeleteServiceIDFailureCode, err)
	case !found:
		return NewDeleteServiceIDNotFound()
	default:
		return NewDeleteServiceIDOK()
	}
}

type getServiceID struct {
	daemon *Daemon
}

func NewGetServiceIDHandler(d *Daemon) GetServiceIDHandler {
	return &getServiceID{daemon: d}
}

func (h *getServiceID) Handle(params GetServiceIDParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("GET /service/{id} request")

	d := h.daemon

	if svc, ok := d.svc.GetDeepCopyServiceByID(loadbalancer.ServiceID(params.ID)); ok {
		return NewGetServiceIDOK().WithPayload(svc.GetModel())
	}
	return NewGetServiceIDNotFound()
}

type getService struct {
	d *Daemon
}

func NewGetServiceHandler(d *Daemon) GetServiceHandler {
	return &getService{d: d}
}

func (h *getService) Handle(params GetServiceParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("GET /service request")
	list := h.d.GetServiceList()
	return NewGetServiceOK().WithPayload(list)
}

// GetServiceList returns list of services
func (d *Daemon) GetServiceList() []*models.Service {
	svcs := d.svc.GetDeepCopyServices()
	list := make([]*models.Service, 0, len(svcs))

	for _, v := range svcs {
		list = append(list, v.GetModel())
	}
	return list
}
<commit_msg>daemon: Inline SVCAdd method into PUT handler<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// 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 main

import (
	"fmt"

	"github.com/cilium/cilium/api/v1/models"
	. "github.com/cilium/cilium/api/v1/server/restapi/service"
	"github.com/cilium/cilium/pkg/api"
	"github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/service"

	"github.com/go-openapi/runtime/middleware"
)

type putServiceID struct {
	d *Daemon
}

func NewPutServiceIDHandler(d *Daemon) PutServiceIDHandler {
	return &putServiceID{d: d}
}

func (h *putServiceID) Handle(params PutServiceIDParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("PUT /service/{id} request")

	if params.Config.ID == 0 {
		return api.Error(PutServiceIDFailureCode, fmt.Errorf("invalid service ID 0"))
	}

	f, err := loadbalancer.NewL3n4AddrFromModel(params.Config.FrontendAddress)
	if err != nil {
		return api.Error(PutServiceIDInvalidFrontendCode, err)
	}

	frontend := loadbalancer.L3n4AddrID{
		L3n4Addr: *f,
		ID:       loadbalancer.ID(params.Config.ID),
	}
	backends := []loadbalancer.LBBackEnd{}
	for _, v := range params.Config.BackendAddresses {
		b, err := loadbalancer.NewLBBackEndFromBackendModel(v)
		if err != nil {
			return api.Error(PutServiceIDInvalidBackendCode, err)
		}
		backends = append(backends, *b)
	}

	created, id, err := h.d.svc.UpsertService(frontend, backends, service.TypeClusterIP)
	if err == nil && id != frontend.ID {
		return api.Error(PutServiceIDInvalidFrontendCode,
			fmt.Errorf("the service provided is already registered with ID %d, please use that ID instead of %d",
				id, frontend.ID))
	} else if err != nil {
		return api.Error(PutServiceIDFailureCode, err)
	} else if created {
		return NewPutServiceIDCreated()
	} else {
		return NewPutServiceIDOK()
	}
}

type deleteServiceID struct {
	d *Daemon
}

func NewDeleteServiceIDHandler(d *Daemon) DeleteServiceIDHandler {
	return &deleteServiceID{d: d}
}

func (h *deleteServiceID) Handle(params DeleteServiceIDParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("DELETE /service/{id} request")

	found, err := h.d.svc.DeleteServiceByID(loadbalancer.ServiceID(params.ID))
	switch {
	case err != nil:
		log.WithError(err).WithField(logfields.ServiceID, params.ID).
			Warn("DELETE /service/{id}: error deleting service")
		return api.Error(DeleteServiceIDFailureCode, err)
	case !found:
		return NewDeleteServiceIDNotFound()
	default:
		return NewDeleteServiceIDOK()
	}
}

type getServiceID struct {
	daemon *Daemon
}

func NewGetServiceIDHandler(d *Daemon) GetServiceIDHandler {
	return &getServiceID{daemon: d}
}

func (h *getServiceID) Handle(params GetServiceIDParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("GET /service/{id} request")

	d := h.daemon

	if svc, ok := d.svc.GetDeepCopyServiceByID(loadbalancer.ServiceID(params.ID)); ok {
		return NewGetServiceIDOK().WithPayload(svc.GetModel())
	}
	return NewGetServiceIDNotFound()
}

type getService struct {
	d *Daemon
}

func NewGetServiceHandler(d *Daemon) GetServiceHandler {
	return &getService{d: d}
}

func (h *getService) Handle(params GetServiceParams) middleware.Responder {
	log.WithField(logfields.Params, logfields.Repr(params)).Debug("GET /service request")
	list := h.d.GetServiceList()
	return NewGetServiceOK().WithPayload(list)
}

// GetServiceList returns list of services
func (d *Daemon) GetServiceList() []*models.Service {
	svcs := d.svc.GetDeepCopyServices()
	list := make([]*models.Service, 0, len(svcs))

	for _, v := range svcs {
		list = append(list, v.GetModel())
	}
	return list
}
<|endoftext|>


<commit_before>// +build !windows

// TODO(amitkris): We need to split this file for solaris.

package daemon

import (
	"encoding/json"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/docker/docker/container"
	"github.com/docker/docker/pkg/fileutils"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/volume"
	"github.com/docker/docker/volume/drivers"
	"github.com/docker/docker/volume/local"
	"github.com/pkg/errors"
)

// setupMounts iterates through each of the mount points for a container and
// calls Setup() on each. It also looks to see if is a network mount such as
// /etc/resolv.conf, and if it is not, appends it to the array of mounts.
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
	var mounts []container.Mount
	// TODO: tmpfs mounts should be part of Mountpoints
	tmpfsMounts := make(map[string]bool)
	tmpfsMountInfo, err := c.TmpfsMounts()
	if err != nil {
		return nil, err
	}
	for _, m := range tmpfsMountInfo {
		tmpfsMounts[m.Destination] = true
	}
	for _, m := range c.MountPoints {
		if tmpfsMounts[m.Destination] {
			continue
		}
		if err := daemon.lazyInitializeVolume(c.ID, m); err != nil {
			return nil, err
		}
		rootUID, rootGID := daemon.GetRemappedUIDGID()
		path, err := m.Setup(c.MountLabel, rootUID, rootGID)
		if err != nil {
			return nil, err
		}
		if !c.TrySetNetworkMount(m.Destination, path) {
			mnt := container.Mount{
				Source:      path,
				Destination: m.Destination,
				Writable:    m.RW,
				Propagation: string(m.Propagation),
			}
			if m.Volume != nil {
				attributes := map[string]string{
					"driver":      m.Volume.DriverName(),
					"container":   c.ID,
					"destination": m.Destination,
					"read/write":  strconv.FormatBool(m.RW),
					"propagation": string(m.Propagation),
				}
				daemon.LogVolumeEvent(m.Volume.Name(), "mount", attributes)
			}
			mounts = append(mounts, mnt)
		}
	}

	mounts = sortMounts(mounts)
	netMounts := c.NetworkMounts()
	// if we are going to mount any of the network files from container
	// metadata, the ownership must be set properly for potential container
	// remapped root (user namespaces)
	rootUID, rootGID := daemon.GetRemappedUIDGID()
	for _, mount := range netMounts {
		if err := os.Chown(mount.Source, rootUID, rootGID); err != nil {
			return nil, err
		}
	}
	return append(mounts, netMounts...), nil
}

// sortMounts sorts an array of mounts in lexicographic order. This ensure that
// when mounting, the mounts don't shadow other mounts. For example, if mounting
// /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first.
func sortMounts(m []container.Mount) []container.Mount {
	sort.Sort(mounts(m))
	return m
}

// setBindModeIfNull is platform specific processing to ensure the
// shared mode is set to 'z' if it is null. This is called in the case
// of processing a named volume and not a typical bind.
func setBindModeIfNull(bind *volume.MountPoint) {
	if bind.Mode == "" {
		bind.Mode = "z"
	}
}

// migrateVolume links the contents of a volume created pre Docker 1.7
// into the location expected by the local driver.
// It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
// It preserves the volume json configuration generated pre Docker 1.7 to be able to
// downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
func migrateVolume(id, vfs string) error {
	l, err := volumedrivers.GetDriver(volume.DefaultDriverName)
	if err != nil {
		return err
	}

	newDataPath := l.(*local.Root).DataPath(id)
	fi, err := os.Stat(newDataPath)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	if fi != nil && fi.IsDir() {
		return nil
	}

	return os.Symlink(vfs, newDataPath)
}

// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
// It reads the container configuration and creates valid mount points for the old volumes.
func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
	// Inspect old structures only when we're upgrading from old versions
	// to versions >= 1.7 and the MountPoints has not been populated with volumes data.
	type volumes struct {
		Volumes   map[string]string
		VolumesRW map[string]bool
	}
	cfgPath, err := container.ConfigPath()
	if err != nil {
		return err
	}
	f, err := os.Open(cfgPath)
	if err != nil {
		return errors.Wrap(err, "could not open container config")
	}
	var cv volumes
	if err := json.NewDecoder(f).Decode(&cv); err != nil {
		return errors.Wrap(err, "could not decode container config")
	}

	if len(container.MountPoints) == 0 && len(cv.Volumes) > 0 {
		for destination, hostPath := range cv.Volumes {
			vfsPath := filepath.Join(daemon.root, "vfs", "dir")
			rw := cv.VolumesRW != nil && cv.VolumesRW[destination]

			if strings.HasPrefix(hostPath, vfsPath) {
				id := filepath.Base(hostPath)
				v, err := daemon.volumes.CreateWithRef(id, volume.DefaultDriverName, container.ID, nil, nil)
				if err != nil {
					return err
				}
				if err := migrateVolume(id, hostPath); err != nil {
					return err
				}
				container.AddMountPointWithVolume(destination, v, true)
			} else { // Bind mount
				m := volume.MountPoint{Source: hostPath, Destination: destination, RW: rw}
				container.MountPoints[destination] = &m
			}
		}
		return container.ToDisk()
	}
	return nil
}

func (daemon *Daemon) mountVolumes(container *container.Container) error {
	mounts, err := daemon.setupMounts(container)
	if err != nil {
		return err
	}

	for _, m := range mounts {
		dest, err := container.GetResourcePath(m.Destination)
		if err != nil {
			return err
		}

		var stat os.FileInfo
		stat, err = os.Stat(m.Source)
		if err != nil {
			return err
		}
		if err = fileutils.CreateIfNotExists(dest, stat.IsDir()); err != nil {
			return err
		}

		opts := "rbind,ro"
		if m.Writable {
			opts = "rbind,rw"
		}

		if err := mount.Mount(m.Source, dest, bindMountType, opts); err != nil {
			return err
		}

		// mountVolumes() seems to be called for temporary mounts
		// outside the container. Soon these will be unmounted with
		// lazy unmount option and given we have mounted the rbind,
		// all the submounts will propagate if these are shared. If
		// daemon is running in host namespace and has / as shared
		// then these unmounts will propagate and unmount original
		// mount as well. So make all these mounts rprivate.
		// Do not use propagation property of volume as that should
		// apply only when mounting happen inside the container.
		if err := mount.MakeRPrivate(dest); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>close the file<commit_after>// +build !windows

// TODO(amitkris): We need to split this file for solaris.

package daemon

import (
	"encoding/json"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/docker/docker/container"
	"github.com/docker/docker/pkg/fileutils"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/volume"
	"github.com/docker/docker/volume/drivers"
	"github.com/docker/docker/volume/local"
	"github.com/pkg/errors"
)

// setupMounts iterates through each of the mount points for a container and
// calls Setup() on each. It also looks to see if is a network mount such as
// /etc/resolv.conf, and if it is not, appends it to the array of mounts.
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
	var mounts []container.Mount
	// TODO: tmpfs mounts should be part of Mountpoints
	tmpfsMounts := make(map[string]bool)
	tmpfsMountInfo, err := c.TmpfsMounts()
	if err != nil {
		return nil, err
	}
	for _, m := range tmpfsMountInfo {
		tmpfsMounts[m.Destination] = true
	}
	for _, m := range c.MountPoints {
		if tmpfsMounts[m.Destination] {
			continue
		}
		if err := daemon.lazyInitializeVolume(c.ID, m); err != nil {
			return nil, err
		}
		rootUID, rootGID := daemon.GetRemappedUIDGID()
		path, err := m.Setup(c.MountLabel, rootUID, rootGID)
		if err != nil {
			return nil, err
		}
		if !c.TrySetNetworkMount(m.Destination, path) {
			mnt := container.Mount{
				Source:      path,
				Destination: m.Destination,
				Writable:    m.RW,
				Propagation: string(m.Propagation),
			}
			if m.Volume != nil {
				attributes := map[string]string{
					"driver":      m.Volume.DriverName(),
					"container":   c.ID,
					"destination": m.Destination,
					"read/write":  strconv.FormatBool(m.RW),
					"propagation": string(m.Propagation),
				}
				daemon.LogVolumeEvent(m.Volume.Name(), "mount", attributes)
			}
			mounts = append(mounts, mnt)
		}
	}

	mounts = sortMounts(mounts)
	netMounts := c.NetworkMounts()
	// if we are going to mount any of the network files from container
	// metadata, the ownership must be set properly for potential container
	// remapped root (user namespaces)
	rootUID, rootGID := daemon.GetRemappedUIDGID()
	for _, mount := range netMounts {
		if err := os.Chown(mount.Source, rootUID, rootGID); err != nil {
			return nil, err
		}
	}
	return append(mounts, netMounts...), nil
}

// sortMounts sorts an array of mounts in lexicographic order. This ensure that
// when mounting, the mounts don't shadow other mounts. For example, if mounting
// /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first.
func sortMounts(m []container.Mount) []container.Mount {
	sort.Sort(mounts(m))
	return m
}

// setBindModeIfNull is platform specific processing to ensure the
// shared mode is set to 'z' if it is null. This is called in the case
// of processing a named volume and not a typical bind.
func setBindModeIfNull(bind *volume.MountPoint) {
	if bind.Mode == "" {
		bind.Mode = "z"
	}
}

// migrateVolume links the contents of a volume created pre Docker 1.7
// into the location expected by the local driver.
// It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
// It preserves the volume json configuration generated pre Docker 1.7 to be able to
// downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
func migrateVolume(id, vfs string) error {
	l, err := volumedrivers.GetDriver(volume.DefaultDriverName)
	if err != nil {
		return err
	}

	newDataPath := l.(*local.Root).DataPath(id)
	fi, err := os.Stat(newDataPath)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	if fi != nil && fi.IsDir() {
		return nil
	}

	return os.Symlink(vfs, newDataPath)
}

// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
// It reads the container configuration and creates valid mount points for the old volumes.
func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
	// Inspect old structures only when we're upgrading from old versions
	// to versions >= 1.7 and the MountPoints has not been populated with volumes data.
	type volumes struct {
		Volumes   map[string]string
		VolumesRW map[string]bool
	}
	cfgPath, err := container.ConfigPath()
	if err != nil {
		return err
	}
	f, err := os.Open(cfgPath)
	if err != nil {
		return errors.Wrap(err, "could not open container config")
	}
	defer f.Close()
	var cv volumes
	if err := json.NewDecoder(f).Decode(&cv); err != nil {
		return errors.Wrap(err, "could not decode container config")
	}

	if len(container.MountPoints) == 0 && len(cv.Volumes) > 0 {
		for destination, hostPath := range cv.Volumes {
			vfsPath := filepath.Join(daemon.root, "vfs", "dir")
			rw := cv.VolumesRW != nil && cv.VolumesRW[destination]

			if strings.HasPrefix(hostPath, vfsPath) {
				id := filepath.Base(hostPath)
				v, err := daemon.volumes.CreateWithRef(id, volume.DefaultDriverName, container.ID, nil, nil)
				if err != nil {
					return err
				}
				if err := migrateVolume(id, hostPath); err != nil {
					return err
				}
				container.AddMountPointWithVolume(destination, v, true)
			} else { // Bind mount
				m := volume.MountPoint{Source: hostPath, Destination: destination, RW: rw}
				container.MountPoints[destination] = &m
			}
		}
		return container.ToDisk()
	}
	return nil
}

func (daemon *Daemon) mountVolumes(container *container.Container) error {
	mounts, err := daemon.setupMounts(container)
	if err != nil {
		return err
	}

	for _, m := range mounts {
		dest, err := container.GetResourcePath(m.Destination)
		if err != nil {
			return err
		}

		var stat os.FileInfo
		stat, err = os.Stat(m.Source)
		if err != nil {
			return err
		}
		if err = fileutils.CreateIfNotExists(dest, stat.IsDir()); err != nil {
			return err
		}

		opts := "rbind,ro"
		if m.Writable {
			opts = "rbind,rw"
		}

		if err := mount.Mount(m.Source, dest, bindMountType, opts); err != nil {
			return err
		}

		// mountVolumes() seems to be called for temporary mounts
		// outside the container. Soon these will be unmounted with
		// lazy unmount option and given we have mounted the rbind,
		// all the submounts will propagate if these are shared. If
		// daemon is running in host namespace and has / as shared
		// then these unmounts will propagate and unmount original
		// mount as well. So make all these mounts rprivate.
		// Do not use propagation property of volume as that should
		// apply only when mounting happen inside the container.
		if err := mount.MakeRPrivate(dest); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bitbucket.org/kardianos/osext"
	"flag"
	"fmt"
	"github.com/ethereum/eth-go/ethlog"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"runtime"
)

var Identifier string
var StartRpc bool
var RpcPort int
var UseUPnP bool
var OutboundPort string
var ShowGenesis bool
var AddPeer string
var MaxPeer int
var GenAddr bool
var UseSeed bool
var ImportKey string
var ExportKey bool
var NonInteractive bool
var Datadir string
var LogFile string
var ConfigFile string
var DebugFile string
var LogLevel int

// flags specific to gui client
var AssetPath string

func defaultAssetPath() string {
	var assetPath string
	// If the current working directory is the go-ethereum dir
	// assume a debug build and use the source directory as
	// asset directory.
	pwd, _ := os.Getwd()
	if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal") {
		assetPath = path.Join(pwd, "assets")
	} else {
		switch runtime.GOOS {
		case "darwin":
			// Get Binary Directory
			exedir, _ := osext.ExecutableFolder()
			assetPath = filepath.Join(exedir, "../Resources")
		case "linux":
			assetPath = "/usr/share/ethereal"
		case "window":
			fallthrough
		default:
			assetPath = "."
		}
	}
	return assetPath
}

func defaultDataDir() string {
	usr, _ := user.Current()
	return path.Join(usr.HomeDir, ".ethereal")
}

var defaultConfigFile = path.Join(defaultDataDir(), "conf.ini")

func Init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "%s [options] [filename]:\noptions precedence: default < config file < environment variables < command line\n", os.Args[0])
		flag.PrintDefaults()
	}

	flag.StringVar(&Identifier, "id", "", "Custom client identifier")
	flag.StringVar(&OutboundPort, "port", "30303", "listening port")
	flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support")
	flag.IntVar(&MaxPeer, "maxpeer", 10, "maximum desired peers")
	flag.IntVar(&RpcPort, "rpcport", 8080, "port to start json-rpc server on")
	flag.BoolVar(&StartRpc, "rpc", false, "start rpc server")
	flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)")
	flag.BoolVar(&UseSeed, "seed", true, "seed peers")
	flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key")
	flag.BoolVar(&ExportKey, "export", false, "export private key")
	flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)")
	flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)")
	flag.StringVar(&Datadir, "datadir", defaultDataDir(), "specifies the datadir to use")
	flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file")
	flag.StringVar(&DebugFile, "debug", "", "debug file (no debugging if not set)")
	flag.IntVar(&LogLevel, "loglevel", int(ethlog.InfoLevel), "loglevel: 0-5: silent,error,warn,info,debug,debug detail)")

	flag.StringVar(&AssetPath, "asset_path", defaultAssetPath(), "absolute path to GUI assets directory")

	flag.Parse()
}
<commit_msg>new command line options - keyring: keyring/session identifier used by key manager - keystore: choice of db/file key storage - import message updated - export: name of directory to export keys to (was bool)<commit_after>package main

import (
	"bitbucket.org/kardianos/osext"
	"flag"
	"fmt"
	"github.com/ethereum/eth-go/ethlog"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"runtime"
)

var Identifier string
var KeyRing string
var KeyStore string
var StartRpc bool
var RpcPort int
var UseUPnP bool
var OutboundPort string
var ShowGenesis bool
var AddPeer string
var MaxPeer int
var GenAddr bool
var UseSeed bool
var SecretFile string
var ExportDir string
var NonInteractive bool
var Datadir string
var LogFile string
var ConfigFile string
var DebugFile string
var LogLevel int

// flags specific to gui client
var AssetPath string

func defaultAssetPath() string {
	var assetPath string
	// If the current working directory is the go-ethereum dir
	// assume a debug build and use the source directory as
	// asset directory.
	pwd, _ := os.Getwd()
	if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal") {
		assetPath = path.Join(pwd, "assets")
	} else {
		switch runtime.GOOS {
		case "darwin":
			// Get Binary Directory
			exedir, _ := osext.ExecutableFolder()
			assetPath = filepath.Join(exedir, "../Resources")
		case "linux":
			assetPath = "/usr/share/ethereal"
		case "window":
			fallthrough
		default:
			assetPath = "."
		}
	}
	return assetPath
}

func defaultDataDir() string {
	usr, _ := user.Current()
	return path.Join(usr.HomeDir, ".ethereal")
}

var defaultConfigFile = path.Join(defaultDataDir(), "conf.ini")

func Init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "%s [options] [filename]:\noptions precedence: default < config file < environment variables < command line\n", os.Args[0])
		flag.PrintDefaults()
	}

	flag.StringVar(&Identifier, "id", "", "Custom client identifier")
	flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use")
	flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file (db)")
	flag.StringVar(&OutboundPort, "port", "30303", "listening port")
	flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support")
	flag.IntVar(&MaxPeer, "maxpeer", 10, "maximum desired peers")
	flag.IntVar(&RpcPort, "rpcport", 8080, "port to start json-rpc server on")
	flag.BoolVar(&StartRpc, "rpc", false, "start rpc server")
	flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)")
	flag.BoolVar(&UseSeed, "seed", true, "seed peers")
	flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key")
	flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)")
	flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given")
	flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)")
	flag.StringVar(&Datadir, "datadir", defaultDataDir(), "specifies the datadir to use")
	flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file")
	flag.StringVar(&DebugFile, "debug", "", "debug file (no debugging if not set)")
	flag.IntVar(&LogLevel, "loglevel", int(ethlog.InfoLevel), "loglevel: 0-5: silent,error,warn,info,debug,debug detail)")

	flag.StringVar(&AssetPath, "asset_path", defaultAssetPath(), "absolute path to GUI assets directory")

	flag.Parse()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	//	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
)

func readLine(scanner *bufio.Scanner) {

	scanner.Split(bufio.ScanLines)

	line := 0

	ra, _ := regexp.Compile("[^\\s]")

	for scanner.Scan() {
		line += 1
		if line == 4 {
			fmt.Printf("%d: %v\n", line, ra.ReplaceAllString(scanner.Text(), "-"))
		} else {
			fmt.Printf("%d: %v\n", line, scanner.Text())
		}
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

}

func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

type replace_lines []int

func main() {

	//	lines := flag.String()

	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 {
		readLine(bufio.NewScanner(os.Stdin))
	} else {
		if len(os.Args) > 1 {

			f := os.Args[1]

			fmt.Println(os.Args)

			if Exists(f) {
				file, err := os.Open(f)
				defer file.Close()

				if err != nil {
					log.Fatal(err)
				}

				readLine(bufio.NewScanner(file))
			}
		}
	}
}
<commit_msg>	modified:   gist.go<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

func readLine(scanner *bufio.Scanner, replace_lines intslice) {

	scanner.Split(bufio.ScanLines)

	sort.Ints(replace_lines)
	rl := removeDuplicates(replace_lines)

	fmt.Println(rl)

	line := 0

	ra, _ := regexp.Compile("[^\\s]")

	for scanner.Scan() {
		line += 1
		if line == 4 {
			fmt.Printf("%d: %v\n", line, ra.ReplaceAllString(scanner.Text(), "-"))
		} else {
			fmt.Printf("%d: %v\n", line, scanner.Text())
		}
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

}

func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// slice of ints
type intslice []int

// String is the method to format the flag's value.
func (i *intslice) String() string {
	return fmt.Sprint(*i)
}

// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (i *intslice) Set(value string) error {
	if len(*i) > 0 {
		return errors.New("line flag already set")
	}

	for _, ln := range strings.Split(value, ",") {
		//
		// try to sanitize input 1, 3,  7, 9
		//fmt.Printf("[%s]\n", strings.TrimSpace(ln))
		//
		line, err := strconv.Atoi(ln)
		if err != nil {
			return err
		}
		*i = append(*i, line)
	}

	return nil
}

func removeDuplicates(a []int) []int {
	result := []int{}
	seen := map[int]int{}
	for _, val := range a {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = val
		}
	}
	return result
}

var replace_lines intslice

func main() {

	flag.Var(&replace_lines, "l", "Number of the line(s) to be replaced")

	flag.Parse()

	//	if len(flag.Args()) == 0 {
	fmt.Println("--:", flag.NArg(), flag.Args(), replace_lines)
	//	}

	//if flag.NFlag() > 0 {
	//fmt.Println("lines to be replaced:")
	//for i := 0; i < len(replace_lines); i++ {
	//fmt.Printf("%d ", replace_lines[i])
	//}
	//}

	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 {
		readLine(bufio.NewScanner(os.Stdin), replace_lines)
	} else {
		if flag.NArg() > 0 {

			f := flag.Arg(0)

			if Exists(f) {
				file, err := os.Open(f)
				defer file.Close()

				if err != nil {
					log.Fatal(err)
				}

				readLine(bufio.NewScanner(file), replace_lines)
			}
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>resource/aws_cloudformation_stack_set_instance: Handle IAM eventual consistency retries during creation (#15173)<commit_after><|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"github.com/justincampbell/zipcode"
	"github.com/mlbright/forecast/v2"
)

var conditionIcons = map[string]string{
	"clear-day":           "☀",
	"clear-night":         "🌙",
	"cloudy":              "☁",
	"fog":                 "🌁",
	"partly-cloudy-day":   "⛅",
	"partly-cloudy-night": "🌙",
	"rain":                "☔",
	"sleet":               "❄ ☔",
	"snow":                "❄",
	"wind":                "🍃",
	"error":               "❗",
}

var maxCacheAge, _ = time.ParseDuration("1h")

var coordinates string
var key string
var tmpDir string
var zipCode string

func init() {
	flag.StringVar(&coordinates, "coordinates", "", "the coordinates, expressed as latitude,longitude")
	flag.StringVar(&key, "key", os.Getenv("FORECAST_IO_API_KEY"), "your forecast.io API key")
	flag.StringVar(&tmpDir, "tmpdir", os.TempDir(), "the directory to use to store cached responses")
	flag.StringVar(&zipCode, "zipcode", "", "a USPS ZIP Code")

	flag.Parse()
}

func main() {
	var err error
	var latitude string
	var longitude string

	if key == "" {
		exitWith("Please provide your forecast.io API key with -key, or set FORECAST_IO_API_KEY", 1)
	}

	if zipCode != "" {
		coord, err := zipcode.Lookup(zipCode)
		check(err)
		coordinates = coord.String()
	}

	coordinateParts := strings.Split(coordinates, ",")

	if len(coordinateParts) != 2 {
		exitWith("You must specify latitude and longitude like so: 39.95,-75.1667", 1)
	}

	latitude, longitude = coordinateParts[0], coordinateParts[1]

	cacheFilename := fmt.Sprintf("emoji-weather-%s-%s.json", latitude, longitude)
	cacheFile := path.Join(tmpDir, cacheFilename)

	var json []byte

	if isCacheStale(cacheFile) {
		json, err = getForecast(key, latitude, longitude)
		check(err)

		err = writeCache(cacheFile, json)
		check(err)
	} else {
		json, err = ioutil.ReadFile(cacheFile)
		check(err)
	}

	fmt.Println(formatConditions(extractConditionFromJSON(json)))
}

func isCacheStale(cacheFile string) bool {
	stat, err := os.Stat(cacheFile)

	return os.IsNotExist(err) || time.Since(stat.ModTime()) > maxCacheAge
}

func getForecast(key string, latitude string, longitude string) (json []byte, err error) {
	res, err := forecast.GetResponse(key, latitude, longitude, "now", "us")
	if err != nil {
		return nil, err
	}

	json, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return json, nil
}

func writeCache(cacheFile string, json []byte) (err error) {
	return ioutil.WriteFile(cacheFile, json, 0644)
}

func formatConditions(condition string) (icon string) {
	icon, ok := conditionIcons[condition]
	if !ok {
		icon = condition
	}
	return
}

func extractConditionFromJSON(jsonBlob []byte) (condition string) {
	f, err := forecast.FromJSON(jsonBlob)

	if err != nil {
		return "error"
	}

	if f.Code > 0 {
		return "error"
	}

	return f.Currently.Icon
}

func exitWith(message interface{}, status int) {
	fmt.Printf("%v\n", message)
	os.Exit(status)
}

func check(err error) {
	if err != nil {
		exitWith(err, 1)
	}
}
<commit_msg>Display helpful message when no location given<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"github.com/justincampbell/zipcode"
	"github.com/mlbright/forecast/v2"
)

var conditionIcons = map[string]string{
	"clear-day":           "☀",
	"clear-night":         "🌙",
	"cloudy":              "☁",
	"fog":                 "🌁",
	"partly-cloudy-day":   "⛅",
	"partly-cloudy-night": "🌙",
	"rain":                "☔",
	"sleet":               "❄ ☔",
	"snow":                "❄",
	"wind":                "🍃",
	"error":               "❗",
}

var maxCacheAge, _ = time.ParseDuration("1h")

var coordinates string
var key string
var tmpDir string
var zipCode string

func init() {
	flag.StringVar(&coordinates, "coordinates", "", "the coordinates, expressed as latitude,longitude")
	flag.StringVar(&key, "key", os.Getenv("FORECAST_IO_API_KEY"), "your forecast.io API key")
	flag.StringVar(&tmpDir, "tmpdir", os.TempDir(), "the directory to use to store cached responses")
	flag.StringVar(&zipCode, "zipcode", "", "a USPS ZIP Code")

	flag.Parse()
}

func main() {
	var err error
	var latitude string
	var longitude string

	if key == "" {
		exitWith("Please provide your forecast.io API key with -key, or set FORECAST_IO_API_KEY", 1)
	}

	if coordinates == "" && zipCode == "" {
		exitWith("Please provide a -zipcode or -coordinates", 1)
	}

	if zipCode != "" {
		coord, err := zipcode.Lookup(zipCode)
		check(err)
		coordinates = coord.String()
	}

	coordinateParts := strings.Split(coordinates, ",")

	if len(coordinateParts) != 2 {
		exitWith("You must specify latitude and longitude like so: 39.95,-75.1667", 1)
	}

	latitude, longitude = coordinateParts[0], coordinateParts[1]

	cacheFilename := fmt.Sprintf("emoji-weather-%s-%s.json", latitude, longitude)
	cacheFile := path.Join(tmpDir, cacheFilename)

	var json []byte

	if isCacheStale(cacheFile) {
		json, err = getForecast(key, latitude, longitude)
		check(err)

		err = writeCache(cacheFile, json)
		check(err)
	} else {
		json, err = ioutil.ReadFile(cacheFile)
		check(err)
	}

	fmt.Println(formatConditions(extractConditionFromJSON(json)))
}

func isCacheStale(cacheFile string) bool {
	stat, err := os.Stat(cacheFile)

	return os.IsNotExist(err) || time.Since(stat.ModTime()) > maxCacheAge
}

func getForecast(key string, latitude string, longitude string) (json []byte, err error) {
	res, err := forecast.GetResponse(key, latitude, longitude, "now", "us")
	if err != nil {
		return nil, err
	}

	json, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return json, nil
}

func writeCache(cacheFile string, json []byte) (err error) {
	return ioutil.WriteFile(cacheFile, json, 0644)
}

func formatConditions(condition string) (icon string) {
	icon, ok := conditionIcons[condition]
	if !ok {
		icon = condition
	}
	return
}

func extractConditionFromJSON(jsonBlob []byte) (condition string) {
	f, err := forecast.FromJSON(jsonBlob)

	if err != nil {
		return "error"
	}

	if f.Code > 0 {
		return "error"
	}

	return f.Currently.Icon
}

func exitWith(message interface{}, status int) {
	fmt.Printf("%v\n", message)
	os.Exit(status)
}

func check(err error) {
	if err != nil {
		exitWith(err, 1)
	}
}
<|endoftext|>


<commit_before>package client

import (
	"log"
	"net/http"
	"runtime"
	"strings"
	"time"

	"github.com/getlantern/analytics"
	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/globals"
	"github.com/getlantern/flashlight/util"
)

const (
	cloudConfigPollInterval = time.Second * 60
)

// clientConfig holds global configuration settings for all clients.
var (
	clientConfig  *config
	trackingCodes = map[string]string{
		"FireTweet": "UA-21408036-4",
	}
)

// MobileClient is an extension of flashlight client with a few custom declarations for mobile
type MobileClient struct {
	client.Client
	closed  chan bool
	fronter *http.Client
}

// init attempts to setup client configuration.
func init() {
	clientConfig = defaultConfig()
}

// NewClient creates a proxy client.
func NewClient(addr, appName string) *MobileClient {

	client := client.Client{
		Addr:         addr,
		ReadTimeout:  0, // don't timeout
		WriteTimeout: 0,
	}

	err := globals.SetTrustedCAs(clientConfig.getTrustedCerts())
	if err != nil {
		log.Fatalf("Unable to configure trusted CAs: %s", err)
	}

	hqfd := client.Configure(clientConfig.Client)

	// store GA session event
	sessionPayload := &analytics.Payload{
		HitType: analytics.EventType,
		Event: &analytics.Event{
			Category: "Session",
			Action:   "Start",
			Label:    runtime.GOOS,
		},
	}

	// attach our app-specific analytics tracking code
	// to session info
	if appName != "" {
		if trackingId, ok := trackingCodes[appName]; ok {
			sessionPayload.TrackingId = trackingId
		}
	}

	httpClient, er := util.HTTPClient(cfg.CloudConfigCA, cfg.Addr)
	if er != nil {
		log.Errorf("Could not create HTTP client %v", er)
	} else {
		analytics.SessionEvent(httpClient, sessionPayload)
	}

	return &MobileClient{
		Client:  client,
		closed:  make(chan bool),
		fronter: hqfd.NewDirectDomainFronter(),
	}
}

func (client *MobileClient) ServeHTTP() {

	defer func() {
		close(client.closed)
	}()

	go func() {
		onListening := func() {
			log.Printf("Now listening for connections...")
		}
		if err := client.ListenAndServe(onListening); err != nil {
			// Error is not exported: https://golang.org/src/net/net.go#L284
			if !strings.Contains(err.Error(), "use of closed network connection") {
				panic(err.Error())
			}
		}
	}()
	go client.pollConfiguration()
}

// updateConfig attempts to pull a configuration file from the network using
// the client proxy itself.
func (client *MobileClient) updateConfig() error {
	var buf []byte
	var err error
	if buf, err = pullConfigFile(client.fronter); err != nil {
		return err
	}
	return clientConfig.updateFrom(buf)
}

// pollConfiguration periodically checks for updates in the cloud configuration
// file.
func (client *MobileClient) pollConfiguration() {
	pollTimer := time.NewTimer(cloudConfigPollInterval)
	defer pollTimer.Stop()

	for {
		select {
		case <-client.closed:
			return
		case <-pollTimer.C:
			// Attempt to update configuration.
			var err error
			if err = client.updateConfig(); err == nil {
				// Configuration changed, lets reload.
				err := globals.SetTrustedCAs(clientConfig.getTrustedCerts())
				if err != nil {
					log.Printf("Unable to configure trusted CAs: %s", err)
				}
				hqfc := client.Configure(clientConfig.Client)
				client.fronter = hqfc.NewDirectDomainFronter()
			}
			// Sleeping 'till next pull.
			pollTimer.Reset(cloudConfigPollInterval)
		}
	}
}

// Stop is currently not implemented but should make the listener stop
// accepting new connections and then kill all active connections.
func (client *MobileClient) Stop() error {
	if err := client.Client.Stop(); err != nil {
		log.Fatalf("Unable to stop proxy client: %q", err)
		return err
	}
	return nil
}
<commit_msg>Changed to record analytics only after the local server is definitely up and running #2540<commit_after>package client

import (
	"log"
	"net/http"
	"runtime"
	"strings"
	"time"

	"github.com/getlantern/analytics"
	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/globals"
	"github.com/getlantern/flashlight/util"
)

const (
	cloudConfigPollInterval = time.Second * 60
)

// clientConfig holds global configuration settings for all clients.
var (
	clientConfig  *config
	trackingCodes = map[string]string{
		"FireTweet": "UA-21408036-4",
	}
)

// MobileClient is an extension of flashlight client with a few custom declarations for mobile
type MobileClient struct {
	client.Client
	closed  chan bool
	fronter *http.Client
	appName string
}

// init attempts to setup client configuration.
func init() {
	clientConfig = defaultConfig()
}

// NewClient creates a proxy client.
func NewClient(addr, appName string) *MobileClient {

	client := client.Client{
		Addr:         addr,
		ReadTimeout:  0, // don't timeout
		WriteTimeout: 0,
	}

	err := globals.SetTrustedCAs(clientConfig.getTrustedCerts())
	if err != nil {
		log.Fatalf("Unable to configure trusted CAs: %s", err)
	}

	hqfd := client.Configure(clientConfig.Client)

	return &MobileClient{
		Client:  client,
		closed:  make(chan bool),
		fronter: hqfd.NewDirectDomainFronter(),
		appName: appName,
	}
}

func (client *MobileClient) ServeHTTP() {

	defer func() {
		close(client.closed)
	}()

	go func() {
		onListening := func() {
			log.Printf("Now listening for connections...")
			go client.recordAnalytics()
		}
		if err := client.ListenAndServe(onListening); err != nil {
			// Error is not exported: https://golang.org/src/net/net.go#L284
			if !strings.Contains(err.Error(), "use of closed network connection") {
				panic(err.Error())
			}
		}
	}()
	go client.pollConfiguration()
}

func (client *MobileClient) recordAnalytics() {

	// store GA session event
	sessionPayload := &analytics.Payload{
		HitType: analytics.EventType,
		Event: &analytics.Event{
			Category: "Session",
			Action:   "Start",
			Label:    runtime.GOOS,
		},
	}

	// attach our app-specific analytics tracking code
	// to session info
	if client.appName != "" {
		if trackingId, ok := trackingCodes[client.appName]; ok {
			sessionPayload.TrackingId = trackingId
		}
	}

	// Report analytics, proxying through the local client. Note this
	// is a little unorthodox by Lantern standards because it doesn't
	// pin the certificate of the cloud.yaml root CA, instead relying
	// on the go defaults.
	httpClient, er := util.HTTPClient("", client.Client.Addr)
	if er != nil {
		log.Fatalf("Could not create HTTP client %v", er)
	} else {
		analytics.SessionEvent(httpClient, sessionPayload)
	}

}

// updateConfig attempts to pull a configuration file from the network using
// the client proxy itself.
func (client *MobileClient) updateConfig() error {
	var buf []byte
	var err error
	if buf, err = pullConfigFile(client.fronter); err != nil {
		return err
	}
	return clientConfig.updateFrom(buf)
}

// pollConfiguration periodically checks for updates in the cloud configuration
// file.
func (client *MobileClient) pollConfiguration() {
	pollTimer := time.NewTimer(cloudConfigPollInterval)
	defer pollTimer.Stop()

	for {
		select {
		case <-client.closed:
			return
		case <-pollTimer.C:
			// Attempt to update configuration.
			var err error
			if err = client.updateConfig(); err == nil {
				// Configuration changed, lets reload.
				err := globals.SetTrustedCAs(clientConfig.getTrustedCerts())
				if err != nil {
					log.Printf("Unable to configure trusted CAs: %s", err)
				}
				hqfc := client.Configure(clientConfig.Client)
				client.fronter = hqfc.NewDirectDomainFronter()
			}
			// Sleeping 'till next pull.
			pollTimer.Reset(cloudConfigPollInterval)
		}
	}
}

// Stop is currently not implemented but should make the listener stop
// accepting new connections and then kill all active connections.
func (client *MobileClient) Stop() error {
	if err := client.Client.Stop(); err != nil {
		log.Fatalf("Unable to stop proxy client: %q", err)
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package websocket

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apex/log"
	"github.com/gbrlsnchs/jwt/v3"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"github.com/pterodactyl/wings/config"
	"github.com/pterodactyl/wings/environment"
	"github.com/pterodactyl/wings/router/tokens"
	"github.com/pterodactyl/wings/server"
	"net/http"
	"strings"
	"sync"
	"time"
)

const (
	PermissionConnect          = "websocket.connect"
	PermissionSendCommand      = "control.console"
	PermissionSendPowerStart   = "control.start"
	PermissionSendPowerStop    = "control.stop"
	PermissionSendPowerRestart = "control.restart"
	PermissionReceiveErrors    = "admin.websocket.errors"
	PermissionReceiveInstall   = "admin.websocket.install"
	PermissionReceiveBackups   = "backup.read"
)

type Handler struct {
	sync.RWMutex
	Connection *websocket.Conn
	jwt        *tokens.WebsocketPayload `json:"-"`
	server     *server.Server
}

var (
	ErrJwtNotPresent    = errors.New("jwt: no jwt present")
	ErrJwtNoConnectPerm = errors.New("jwt: missing connect permission")
	ErrJwtUuidMismatch  = errors.New("jwt: server uuid mismatch")
)

func IsJwtError(err error) bool {
	return errors.Is(err, ErrJwtNotPresent) ||
		errors.Is(err, ErrJwtNoConnectPerm) ||
		errors.Is(err, ErrJwtUuidMismatch) ||
		errors.Is(err, jwt.ErrExpValidation)
}

// Parses a JWT into a websocket token payload.
func NewTokenPayload(token []byte) (*tokens.WebsocketPayload, error) {
	payload := tokens.WebsocketPayload{}
	err := tokens.ParseToken(token, &payload)
	if err != nil {
		return nil, err
	}

	if !payload.HasPermission(PermissionConnect) {
		return nil, errors.New("not authorized to connect to this socket")
	}

	return &payload, nil
}

// Returns a new websocket handler using the context provided.
func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Handler, error) {
	upgrader := websocket.Upgrader{
		// Ensure that the websocket request is originating from the Panel itself,
		// and not some other location.
		CheckOrigin: func(r *http.Request) bool {
			o := r.Header.Get("Origin")
			if o == config.Get().PanelLocation {
				return true
			}

			for _, origin := range config.Get().AllowedOrigins {
				if origin == "*" {
					return true
				}

				if o != origin {
					continue
				}

				return true
			}

			return false
		},
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return nil, err
	}

	return &Handler{
		Connection: conn,
		jwt:        nil,
		server:     s,
	}, nil
}

func (h *Handler) SendJson(v *Message) error {
	// Do not send JSON down the line if the JWT on the connection is not valid!
	if err := h.TokenValid(); err != nil {
		h.unsafeSendJson(Message{
			Event: ErrorEvent,
			Args:  []string{"could not authenticate client: " + err.Error()},
		})

		return nil
	}

	j := h.GetJwt()
	if j != nil {
		// If we're sending installation output but the user does not have the required
		// permissions to see the output, don't send it down the line.
		if v.Event == server.InstallOutputEvent {
			if !j.HasPermission(PermissionReceiveInstall) {
				return nil
			}
		}

		// If the user does not have permission to see backup events, do not emit
		// them over the socket.
		if strings.HasPrefix(v.Event, server.BackupCompletedEvent) {
			if !j.HasPermission(PermissionReceiveBackups) {
				return nil
			}
		}
	}

	return h.unsafeSendJson(v)
}

// Sends JSON over the websocket connection, ignoring the authentication state of the
// socket user. Do not call this directly unless you are positive a response should be
// sent back to the client!
func (h *Handler) unsafeSendJson(v interface{}) error {
	h.Lock()
	defer h.Unlock()

	return h.Connection.WriteJSON(v)
}

// Checks if the JWT is still valid.
func (h *Handler) TokenValid() error {
	j := h.GetJwt()
	if j == nil {
		return ErrJwtNotPresent
	}

	if err := jwt.ExpirationTimeValidator(time.Now())(&j.Payload); err != nil {
		return err
	}

	if !j.HasPermission(PermissionConnect) {
		return ErrJwtNoConnectPerm
	}

	if h.server.Id() != j.GetServerUuid() {
		return ErrJwtUuidMismatch
	}

	return nil
}

// Sends an error back to the connected websocket instance by checking the permissions
// of the token. If the user has the "receive-errors" grant we will send back the actual
// error message, otherwise we just send back a standard error message.
func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error {
	j := h.GetJwt()
	expected := errors.Is(err, server.ErrSuspended) ||
		errors.Is(err, server.ErrIsRunning) ||
		errors.Is(err, server.ErrNotEnoughDiskSpace)

	message := "an unexpected error was encountered while handling this request"
	if expected || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
		message = err.Error()
	}

	m, u := h.GetErrorMessage(message)

	wsm := Message{Event: ErrorEvent}
	wsm.Args = []string{m}

	if len(shouldLog) == 0 || (len(shouldLog) == 1 && shouldLog[0] == true) {
		if !expected && !IsJwtError(err) {
			h.server.Log().WithFields(log.Fields{"event": msg.Event, "error_identifier": u.String(), "error": err}).
				Error("failed to handle websocket process; an error was encountered processing an event")
		}
	}

	return h.unsafeSendJson(wsm)
}

// Converts an error message into a more readable representation and returns a UUID
// that can be cross-referenced to find the specific error that triggered.
func (h *Handler) GetErrorMessage(msg string) (string, uuid.UUID) {
	u := uuid.Must(uuid.NewRandom())

	m := fmt.Sprintf("Error Event [%s]: %s", u.String(), msg)

	return m, u
}

// Sets the JWT for the websocket in a race-safe manner.
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
	h.Lock()
	h.jwt = token
	h.Unlock()
}

func (h *Handler) GetJwt() *tokens.WebsocketPayload {
	h.RLock()
	defer h.RUnlock()

	return h.jwt
}

// Handle the inbound socket request and route it to the proper server action.
func (h *Handler) HandleInbound(m Message) error {
	if m.Event != AuthenticationEvent {
		if err := h.TokenValid(); err != nil {
			h.unsafeSendJson(Message{
				Event: ErrorEvent,
				Args:  []string{"could not authenticate client: " + err.Error()},
			})

			return nil
		}
	}

	switch m.Event {
	case AuthenticationEvent:
		{
			token, err := NewTokenPayload([]byte(strings.Join(m.Args, "")))
			if err != nil {
				// If the error says the JWT expired, send a token expired
				// event and hopefully the client renews the token.
				if err == jwt.ErrExpValidation {
					h.SendJson(&Message{Event: TokenExpiredEvent})
					return nil
				}

				return err
			}

			// Check if the user has previously authenticated successfully.
			newConnection := h.GetJwt() == nil

			// Previously there was a HasPermission(PermissionConnect) check around this,
			// however NewTokenPayload will return an error if it doesn't have the connect
			// permission meaning that it was a redundant function call.
			h.setJwt(token)

			// Tell the client they authenticated successfully.
			h.unsafeSendJson(Message{
				Event: AuthenticationSuccessEvent,
				Args:  []string{},
			})

			// Check if the client was refreshing their authentication token
			// instead of authenticating for the first time.
			if !newConnection {
				// This prevents duplicate status messages as outlined in
				// https://github.com/pterodactyl/panel/issues/2077
				return nil
			}

			// On every authentication event, send the current server status back
			// to the client. :)
			state := h.server.GetState()
			h.SendJson(&Message{
				Event: server.StatusEvent,
				Args:  []string{state},
			})

			// Only send the current disk usage if the server is offline, if docker container is running,
			// Environment#EnableResourcePolling() will send this data to all clients.
			if state == environment.ProcessOfflineState {
				_ = h.server.Filesystem.HasSpaceAvailable(false)

				b, _ := json.Marshal(h.server.Proc())
				h.SendJson(&Message{
					Event: server.StatsEvent,
					Args:  []string{string(b)},
				})
			}

			return nil
		}
	case SetStateEvent:
		{
			action := server.PowerAction(strings.Join(m.Args, ""))

			actions := make(map[server.PowerAction]string)
			actions[server.PowerActionStart] = PermissionSendPowerStart
			actions[server.PowerActionStop] = PermissionSendPowerStop
			actions[server.PowerActionRestart] = PermissionSendPowerRestart
			actions[server.PowerActionTerminate] = PermissionSendPowerStop

			// Check that they have permission to perform this action if it is needed.
			if permission, exists := actions[action]; exists {
				if !h.GetJwt().HasPermission(permission) {
					return nil
				}
			}

			err := h.server.HandlePowerAction(action)
			if errors.Is(err, context.DeadlineExceeded) {
				m, _ := h.GetErrorMessage("another power action is currently being processed for this server, please try again later")

				h.SendJson(&Message{
					Event: ErrorEvent,
					Args:  []string{m},
				})

				return nil
			}

			return err
		}
	case SendServerLogsEvent:
		{
			if running, _ := h.server.Environment.IsRunning(); !running {
				return nil
			}

			logs, err := h.server.Environment.Readlog(100)
			if err != nil {
				return err
			}

			for _, line := range logs {
				h.SendJson(&Message{
					Event: server.ConsoleOutputEvent,
					Args:  []string{line},
				})
			}

			return nil
		}
	case SendCommandEvent:
		{
			if !h.GetJwt().HasPermission(PermissionSendCommand) {
				return nil
			}

			if h.server.GetState() == environment.ProcessOfflineState {
				return nil
			}

			return h.server.Environment.SendCommand(strings.Join(m.Args, ""))
		}
	}

	return nil
}
<commit_msg>Mask errors from websocket being closed; closes pterodactyl/panel#2387<commit_after>package websocket

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apex/log"
	"github.com/gbrlsnchs/jwt/v3"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"github.com/pterodactyl/wings/config"
	"github.com/pterodactyl/wings/environment"
	"github.com/pterodactyl/wings/router/tokens"
	"github.com/pterodactyl/wings/server"
	"net/http"
	"strings"
	"sync"
	"time"
)

const (
	PermissionConnect          = "websocket.connect"
	PermissionSendCommand      = "control.console"
	PermissionSendPowerStart   = "control.start"
	PermissionSendPowerStop    = "control.stop"
	PermissionSendPowerRestart = "control.restart"
	PermissionReceiveErrors    = "admin.websocket.errors"
	PermissionReceiveInstall   = "admin.websocket.install"
	PermissionReceiveBackups   = "backup.read"
)

type Handler struct {
	sync.RWMutex
	Connection *websocket.Conn
	jwt        *tokens.WebsocketPayload `json:"-"`
	server     *server.Server
}

var (
	ErrJwtNotPresent    = errors.New("jwt: no jwt present")
	ErrJwtNoConnectPerm = errors.New("jwt: missing connect permission")
	ErrJwtUuidMismatch  = errors.New("jwt: server uuid mismatch")
)

func IsJwtError(err error) bool {
	return errors.Is(err, ErrJwtNotPresent) ||
		errors.Is(err, ErrJwtNoConnectPerm) ||
		errors.Is(err, ErrJwtUuidMismatch) ||
		errors.Is(err, jwt.ErrExpValidation)
}

// Parses a JWT into a websocket token payload.
func NewTokenPayload(token []byte) (*tokens.WebsocketPayload, error) {
	payload := tokens.WebsocketPayload{}
	err := tokens.ParseToken(token, &payload)
	if err != nil {
		return nil, err
	}

	if !payload.HasPermission(PermissionConnect) {
		return nil, errors.New("not authorized to connect to this socket")
	}

	return &payload, nil
}

// Returns a new websocket handler using the context provided.
func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Handler, error) {
	upgrader := websocket.Upgrader{
		// Ensure that the websocket request is originating from the Panel itself,
		// and not some other location.
		CheckOrigin: func(r *http.Request) bool {
			o := r.Header.Get("Origin")
			if o == config.Get().PanelLocation {
				return true
			}

			for _, origin := range config.Get().AllowedOrigins {
				if origin == "*" {
					return true
				}

				if o != origin {
					continue
				}

				return true
			}

			return false
		},
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return nil, err
	}

	return &Handler{
		Connection: conn,
		jwt:        nil,
		server:     s,
	}, nil
}

func (h *Handler) SendJson(v *Message) error {
	// Do not send JSON down the line if the JWT on the connection is not valid!
	if err := h.TokenValid(); err != nil {
		h.unsafeSendJson(Message{
			Event: ErrorEvent,
			Args:  []string{"could not authenticate client: " + err.Error()},
		})

		return nil
	}

	j := h.GetJwt()
	if j != nil {
		// If we're sending installation output but the user does not have the required
		// permissions to see the output, don't send it down the line.
		if v.Event == server.InstallOutputEvent {
			if !j.HasPermission(PermissionReceiveInstall) {
				return nil
			}
		}

		// If the user does not have permission to see backup events, do not emit
		// them over the socket.
		if strings.HasPrefix(v.Event, server.BackupCompletedEvent) {
			if !j.HasPermission(PermissionReceiveBackups) {
				return nil
			}
		}
	}

	if err := h.unsafeSendJson(v); err != nil {
		// Not entirely sure how this happens (likely just when there is a ton of console spam)
		// but I don't care to fix it right now, so just mask the error and throw a warning into
		// the logs for us to look into later.
		if errors.Is(err, websocket.ErrCloseSent) {
			if h.server != nil {
				h.server.Log().WithField("subsystem", "websocket").
					WithField("event", v.Event).
					Warn("failed to send event to websocket: close already sent")
			}
			return nil
		}

		return err
	}

	return nil
}

// Sends JSON over the websocket connection, ignoring the authentication state of the
// socket user. Do not call this directly unless you are positive a response should be
// sent back to the client!
func (h *Handler) unsafeSendJson(v interface{}) error {
	h.Lock()
	defer h.Unlock()

	return h.Connection.WriteJSON(v)
}

// Checks if the JWT is still valid.
func (h *Handler) TokenValid() error {
	j := h.GetJwt()
	if j == nil {
		return ErrJwtNotPresent
	}

	if err := jwt.ExpirationTimeValidator(time.Now())(&j.Payload); err != nil {
		return err
	}

	if !j.HasPermission(PermissionConnect) {
		return ErrJwtNoConnectPerm
	}

	if h.server.Id() != j.GetServerUuid() {
		return ErrJwtUuidMismatch
	}

	return nil
}

// Sends an error back to the connected websocket instance by checking the permissions
// of the token. If the user has the "receive-errors" grant we will send back the actual
// error message, otherwise we just send back a standard error message.
func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error {
	j := h.GetJwt()
	expected := errors.Is(err, server.ErrSuspended) ||
		errors.Is(err, server.ErrIsRunning) ||
		errors.Is(err, server.ErrNotEnoughDiskSpace)

	message := "an unexpected error was encountered while handling this request"
	if expected || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
		message = err.Error()
	}

	m, u := h.GetErrorMessage(message)

	wsm := Message{Event: ErrorEvent}
	wsm.Args = []string{m}

	if len(shouldLog) == 0 || (len(shouldLog) == 1 && shouldLog[0] == true) {
		if !expected && !IsJwtError(err) {
			h.server.Log().WithFields(log.Fields{"event": msg.Event, "error_identifier": u.String(), "error": err}).
				Error("failed to handle websocket process; an error was encountered processing an event")
		}
	}

	return h.unsafeSendJson(wsm)
}

// Converts an error message into a more readable representation and returns a UUID
// that can be cross-referenced to find the specific error that triggered.
func (h *Handler) GetErrorMessage(msg string) (string, uuid.UUID) {
	u := uuid.Must(uuid.NewRandom())

	m := fmt.Sprintf("Error Event [%s]: %s", u.String(), msg)

	return m, u
}

// Sets the JWT for the websocket in a race-safe manner.
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
	h.Lock()
	h.jwt = token
	h.Unlock()
}

func (h *Handler) GetJwt() *tokens.WebsocketPayload {
	h.RLock()
	defer h.RUnlock()

	return h.jwt
}

// Handle the inbound socket request and route it to the proper server action.
func (h *Handler) HandleInbound(m Message) error {
	if m.Event != AuthenticationEvent {
		if err := h.TokenValid(); err != nil {
			h.unsafeSendJson(Message{
				Event: ErrorEvent,
				Args:  []string{"could not authenticate client: " + err.Error()},
			})

			return nil
		}
	}

	switch m.Event {
	case AuthenticationEvent:
		{
			token, err := NewTokenPayload([]byte(strings.Join(m.Args, "")))
			if err != nil {
				// If the error says the JWT expired, send a token expired
				// event and hopefully the client renews the token.
				if err == jwt.ErrExpValidation {
					h.SendJson(&Message{Event: TokenExpiredEvent})
					return nil
				}

				return err
			}

			// Check if the user has previously authenticated successfully.
			newConnection := h.GetJwt() == nil

			// Previously there was a HasPermission(PermissionConnect) check around this,
			// however NewTokenPayload will return an error if it doesn't have the connect
			// permission meaning that it was a redundant function call.
			h.setJwt(token)

			// Tell the client they authenticated successfully.
			h.unsafeSendJson(Message{
				Event: AuthenticationSuccessEvent,
				Args:  []string{},
			})

			// Check if the client was refreshing their authentication token
			// instead of authenticating for the first time.
			if !newConnection {
				// This prevents duplicate status messages as outlined in
				// https://github.com/pterodactyl/panel/issues/2077
				return nil
			}

			// On every authentication event, send the current server status back
			// to the client. :)
			state := h.server.GetState()
			h.SendJson(&Message{
				Event: server.StatusEvent,
				Args:  []string{state},
			})

			// Only send the current disk usage if the server is offline, if docker container is running,
			// Environment#EnableResourcePolling() will send this data to all clients.
			if state == environment.ProcessOfflineState {
				_ = h.server.Filesystem.HasSpaceAvailable(false)

				b, _ := json.Marshal(h.server.Proc())
				h.SendJson(&Message{
					Event: server.StatsEvent,
					Args:  []string{string(b)},
				})
			}

			return nil
		}
	case SetStateEvent:
		{
			action := server.PowerAction(strings.Join(m.Args, ""))

			actions := make(map[server.PowerAction]string)
			actions[server.PowerActionStart] = PermissionSendPowerStart
			actions[server.PowerActionStop] = PermissionSendPowerStop
			actions[server.PowerActionRestart] = PermissionSendPowerRestart
			actions[server.PowerActionTerminate] = PermissionSendPowerStop

			// Check that they have permission to perform this action if it is needed.
			if permission, exists := actions[action]; exists {
				if !h.GetJwt().HasPermission(permission) {
					return nil
				}
			}

			err := h.server.HandlePowerAction(action)
			if errors.Is(err, context.DeadlineExceeded) {
				m, _ := h.GetErrorMessage("another power action is currently being processed for this server, please try again later")

				h.SendJson(&Message{
					Event: ErrorEvent,
					Args:  []string{m},
				})

				return nil
			}

			return err
		}
	case SendServerLogsEvent:
		{
			if running, _ := h.server.Environment.IsRunning(); !running {
				return nil
			}

			logs, err := h.server.Environment.Readlog(100)
			if err != nil {
				return err
			}

			for _, line := range logs {
				h.SendJson(&Message{
					Event: server.ConsoleOutputEvent,
					Args:  []string{line},
				})
			}

			return nil
		}
	case SendCommandEvent:
		{
			if !h.GetJwt().HasPermission(PermissionSendCommand) {
				return nil
			}

			if h.server.GetState() == environment.ProcessOfflineState {
				return nil
			}

			return h.server.Environment.SendCommand(strings.Join(m.Args, ""))
		}
	}

	return nil
}
<|endoftext|>