<commit_before>package boardgame

import (
	"encoding/json"
	"errors"
	"math/rand"
	"strconv"
	"time"
)

//maxRecurseCount is the number of fixUp moves that can be considered normal--
//anything more than that and we'll panic because the delegate is likely going
//to return fixup moves forever.
const maxRecurseCount = 50

//A Game represents a specific game between a collection of Players. Create a
//new one with NewGame().
type Game struct {
	manager *GameManager

	finished bool

	winners []int

	//The current version of State.
	version int

	//Memozied answer to CurrentState. Invalidated whenever ApplyMove is
	//called.
	cachedCurrentState State

	//Modifiable controls whether moves can be made on this game.
	modifiable bool

	//A unique ID provided to this game when it is created.
	id string

	//Proposed moves is where moves that have been proposed but have not yet been applied go.
	proposedMoves chan *proposedMoveItem

	//Initalized is set to True after SetUp is called.
	initalized bool

	//TODO: HistoricalState(index int) and HistoryLen() int

	//TODO: an array of Player objects.
}

const gameIDLength = 16

type DelayedError chan error

type proposedMoveItem struct {
	move Move
	//Ch is the channel we should either return an error on and then close, or
	//send nil and close.
	ch DelayedError
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

const randomStringChars = "ABCDEF0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//NewGame returns a new game. You must call SetUp before using it.
func NewGame(manager *GameManager) *Game {

	if manager == nil {
		return nil
	}

	result := &Game{
		manager: manager,
		//TODO: set the size of chan based on something more reasonable.
		//Note: this is also set similarly in manager.ModifiableGame
		proposedMoves: make(chan *proposedMoveItem, 20),
		id:            randomString(gameIDLength),
		modifiable:    true,
	}

	manager.modifiableGameCreated(result)

	return result

}

//Winners is the player indexes who were winners. Typically, this will be
//one player, but it could be multiple in the case of tie, or 0 in the
//case of a draw.
func (g *Game) Winners() []int {
	return g.winners
}

//Finished is whether the came has been completed. If it is over, the
//Winners will be set.
func (g *Game) Finished() bool {
	return g.finished
}

//Manager is a reference to the GameManager that controls this game.
//GameManager's methods will be called at key points in the lifecycle of
//this game.
func (g *Game) Manager() *GameManager {
	return g.manager
}

//MarshalJSONForPlayer returns an output similar to MarshalJSON, but with
//CurrentState set to a Sanitized state for the given Player.
func (g *Game) MarshalJSONForPlayer(playerIndex int) ([]byte, error) {
	return g.marshalJSONImpl(playerIndex)
}

func (g *Game) marshalJSONImpl(playerIndex int) ([]byte, error) {

	result := map[string]interface{}{
		"Name":         g.Name(),
		"Finished":     g.Finished(),
		"Winners":      g.Winners(),
		"CurrentState": g.Manager().SanitizedStateForPlayer(g.CurrentState(), playerIndex),
		"Id":           g.Id(),
		"Version":      g.Version(),
	}

	return json.Marshal(result)
}

func (g *Game) MarshalJSON() ([]byte, error) {
	//We define our own MarshalJSON because if we didn't there'd be an infinite loop because of the redirects back up.
	return g.marshalJSONImpl(-1)
}

func (g *Game) Name() string {
	return g.manager.Delegate().Name()
}

func (g *Game) Id() string {
	return g.id
}

//Version returns the version number of the highest State that is stored for
//this game. This number will increase by one every time a move (either Player
//or FixUp) is applied.
func (g *Game) Version() int {
	return g.version
}

//CurrentVersion returns the state object for the current state. Equivalent,
//semantically, to game.State(game.Version())
func (g *Game) CurrentState() State {
	if g.cachedCurrentState == nil {
		g.cachedCurrentState = g.State(g.Version())
	}
	return g.cachedCurrentState
}

//Returns the game's atate at the current version.
func (g *Game) State(version int) State {

	if version < 0 || version > g.Version() {
		return nil
	}

	if result, err := g.manager.Storage().State(g, version); err == nil {
		return result
	}

	return nil

}

//SetUp should be called a single time after all of the member variables are
//set correctly, including Chest. SetUp must be called before ProposeMove can
//be called. Even if an error is returned, the game should be in a consistent
//state. If numPlayers is 0, we will use delegate.DefaultNumPlayers().
func (g *Game) SetUp(numPlayers int) error {

	if g.initalized {
		return errors.New("Game already initalized")
	}

	//TODO: we don't need this anymore because managers can't be created without chests.
	if g.manager.Chest() == nil {
		return errors.New("No component chest set on manager")
	}

	if numPlayers == 0 {
		numPlayers = g.manager.Delegate().DefaultNumPlayers()
	}

	//We'll work on a copy of Payload, so if it fails at some point we can just drop it
	stateCopy := g.manager.Delegate().StartingState(numPlayers)

	if stateCopy == nil {
		return errors.New("Delegate didn't return a starter state.")
	}

	//Distribute all components to their starter locations

	for _, name := range g.Chest().DeckNames() {
		deck := g.Chest().Deck(name)
		for i, component := range deck.Components() {
			if err := g.manager.Delegate().DistributeComponentToStarterStack(stateCopy, component); err != nil {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ":" + err.Error())
			}
		}
	}

	g.manager.delegate.FinishSetUp(stateCopy)

	if g.Modifiable() {

		//Save the initial state to DB.
		g.manager.Storage().SaveGameAndCurrentState(g, stateCopy)

		go g.mainLoop()
	}

	g.initalized = true

	return nil
}

//MainLoop should be run in a goroutine. It is what takes moves off of
//proposedMoves and applies them. It is the only method that may call
//applyMove.
func (g *Game) mainLoop() {

	for item := range g.proposedMoves {
		if item == nil {
			return
		}
		item.ch <- g.applyMove(item.move, false, 0)
		close(item.ch)
	}

}

//Modifiable returns true if this instantiation of the game can be modified.
//If false, this instantiation is read-only: attributes can be read, but not
//written. In practice this means moves cannot be made.
func (g *Game) Modifiable() bool {
	return g.modifiable
}

//PlayerMoves is a thin wrapper around GameManager.PlayerMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) PlayerMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result
}

//FixUpMoves is a thin wrapper around GameManager.FixUpMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) FixUpMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result

}

//PlayerMoveByName is a thin wrapper around GameManager.PlayerMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) PlayerMoveByName(name string) Move {
	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//FixUpMoveByName is a thin wrapper around GameManager.FixUpMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) FixUpMoveByName(name string) Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//Chest is the ComponentChest in use for this game.
func (g *Game) Chest() *ComponentChest {
	return g.manager.Chest()
}

//Refresh goes and sets this game object to reflect the current state of the
//underlying game in Storage. Basically, when you call manager.Game() you get
//a snapshot of the game in storage at that moment. If you believe that the
//underlying game in storage has been modified, calling Refresh() will re-load
//the snapshot, effectively. Most useful after calling ProposeMove() on a non-
//modifiable game.
func (g *Game) Refresh() {

	freshGame := g.manager.Game(g.Id())

	g.cachedCurrentState = nil
	g.version = freshGame.Version()
	g.finished = freshGame.Finished()
	g.winners = freshGame.Winners()

}

//ProposedMove is the way to propose a move to the game. DelayedError will
//return an error in the future if the move was unable to be applied, or nil
//if the move was applied successfully. DelayedError will only resolve once
//any applicable FixUp moves have been applied already. Note: DelayedError
//won't return anything until after SetUp has been called. This is legal to
//call on a non-modifiable game--the change will be dispatched to a modifiable
//version of the game with this ID. However, note that if you call it on a
//non-modifiable game, even once DelayedError has resolved, the original game
//will still represent its old state. If you wantt to see its current state,
//calling game.Refresh() after DelayedError has resolved should contain the
//move changes you proposed, if they were accepted (and of course potentially
//more moves if other moves were applied in the meantime).
func (g *Game) ProposeMove(move Move) DelayedError {

	if !g.Modifiable() {
		return g.manager.proposeMoveOnGame(g.Id(), move)
	}

	errChan := make(DelayedError, 1)

	workItem := &proposedMoveItem{
		move: move,
		ch:   errChan,
	}

	g.proposedMoves <- workItem

	return errChan

}

//Game applies the move to the state if it is currently legal. May only be
//called by mainLoop. Propose moves with game.ProposeMove instead.
func (g *Game) applyMove(move Move, isFixUp bool, recurseCount int) error {

	if !g.initalized {
		return errors.New("The game has not been initalized.")
	}

	if g.finished {
		return errors.New("Game was already finished")
	}

	if isFixUp {
		if g.FixUpMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Fix Up move for this game.")
		}
	} else {

		//Verify that the Move is actually configured to be part of this game.
		if g.PlayerMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Player move for this game.")
		}
	}

	currentState := g.CurrentState()

	if err := move.Legal(currentState); err != nil {
		//It's not legal, reject.
		return errors.New("The move was not legal: " + err.Error())
	}

	newState := currentState.Copy(false)

	if err := move.Apply(newState); err != nil {
		return errors.New("The move's apply function returned an error:" + err.Error())
	}

	//Check to see if that move made the game finished.

	finished, winners := g.manager.Delegate().CheckGameFinished(newState)

	if finished {
		g.finished = true
		g.winners = winners
		//TODO: persist to database here.
	}

	g.version = g.version + 1
	//Expire the currentState cache; it's no longer valid.
	g.cachedCurrentState = nil

	//TODO: test that if we fail to save state to storage everything's fine.
	if err := g.manager.Storage().SaveGameAndCurrentState(g, newState); err != nil {
		return errors.New("Storage returned an error:" + err.Error())
	}

	if recurseCount > maxRecurseCount {
		panic("We recursed deeply in fixup, which implies that ProposeFixUp has a move that is always legal. Quitting.")
	}

	move = g.manager.Delegate().ProposeFixUpMove(newState)

	if move != nil {
		//We apply the move immediately. This ensures that when
		//DelayedError resolves, all of the fix up moves have been
		//applied.
		g.applyMove(move, true, recurseCount+1)
	}

	return nil

}
<commit_msg>Game has a JSONForPlayer() that returns an interface{} appropriate for being passed to json.Marshal. This is necessary because Server needs an unmarshaled object, otherwise we'd need to unmarshal again. Part of #40.<commit_after>package boardgame

import (
	"encoding/json"
	"errors"
	"math/rand"
	"strconv"
	"time"
)

//maxRecurseCount is the number of fixUp moves that can be considered normal--
//anything more than that and we'll panic because the delegate is likely going
//to return fixup moves forever.
const maxRecurseCount = 50

//A Game represents a specific game between a collection of Players. Create a
//new one with NewGame().
type Game struct {
	manager *GameManager

	finished bool

	winners []int

	//The current version of State.
	version int

	//Memozied answer to CurrentState. Invalidated whenever ApplyMove is
	//called.
	cachedCurrentState State

	//Modifiable controls whether moves can be made on this game.
	modifiable bool

	//A unique ID provided to this game when it is created.
	id string

	//Proposed moves is where moves that have been proposed but have not yet been applied go.
	proposedMoves chan *proposedMoveItem

	//Initalized is set to True after SetUp is called.
	initalized bool

	//TODO: HistoricalState(index int) and HistoryLen() int

	//TODO: an array of Player objects.
}

const gameIDLength = 16

type DelayedError chan error

type proposedMoveItem struct {
	move Move
	//Ch is the channel we should either return an error on and then close, or
	//send nil and close.
	ch DelayedError
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

const randomStringChars = "ABCDEF0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//NewGame returns a new game. You must call SetUp before using it.
func NewGame(manager *GameManager) *Game {

	if manager == nil {
		return nil
	}

	result := &Game{
		manager: manager,
		//TODO: set the size of chan based on something more reasonable.
		//Note: this is also set similarly in manager.ModifiableGame
		proposedMoves: make(chan *proposedMoveItem, 20),
		id:            randomString(gameIDLength),
		modifiable:    true,
	}

	manager.modifiableGameCreated(result)

	return result

}

//Winners is the player indexes who were winners. Typically, this will be
//one player, but it could be multiple in the case of tie, or 0 in the
//case of a draw.
func (g *Game) Winners() []int {
	return g.winners
}

//Finished is whether the came has been completed. If it is over, the
//Winners will be set.
func (g *Game) Finished() bool {
	return g.finished
}

//Manager is a reference to the GameManager that controls this game.
//GameManager's methods will be called at key points in the lifecycle of
//this game.
func (g *Game) Manager() *GameManager {
	return g.manager
}

//JSONForPlayer returns an object appropriate for being json'd via
//json.Marshal. The object is the equivalent to what MarshalJSON would output,
//only as an object, and with CurrentState equal to a sanitized state for the
//given player.
func (g *Game) JSONForPlayer(playerIndex int) interface{} {
	return map[string]interface{}{
		"Name":         g.Name(),
		"Finished":     g.Finished(),
		"Winners":      g.Winners(),
		"CurrentState": g.Manager().SanitizedStateForPlayer(g.CurrentState(), playerIndex),
		"Id":           g.Id(),
		"Version":      g.Version(),
	}
}

func (g *Game) MarshalJSON() ([]byte, error) {
	//We define our own MarshalJSON because if we didn't there'd be an infinite loop because of the redirects back up.
	return json.Marshal(g.JSONForPlayer(-1))
}

func (g *Game) Name() string {
	return g.manager.Delegate().Name()
}

func (g *Game) Id() string {
	return g.id
}

//Version returns the version number of the highest State that is stored for
//this game. This number will increase by one every time a move (either Player
//or FixUp) is applied.
func (g *Game) Version() int {
	return g.version
}

//CurrentVersion returns the state object for the current state. Equivalent,
//semantically, to game.State(game.Version())
func (g *Game) CurrentState() State {
	if g.cachedCurrentState == nil {
		g.cachedCurrentState = g.State(g.Version())
	}
	return g.cachedCurrentState
}

//Returns the game's atate at the current version.
func (g *Game) State(version int) State {

	if version < 0 || version > g.Version() {
		return nil
	}

	if result, err := g.manager.Storage().State(g, version); err == nil {
		return result
	}

	return nil

}

//SetUp should be called a single time after all of the member variables are
//set correctly, including Chest. SetUp must be called before ProposeMove can
//be called. Even if an error is returned, the game should be in a consistent
//state. If numPlayers is 0, we will use delegate.DefaultNumPlayers().
func (g *Game) SetUp(numPlayers int) error {

	if g.initalized {
		return errors.New("Game already initalized")
	}

	//TODO: we don't need this anymore because managers can't be created without chests.
	if g.manager.Chest() == nil {
		return errors.New("No component chest set on manager")
	}

	if numPlayers == 0 {
		numPlayers = g.manager.Delegate().DefaultNumPlayers()
	}

	//We'll work on a copy of Payload, so if it fails at some point we can just drop it
	stateCopy := g.manager.Delegate().StartingState(numPlayers)

	if stateCopy == nil {
		return errors.New("Delegate didn't return a starter state.")
	}

	//Distribute all components to their starter locations

	for _, name := range g.Chest().DeckNames() {
		deck := g.Chest().Deck(name)
		for i, component := range deck.Components() {
			if err := g.manager.Delegate().DistributeComponentToStarterStack(stateCopy, component); err != nil {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ":" + err.Error())
			}
		}
	}

	g.manager.delegate.FinishSetUp(stateCopy)

	if g.Modifiable() {

		//Save the initial state to DB.
		g.manager.Storage().SaveGameAndCurrentState(g, stateCopy)

		go g.mainLoop()
	}

	g.initalized = true

	return nil
}

//MainLoop should be run in a goroutine. It is what takes moves off of
//proposedMoves and applies them. It is the only method that may call
//applyMove.
func (g *Game) mainLoop() {

	for item := range g.proposedMoves {
		if item == nil {
			return
		}
		item.ch <- g.applyMove(item.move, false, 0)
		close(item.ch)
	}

}

//Modifiable returns true if this instantiation of the game can be modified.
//If false, this instantiation is read-only: attributes can be read, but not
//written. In practice this means moves cannot be made.
func (g *Game) Modifiable() bool {
	return g.modifiable
}

//PlayerMoves is a thin wrapper around GameManager.PlayerMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) PlayerMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result
}

//FixUpMoves is a thin wrapper around GameManager.FixUpMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) FixUpMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result

}

//PlayerMoveByName is a thin wrapper around GameManager.PlayerMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) PlayerMoveByName(name string) Move {
	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//FixUpMoveByName is a thin wrapper around GameManager.FixUpMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) FixUpMoveByName(name string) Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//Chest is the ComponentChest in use for this game.
func (g *Game) Chest() *ComponentChest {
	return g.manager.Chest()
}

//Refresh goes and sets this game object to reflect the current state of the
//underlying game in Storage. Basically, when you call manager.Game() you get
//a snapshot of the game in storage at that moment. If you believe that the
//underlying game in storage has been modified, calling Refresh() will re-load
//the snapshot, effectively. Most useful after calling ProposeMove() on a non-
//modifiable game.
func (g *Game) Refresh() {

	freshGame := g.manager.Game(g.Id())

	g.cachedCurrentState = nil
	g.version = freshGame.Version()
	g.finished = freshGame.Finished()
	g.winners = freshGame.Winners()

}

//ProposedMove is the way to propose a move to the game. DelayedError will
//return an error in the future if the move was unable to be applied, or nil
//if the move was applied successfully. DelayedError will only resolve once
//any applicable FixUp moves have been applied already. Note: DelayedError
//won't return anything until after SetUp has been called. This is legal to
//call on a non-modifiable game--the change will be dispatched to a modifiable
//version of the game with this ID. However, note that if you call it on a
//non-modifiable game, even once DelayedError has resolved, the original game
//will still represent its old state. If you wantt to see its current state,
//calling game.Refresh() after DelayedError has resolved should contain the
//move changes you proposed, if they were accepted (and of course potentially
//more moves if other moves were applied in the meantime).
func (g *Game) ProposeMove(move Move) DelayedError {

	if !g.Modifiable() {
		return g.manager.proposeMoveOnGame(g.Id(), move)
	}

	errChan := make(DelayedError, 1)

	workItem := &proposedMoveItem{
		move: move,
		ch:   errChan,
	}

	g.proposedMoves <- workItem

	return errChan

}

//Game applies the move to the state if it is currently legal. May only be
//called by mainLoop. Propose moves with game.ProposeMove instead.
func (g *Game) applyMove(move Move, isFixUp bool, recurseCount int) error {

	if !g.initalized {
		return errors.New("The game has not been initalized.")
	}

	if g.finished {
		return errors.New("Game was already finished")
	}

	if isFixUp {
		if g.FixUpMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Fix Up move for this game.")
		}
	} else {

		//Verify that the Move is actually configured to be part of this game.
		if g.PlayerMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Player move for this game.")
		}
	}

	currentState := g.CurrentState()

	if err := move.Legal(currentState); err != nil {
		//It's not legal, reject.
		return errors.New("The move was not legal: " + err.Error())
	}

	newState := currentState.Copy(false)

	if err := move.Apply(newState); err != nil {
		return errors.New("The move's apply function returned an error:" + err.Error())
	}

	//Check to see if that move made the game finished.

	finished, winners := g.manager.Delegate().CheckGameFinished(newState)

	if finished {
		g.finished = true
		g.winners = winners
		//TODO: persist to database here.
	}

	g.version = g.version + 1
	//Expire the currentState cache; it's no longer valid.
	g.cachedCurrentState = nil

	//TODO: test that if we fail to save state to storage everything's fine.
	if err := g.manager.Storage().SaveGameAndCurrentState(g, newState); err != nil {
		return errors.New("Storage returned an error:" + err.Error())
	}

	if recurseCount > maxRecurseCount {
		panic("We recursed deeply in fixup, which implies that ProposeFixUp has a move that is always legal. Quitting.")
	}

	move = g.manager.Delegate().ProposeFixUpMove(newState)

	if move != nil {
		//We apply the move immediately. This ensures that when
		//DelayedError resolves, all of the fix up moves have been
		//applied.
		g.applyMove(move, true, recurseCount+1)
	}

	return nil

}
<|endoftext|>


<commit_before>package gamedayapi

import (
	"encoding/xml"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	s "strings"
)

// 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"`
	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"`
	GameDataDirectory string `xml:"game_data_directory,attr"`
	GamePk            string `xml:"game_pk,attr"`
	GameType          string `xml:"game_type,attr"`
	Timezone          string `xml:"time_zone,attr"`
	Venue             string `xml:"venue,attr"`

	boxScore   BoxScore
	allInnings AllInnings
}

func GameFor(teamCode string, date string) (*Game, error) {
	epg := EpgFor(date)
	game, err := epg.GameForTeam(teamCode)
	if err != nil {
		return &Game{}, err
	}
	return game, nil
}

func (game *Game) BoxScore() *BoxScore {
	if len(game.boxScore.GameID) == 0 {
		game.load("/boxscore.xml", &game.boxScore)
	}
	return &game.boxScore
}

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

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

func fetchAndCache(filePath string, val interface{}) {
	log.Println("Fetching " + filePath + " from MLB")
	resp, err := http.Get(GamedayHostname + filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(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>Starting to work through rounding out the data. Adding placeholders for the files yet to fetch.<commit_after>package gamedayapi

import (
	"encoding/xml"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	s "strings"
)

// 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"`
	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"`
	GameDataDirectory string `xml:"game_data_directory,attr"`
	GamePk            string `xml:"game_pk,attr"`
	GameType          string `xml:"game_type,attr"`
	Timezone          string `xml:"time_zone,attr"`
	Venue             string `xml:"venue,attr"`

	boxScore   BoxScore
	allInnings AllInnings
}

func GameFor(teamCode string, date string) (*Game, error) {
	epg := EpgFor(date)
	game, err := epg.GameForTeam(teamCode)
	if err != nil {
		return &Game{}, err
	}
	return game, nil
}

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

func (game *Game) BoxScore() *BoxScore {
	if len(game.boxScore.GameID) == 0 {
		game.load("/boxscore.xml", &game.boxScore)
	}
	return &game.boxScore
}

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

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

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

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

func fetchAndCache(filePath string, val interface{}) {
	log.Println("Fetching " + filePath + " from MLB")
	resp, err := http.Get(GamedayHostname + filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(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>package models

import (
	"fmt"
	"encoding/json"
	"strings"
	"time"
)

/*
GenericFile contains information about a file that makes up
part (or all) of an IntellectualObject.

IntellectualObject is the object to which the file belongs.

Format is typically a mime-type, such as "application/xml",
that describes the file format.

URI describes the location of the object (in APTrust?).

Size is the size of the object, in bytes.

FileCreated is the date and time at which the file was created
by the depositor.

FileModified is the data and time at which the object was last
modified (in APTrust, or at the institution that owns it?).

CreatedAt and UpdatedAt are Rails timestamps describing when
this GenericFile records was created and last updated.

FileCreated and FileModified should be ISO8601 DateTime strings,
such as:
1994-11-05T08:15:30-05:00     (Local Time)
1994-11-05T08:15:30Z          (UTC)
*/
type GenericFile struct {
	// Pharos fields.

	// The Rails/Database id for this generic file.
	// If the Id is non-zero, it's been recorded in Pharos.
	Id                           int            `json:"id"`

	// The human-readable identifier for this file. It consists of
	// the object name, followed by a slash, followed by the path
	// of the file within the bag. E.g. "virginia.edu/bag001/data/file1.pdf"
	Identifier                   string         `json:"identifier"`

	// The id of the IntellectualObject to which this file belongs.
	IntellectualObjectId         int            `json:"intellectual_object_id"`

	// The identifier of the intellectual object to which this file belongs.
	IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`

	// The file's mime type. E.g. "application/xml"
	FileFormat                   string         `json:"file_format"`

	// The location of this file in our primary s3 long-term storage bucket.
	URI                          string         `json:"uri"`

	// The size of the file, in bytes.
	Size                         int64          `json:"size"`

	// The date this file was created by the depositor. This date comes from
	// the file record in the tarred bag.
	FileCreated                  time.Time      `json:"file_created"`

	// The date this file was last modified by the depository. This date comes
	// from the file record in the tarred bag.
	FileModified                 time.Time      `json:"file_modified"`

	// A timestamp indicating when this GenericFile record was created in
	// our repository.
	CreatedAt                    time.Time      `json:"created_at"`

	// A timestamp indicating when this GenericFile record was last updated in
	// our repository.
	UpdatedAt                    time.Time      `json:"updated_at"`

	// A list of checksums for this file.
	Checksums                    []*Checksum    `json:"checksums"`

	// A list of PREMIS events for this file.
	PremisEvents                 []*PremisEvent `json:"premis_events"`

	// ----------------------------------------------------
	// The fields below are for internal housekeeping.
	// We don't send this data to Pharos.
	// ----------------------------------------------------

	// IngestFileType can be one of the types defined in constants.
	// PAYLOAD_FILE, PAYLOAD_MANIFEST, TAG_MANIFEST, TAG_FILE
	IngestFileType               string         `json:"ingest_file_type"`

	// IngestLocalPath is the absolute path to this file on local disk.
	// It may be empty if we're working with a tar file.
	IngestLocalPath              string         `json:"ingest_local_path"`

	// IngestManifestMd5 is the md5 checksum of this file, as reported
	// in the bag's manifest-md5.txt file. This may be empty if there
	// was no md5 checksum file, or if this generic file wasn't listed
	// in the md5 manifest.
	IngestManifestMd5            string         `json:"ingest_manifest_md5"`

	// The md5 checksum we calculated at ingest from the actual file.
	IngestMd5                    string         `json:"ingest_md5"`

	// DateTime we calculated the md5 digest from local file.
	IngestMd5GeneratedAt         time.Time      `json:"ingest_md5_generated_at"`

	// DateTime we verified that our md5 checksum matches what's in the manifest.
	IngestMd5VerifiedAt          time.Time      `json:"ingest_md5_verified_at"`

	// The sha256 checksum for this file, as reported in the payload manifest.
	// This may be empty if the bag had no sha256 manifest, or if this file
	// was not listed in the manifest.
	IngestManifestSha256         string         `json:"ingest_manifest_sha256"`

	// The sha256 checksum we calculated when we read the actual file.
	IngestSha256                 string         `json:"ingest_sha_256"`

	// Timestamp of when we calculated the sha256 checksum.
	IngestSha256GeneratedAt      time.Time      `json:"ingest_sha_256_generated_at"`

	// Timestamp of when we verified that the sha256 checksum we calculated
	// matches what's in the manifest.
	IngestSha256VerifiedAt       time.Time      `json:"ingest_sha_256_verified_at"`

	// The UUID assigned to this file. This will be its S3 key when we store it.
	IngestUUID                   string         `json:"ingest_uuid"`

	// Timestamp of when we generated the UUID for this file. Needed to create
	// the identifier assignment PREMIS event.
	IngestUUIDGeneratedAt        time.Time      `json:"ingest_uuid_generated_at"`

	// Where this file is stored in S3.
	IngestStorageURL             string         `json:"ingest_storage_url"`

	// Timestamp indicating when this file was stored in S3.
	IngestStoredAt               time.Time      `json:"ingest_stored_at"`

	// Where this file is stored in Glacier.
	IngestReplicationURL         string         `json:"ingest_replication_url"`

	// Timestamp indicating when this file was stored in Glacier.
	IngestReplicatedAt           time.Time      `json:"ingest_replicated_at"`

	// If true, a previous version of this same file exists in S3/Glacier.
	IngestPreviousVersionExists  bool           `json:"ingest_previous_version_exists"`

	// If true, this file needs to be saved to S3.
	IngestNeedsSave              bool           `json:"ingest_needs_save"`

	// Error that occurred during ingest. If empty, there was no error.
	IngestErrorMessage           string         `json:"ingesterror_message"`

	// File User Id (unreliable)
	IngestFileUid                int            `json:"ingest_file_uid"`

	// File Group Id (unreliable)
	IngestFileGid                int            `json:"ingest_file_gid"`

	// File User Name (unreliable)
	IngestFileUname              string         `json:"ingest_file_uname"`

	// File Group Name (unreliable)
	IngestFileGname              string         `json:"ingest_file_gname"`

	// File Mode/Permissions (unreliable)
	IngestFileMode               int64          `json:"ingest_file_mode"`
}

func NewGenericFile() (*GenericFile) {
	return &GenericFile{
		Checksums: make([]*Checksum, 0),
		PremisEvents: make([]*PremisEvent, 0),
		IngestPreviousVersionExists: false,
		IngestNeedsSave: true,
	}
}

// Serializes a version of GenericFile that Fluctus will accept as post/put input.
// Note that we don't serialize the id or any of our internal housekeeping info.
func (gf *GenericFile) SerializeForPharos() ([]byte, error) {
	// We have to create a temporary structure to prevent json.Marshal
	// from serializing Size (int64) with scientific notation.
	// Without this step, Size will be serialized as something like
	// 2.706525e+06, which is not valid JSON.
	temp := struct{
		Identifier                   string         `json:"identifier"`
		IntellectualObjectId         int            `json:"intellectual_object_id"`
		IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`
		FileFormat                   string         `json:"file_format"`
		URI                          string         `json:"uri"`
		Size                         int64          `json:"size"`
		FileCreated                  time.Time      `json:"file_created"`
		FileModified                 time.Time      `json:"file_modified"`
		CreatedAt                    time.Time      `json:"created_at"`
		UpdatedAt                    time.Time      `json:"updated_at"`
		Checksums                    []*Checksum    `json:"checksums"`
	} {
		Identifier:                     gf.Identifier,
		IntellectualObjectId:           gf.IntellectualObjectId,
		IntellectualObjectIdentifier:   gf.IntellectualObjectIdentifier,
		FileFormat:                     gf.FileFormat,
		URI:                            gf.URI,
		Size:                           gf.Size,
		FileCreated:                    gf.FileCreated,
		FileModified:                   gf.FileModified,
		Checksums:                      gf.Checksums,
	}
	return json.Marshal(temp)
}

// Returns the original path of the file within the original bag.
// This is just the identifier minus the institution id and bag name.
// For example, if the identifier is "uc.edu/cin.675812/data/object.properties",
// this returns "data/object.properties"
func (gf *GenericFile) OriginalPath() (string) {
	return strings.Replace(gf.Identifier, gf.IntellectualObjectIdentifier + "/", "", 1)
}

// Returns the name of the institution that owns this file.
func (gf *GenericFile) InstitutionIdentifier() (string, error) {
	parts := strings.Split(gf.Identifier, "/")
	if len(parts) < 2 {
		return "", fmt.Errorf("GenericFile identifier '%s' is not valid", gf.Identifier)
	}
	return parts[0], nil
}

// Returns the checksum digest for the given algorithm for this file.
func (gf *GenericFile) GetChecksum(algorithm string) (*Checksum) {
	for _, cs := range gf.Checksums {
		if cs != nil && cs.Algorithm == algorithm {
			return cs
		}
	}
	return nil
}

// Returns events of the specified type
func (gf *GenericFile) FindEventsByType(eventType string) ([]PremisEvent) {
	events := make([]PremisEvent, 0)
	for _, event := range gf.PremisEvents {
		if event != nil && event.EventType == eventType {
			events = append(events, *event)
		}
	}
	return events
}

// Returns the name of this file in the preservation storage bucket
// (that should be a UUID), or an error if the GenericFile does not
// have a valid preservation storage URL.
func (gf *GenericFile) PreservationStorageFileName() (string, error) {
	if strings.Index(gf.URI, "/") < 0 {
		return "", fmt.Errorf("Cannot get preservation storage file name because GenericFile has an invalid URI")
	}
	parts := strings.Split(gf.URI, "/")
	return parts[len(parts) - 1], nil
}
<commit_msg>Added fields related to restore, DPN, and fixity checking<commit_after>package models

import (
	"fmt"
	"encoding/json"
	"strings"
	"time"
)

/*
GenericFile contains information about a file that makes up
part (or all) of an IntellectualObject.

IntellectualObject is the object to which the file belongs.

Format is typically a mime-type, such as "application/xml",
that describes the file format.

URI describes the location of the object (in APTrust?).

Size is the size of the object, in bytes.

FileCreated is the date and time at which the file was created
by the depositor.

FileModified is the data and time at which the object was last
modified (in APTrust, or at the institution that owns it?).

CreatedAt and UpdatedAt are Rails timestamps describing when
this GenericFile records was created and last updated.

FileCreated and FileModified should be ISO8601 DateTime strings,
such as:
1994-11-05T08:15:30-05:00     (Local Time)
1994-11-05T08:15:30Z          (UTC)
*/
type GenericFile struct {
	// Pharos fields.

	// The Rails/Database id for this generic file.
	// If the Id is non-zero, it's been recorded in Pharos.
	Id                           int            `json:"id"`

	// The human-readable identifier for this file. It consists of
	// the object name, followed by a slash, followed by the path
	// of the file within the bag. E.g. "virginia.edu/bag001/data/file1.pdf"
	Identifier                   string         `json:"identifier"`

	// The id of the IntellectualObject to which this file belongs.
	IntellectualObjectId         int            `json:"intellectual_object_id"`

	// The identifier of the intellectual object to which this file belongs.
	IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`

	// The file's mime type. E.g. "application/xml"
	FileFormat                   string         `json:"file_format"`

	// The location of this file in our primary s3 long-term storage bucket.
	URI                          string         `json:"uri"`

	// The size of the file, in bytes.
	Size                         int64          `json:"size"`

	// The date this file was created by the depositor. This date comes from
	// the file record in the tarred bag.
	FileCreated                  time.Time      `json:"file_created"`

	// The date this file was last modified by the depository. This date comes
	// from the file record in the tarred bag.
	FileModified                 time.Time      `json:"file_modified"`

	// A timestamp indicating when this GenericFile record was created in
	// our repository.
	CreatedAt                    time.Time      `json:"created_at"`

	// A timestamp indicating when this GenericFile record was last updated in
	// our repository.
	UpdatedAt                    time.Time      `json:"updated_at"`

	// A list of checksums for this file.
	Checksums                    []*Checksum    `json:"checksums"`

	// A list of PREMIS events for this file.
	PremisEvents                 []*PremisEvent `json:"premis_events"`

	// ----------------------------------------------------
	// The fields below are for internal housekeeping
	// during the ingest process. We don't send this data
	// to Pharos, and none of the Ingest fields will be
	// populated on GenericFile objects retrieved from
	// Pharos.
	// ----------------------------------------------------

	// IngestFileType can be one of the types defined in constants.
	// PAYLOAD_FILE, PAYLOAD_MANIFEST, TAG_MANIFEST, TAG_FILE
	IngestFileType               string         `json:"ingest_file_type"`

	// IngestLocalPath is the absolute path to this file on local disk.
	// It may be empty if we're working with a tar file.
	IngestLocalPath              string         `json:"ingest_local_path"`

	// IngestManifestMd5 is the md5 checksum of this file, as reported
	// in the bag's manifest-md5.txt file. This may be empty if there
	// was no md5 checksum file, or if this generic file wasn't listed
	// in the md5 manifest.
	IngestManifestMd5            string         `json:"ingest_manifest_md5"`

	// The md5 checksum we calculated at ingest from the actual file.
	IngestMd5                    string         `json:"ingest_md5"`

	// DateTime we calculated the md5 digest from local file.
	IngestMd5GeneratedAt         time.Time      `json:"ingest_md5_generated_at"`

	// DateTime we verified that our md5 checksum matches what's in the manifest.
	IngestMd5VerifiedAt          time.Time      `json:"ingest_md5_verified_at"`

	// The sha256 checksum for this file, as reported in the payload manifest.
	// This may be empty if the bag had no sha256 manifest, or if this file
	// was not listed in the manifest.
	IngestManifestSha256         string         `json:"ingest_manifest_sha256"`

	// The sha256 checksum we calculated when we read the actual file.
	IngestSha256                 string         `json:"ingest_sha_256"`

	// Timestamp of when we calculated the sha256 checksum.
	IngestSha256GeneratedAt      time.Time      `json:"ingest_sha_256_generated_at"`

	// Timestamp of when we verified that the sha256 checksum we calculated
	// matches what's in the manifest.
	IngestSha256VerifiedAt       time.Time      `json:"ingest_sha_256_verified_at"`

	// The UUID assigned to this file. This will be its S3 key when we store it.
	IngestUUID                   string         `json:"ingest_uuid"`

	// Timestamp of when we generated the UUID for this file. Needed to create
	// the identifier assignment PREMIS event.
	IngestUUIDGeneratedAt        time.Time      `json:"ingest_uuid_generated_at"`

	// Where this file is stored in S3.
	IngestStorageURL             string         `json:"ingest_storage_url"`

	// Timestamp indicating when this file was stored in S3.
	IngestStoredAt               time.Time      `json:"ingest_stored_at"`

	// Where this file is stored in Glacier.
	IngestReplicationURL         string         `json:"ingest_replication_url"`

	// Timestamp indicating when this file was stored in Glacier.
	IngestReplicatedAt           time.Time      `json:"ingest_replicated_at"`

	// If true, a previous version of this same file exists in S3/Glacier.
	IngestPreviousVersionExists  bool           `json:"ingest_previous_version_exists"`

	// If true, this file needs to be saved to S3.
	IngestNeedsSave              bool           `json:"ingest_needs_save"`

	// Error that occurred during ingest. If empty, there was no error.
	IngestErrorMessage           string         `json:"ingesterror_message"`

	// File User Id (unreliable)
	IngestFileUid                int            `json:"ingest_file_uid"`

	// File Group Id (unreliable)
	IngestFileGid                int            `json:"ingest_file_gid"`

	// File User Name (unreliable)
	IngestFileUname              string         `json:"ingest_file_uname"`

	// File Group Name (unreliable)
	IngestFileGname              string         `json:"ingest_file_gname"`

	// File Mode/Permissions (unreliable)
	IngestFileMode               int64          `json:"ingest_file_mode"`

	// ----------------------------------------------------
	// The fields below are for internal housekeeping
	// during the restoration, fixity checking, and DPN
	// packaging processes, during which Exchange services
	// download files from long-term storage to run fixity
	// or to rebuild a bag from its component files. The
	// Fetch fields are not saved in Pharos,
	// and GenericFiles retrieved from Pharos will not have
	// Fetch data. Exchange populates those fields
	// as necessary, according to the work it's doing.
	// ----------------------------------------------------

	// FetchLocalPath is the path on the local file system where we
	// saved this file after retrieving it from S3 long-term storage.
	// We only set this when we fetch files to be restored or to be
	// packaged into a DPN bag. When we fetch files for fixity checking,
	// we stream them to /dev/null, because we're only interested in
	// computing a checksum.
	FetchLocalPath              string         `json:"fetch_local_path"`

	// FetchMd5Value is the md5 digest we computed on the file we pulled
	// down from S3. This is supposed to match the file's known md5 fixity
	// value.
	FetchMd5Value               string         `json:"fetch_md5_value"`

	// FetchSha256Value is the sha256 digest we computed on the file we
	// pulled down from S3. This should match the known sha256 digest.
	FetchSha256Value            string         `json:"fetch_sha256_value"`

	// FetchErrorMessage describes any error that occured during the
	// fetch process, including network errors, object not found, no disk
	// space, fixity mismatches, etc.
	FetchErrorMessage           string         `json:"fetch_error_message"`
}

func NewGenericFile() (*GenericFile) {
	return &GenericFile{
		Checksums: make([]*Checksum, 0),
		PremisEvents: make([]*PremisEvent, 0),
		IngestPreviousVersionExists: false,
		IngestNeedsSave: true,
	}
}

// Serializes a version of GenericFile that Fluctus will accept as post/put input.
// Note that we don't serialize the id or any of our internal housekeeping info.
func (gf *GenericFile) SerializeForPharos() ([]byte, error) {
	// We have to create a temporary structure to prevent json.Marshal
	// from serializing Size (int64) with scientific notation.
	// Without this step, Size will be serialized as something like
	// 2.706525e+06, which is not valid JSON.
	temp := struct{
		Identifier                   string         `json:"identifier"`
		IntellectualObjectId         int            `json:"intellectual_object_id"`
		IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`
		FileFormat                   string         `json:"file_format"`
		URI                          string         `json:"uri"`
		Size                         int64          `json:"size"`
		FileCreated                  time.Time      `json:"file_created"`
		FileModified                 time.Time      `json:"file_modified"`
		CreatedAt                    time.Time      `json:"created_at"`
		UpdatedAt                    time.Time      `json:"updated_at"`
		Checksums                    []*Checksum    `json:"checksums"`
	} {
		Identifier:                     gf.Identifier,
		IntellectualObjectId:           gf.IntellectualObjectId,
		IntellectualObjectIdentifier:   gf.IntellectualObjectIdentifier,
		FileFormat:                     gf.FileFormat,
		URI:                            gf.URI,
		Size:                           gf.Size,
		FileCreated:                    gf.FileCreated,
		FileModified:                   gf.FileModified,
		Checksums:                      gf.Checksums,
	}
	return json.Marshal(temp)
}

// Returns the original path of the file within the original bag.
// This is just the identifier minus the institution id and bag name.
// For example, if the identifier is "uc.edu/cin.675812/data/object.properties",
// this returns "data/object.properties"
func (gf *GenericFile) OriginalPath() (string) {
	return strings.Replace(gf.Identifier, gf.IntellectualObjectIdentifier + "/", "", 1)
}

// Returns the name of the institution that owns this file.
func (gf *GenericFile) InstitutionIdentifier() (string, error) {
	parts := strings.Split(gf.Identifier, "/")
	if len(parts) < 2 {
		return "", fmt.Errorf("GenericFile identifier '%s' is not valid", gf.Identifier)
	}
	return parts[0], nil
}

// Returns the checksum digest for the given algorithm for this file.
func (gf *GenericFile) GetChecksum(algorithm string) (*Checksum) {
	for _, cs := range gf.Checksums {
		if cs != nil && cs.Algorithm == algorithm {
			return cs
		}
	}
	return nil
}

// Returns events of the specified type
func (gf *GenericFile) FindEventsByType(eventType string) ([]PremisEvent) {
	events := make([]PremisEvent, 0)
	for _, event := range gf.PremisEvents {
		if event != nil && event.EventType == eventType {
			events = append(events, *event)
		}
	}
	return events
}

// Returns the name of this file in the preservation storage bucket
// (that should be a UUID), or an error if the GenericFile does not
// have a valid preservation storage URL.
func (gf *GenericFile) PreservationStorageFileName() (string, error) {
	if strings.Index(gf.URI, "/") < 0 {
		return "", fmt.Errorf("Cannot get preservation storage file name because GenericFile has an invalid URI")
	}
	parts := strings.Split(gf.URI, "/")
	return parts[len(parts) - 1], nil
}
<|endoftext|>


<commit_before>// +build windows

package serial

import (
	"fmt"
	"io"
	"os"
	"sync"
	"syscall"
	"unsafe"
)

const (
	SERIAL_FLAGS_CLEAR = 0

	FLAG_DTRCONTROL = 0
	FLAG_RTSCONTROL = 1
	OFFSET_DTRCONTROL = 4
	OFFSET_RTSCONTROL = 4

	// Imported from winbase.h
	RTS_CONTROL_DISABLE     = 0 /* 0b00 */ << OFFSET_RTSCONTROL
	RTS_CONTROL_ENABLE      = 1 /* 0b01 */ << OFFSET_RTSCONTROL
	RTS_CONTROL_HANDSHAKE   = 2 /* 0b10 */ << OFFSET_RTSCONTROL
	RTS_CONTROL_TOGGLE      = 3 /* 0b11 */ << OFFSET_RTSCONTROL
	DTR_CONTROL_DISABLE     = 0 /* 0b00 */ << OFFSET_DTRCONTROL
	DTR_CONTROL_ENABLE      = 1 /* 0b01 */ << OFFSET_DTRCONTROL
	DTR_CONTROL_HANDSHAKE   = 2 /* 0b10 */ << OFFSET_DTRCONTROL
)

type serialPort struct {
	f  *os.File
	fd syscall.Handle
	rl sync.Mutex
	wl sync.Mutex
	st *structTimeouts
	h syscall.Handle
	ro *syscall.Overlapped
	wo *syscall.Overlapped
}

type structDCB struct {
	DCBlength, BaudRate                            uint32
	flags                                          [4]byte
	wReserved, XonLim, XoffLim                     uint16
	ByteSize, Parity, StopBits                     byte
	XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
	wReserved1                                     uint16
}

type structTimeouts struct {
	ReadIntervalTimeout         uint32
	ReadTotalTimeoutMultiplier  uint32
	ReadTotalTimeoutConstant    uint32
	WriteTotalTimeoutMultiplier uint32
	WriteTotalTimeoutConstant   uint32
}

func openPort(name string, baud int, spec []byte, flow []bool) (rwc io.ReadWriteCloser, err error) {
	if len(name) > 0 && name[0] != '\\' {
		name = "\\\\.\\" + name
	}

	h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
		0,
		nil,
		syscall.OPEN_EXISTING,
		syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
		0)
	if err != nil {
		return nil, err
	}
	f := os.NewFile(uintptr(h), name)
	defer func() {
		if err != nil {
			f.Close()
		}
	}()

	// TODO: Sanity checking for these comm parameters
	byteSize := spec[0]
	stopBits := spec[1]
	parity := spec[2]

	if err = setCommState(h, baud, byteSize, stopBits, parity, flow); err != nil {
		return
	}
	if err = setupComm(h, 64, 64); err != nil {
		return
	}
	if err = setCommMask(h); err != nil {
		return
	}

	ro, err := newOverlapped()
	if err != nil {
		return
	}
	wo, err := newOverlapped()
	if err != nil {
		return
	}
	port := new(serialPort)
	port.f = f
	port.fd = h
	port.ro = ro
	port.wo = wo
	var timeouts structTimeouts
	port.st = &timeouts
	port.SetTimeouts(100)

	return port, nil
}

func (p *serialPort) Close() error {
	return p.f.Close()
}

func (p *serialPort) SetTimeouts(msec uint32){
	timeouts := p.st
	timeouts.ReadIntervalTimeout = msec/10
	timeouts.ReadTotalTimeoutMultiplier = msec
	timeouts.ReadTotalTimeoutConstant = msec

	/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx

		 For blocking I/O see below:

		 Remarks:

		 If an application sets ReadIntervalTimeout and
		 ReadTotalTimeoutMultiplier to MAXDWORD and sets
		 ReadTotalTimeoutConstant to a value greater than zero and
		 less than MAXDWORD, one of the following occurs when the
		 ReadFile function is called:

		 If there are any bytes in the input buffer, ReadFile returns
		       immediately with the bytes in the buffer.

		 If there are no bytes in the input buffer, ReadFile waits
	               until a byte arrives and then returns immediately.

		 If no bytes arrive within the time specified by
		       ReadTotalTimeoutConstant, ReadFile times out.
	*/

    p.st = timeouts
    setCommTimeouts(p.h, timeouts)
}

func (p *serialPort) Write(buf []byte) (int, error) {
	p.wl.Lock()
	defer p.wl.Unlock()

	if err := resetEvent(p.wo.HEvent); err != nil {
		return 0, err
	}
	var n uint32
	err := syscall.WriteFile(p.fd, buf, &n, p.wo)
	if err != nil && err != syscall.ERROR_IO_PENDING {
		return int(n), err
	}
	return getOverlappedResult(p.fd, p.wo)
}

func (p *serialPort) Read(buf []byte) (int, error) {
	if p == nil || p.f == nil {
		return 0, fmt.Errorf("Invalid port on read %v %v", p, p.f)
	}

	p.rl.Lock()
	defer p.rl.Unlock()

	if err := resetEvent(p.ro.HEvent); err != nil {
		return 0, err
	}
	var done uint32
	err := syscall.ReadFile(p.fd, buf, &done, p.ro)
	if err != nil && err != syscall.ERROR_IO_PENDING {
		return int(done), err
	}
	return getOverlappedResult(p.fd, p.ro)
}

var (
	nSetCommState,
	nSetCommTimeouts,
	nSetCommMask,
	nSetupComm,
	nGetOverlappedResult,
	nCreateEvent,
	nResetEvent uintptr
)

func init() {
	k32, err := syscall.LoadLibrary("kernel32.dll")
	if err != nil {
		panic("LoadLibrary " + err.Error())
	}
	defer syscall.FreeLibrary(k32)

	nSetCommState = getProcAddr(k32, "SetCommState")
	nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
	nSetCommMask = getProcAddr(k32, "SetCommMask")
	nSetupComm = getProcAddr(k32, "SetupComm")
	nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
	nCreateEvent = getProcAddr(k32, "CreateEventW")
	nResetEvent = getProcAddr(k32, "ResetEvent")
}

func getProcAddr(lib syscall.Handle, name string) uintptr {
	addr, err := syscall.GetProcAddress(lib, name)
	if err != nil {
		panic(name + " " + err.Error())
	}
	return addr
}

func setCommState(h syscall.Handle, baud int, byteSize, stopBits, parity byte, flow []bool) error {
	var params structDCB
	params.DCBlength = uint32(unsafe.Sizeof(params))

	params.flags[0] = SERIAL_FLAGS_CLEAR
	params.flags[0] |= 1  // fBinary (0b01)

        if flow[DTR_FLAG] {
		params.flags[FLAG_DTRCONTROL] |= DTR_CONTROL_ENABLE // Assert DSR
        } else {
		params.flags[FLAG_DTRCONTROL] |= DTR_CONTROL_HANDSHAKE // Assert DSR
	}

        if flow[RTS_FLAG] {
		params.flags[FLAG_RTSCONTROL] |= RTS_CONTROL_ENABLE // Assert RTS/CTS
        } else {
		params.flags[FLAG_RTSCONTROL] |= RTS_CONTROL_HANDSHAKE // Assert RTS/CTS
	}

	params.BaudRate = uint32(baud)
	params.ByteSize = byteSize
	params.Parity = parity
	params.StopBits = stopBits

	r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(&params)), 0)
	if r == 0 {
		return err
	}
	return nil
}

func setCommTimeouts(h syscall.Handle, timeouts *structTimeouts) error {
	r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(timeouts)), 0)
	if r == 0 {
		return err
	}
	return nil
}

func setupComm(h syscall.Handle, in, out int) error {
	r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
	if r == 0 {
		return err
	}
	return nil
}

func setCommMask(h syscall.Handle) error {
	const EV_RXCHAR = 1 /* 0b0001 */
	r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
	if r == 0 {
		return err
	}
	return nil
}

func resetEvent(h syscall.Handle) error {
	r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
	if r == 0 {
		return err
	}
	return nil
}

func newOverlapped() (*syscall.Overlapped, error) {
	var overlapped syscall.Overlapped
	r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
	if r == 0 {
		return nil, err
	}
	overlapped.HEvent = syscall.Handle(r)
	return &overlapped, nil
}

func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
	var n int
	r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
		uintptr(h),
		uintptr(unsafe.Pointer(overlapped)),
		uintptr(unsafe.Pointer(&n)), 1, 0, 0)
	if r == 0 {
		return n, err
	}

	return n, nil
}
<commit_msg>Add debug logging to windows.<commit_after>// +build windows

package serial

import (
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"syscall"
	"unsafe"
)

const (
	SERIAL_FLAGS_CLEAR = 0

	FLAG_DTRCONTROL = 0
	FLAG_RTSCONTROL = 1
	OFFSET_DTRCONTROL = 4
	OFFSET_RTSCONTROL = 4

	// Imported from winbase.h
	RTS_CONTROL_DISABLE     = 0 /* 0b00 */ << OFFSET_RTSCONTROL
	RTS_CONTROL_ENABLE      = 1 /* 0b01 */ << OFFSET_RTSCONTROL
	RTS_CONTROL_HANDSHAKE   = 2 /* 0b10 */ << OFFSET_RTSCONTROL
	RTS_CONTROL_TOGGLE      = 3 /* 0b11 */ << OFFSET_RTSCONTROL
	DTR_CONTROL_DISABLE     = 0 /* 0b00 */ << OFFSET_DTRCONTROL
	DTR_CONTROL_ENABLE      = 1 /* 0b01 */ << OFFSET_DTRCONTROL
	DTR_CONTROL_HANDSHAKE   = 2 /* 0b10 */ << OFFSET_DTRCONTROL
)

type serialPort struct {
	f  *os.File
	fd syscall.Handle
	rl sync.Mutex
	wl sync.Mutex
	st *structTimeouts
	h syscall.Handle
	ro *syscall.Overlapped
	wo *syscall.Overlapped
}

type structDCB struct {
	DCBlength, BaudRate                            uint32
	flags                                          [4]byte
	wReserved, XonLim, XoffLim                     uint16
	ByteSize, Parity, StopBits                     byte
	XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
	wReserved1                                     uint16
}

type structTimeouts struct {
	ReadIntervalTimeout         uint32
	ReadTotalTimeoutMultiplier  uint32
	ReadTotalTimeoutConstant    uint32
	WriteTotalTimeoutMultiplier uint32
	WriteTotalTimeoutConstant   uint32
}

func openPort(name string, baud int, spec []byte, flow []bool) (rwc io.ReadWriteCloser, err error) {
	if len(name) > 0 && name[0] != '\\' {
		name = "\\\\.\\" + name
	}

	h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
		0,
		nil,
		syscall.OPEN_EXISTING,
		syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
		0)
	if err != nil {
		return nil, err
	}
	f := os.NewFile(uintptr(h), name)
	defer func() {
		if err != nil {
			f.Close()
		}
	}()

	// TODO: Sanity checking for these comm parameters
	byteSize := spec[0]
	stopBits := spec[1]
	parity := spec[2]
	log.Printf("byteSize = %s, stopBits = %s, parity = %s", string(byteSize), string(stopBits), string(parity))

	if err = setCommState(h, baud, byteSize, stopBits, parity, flow); err != nil {
		log.Print("Failed to setCommState")
		return
	}
	if err = setupComm(h, 64, 64); err != nil {
		log.Print("Failed to setupComm")
		return
	}
	if err = setCommMask(h); err != nil {
		log.Print("Failed to setCommMask")
		return
	}

	ro, err := newOverlapped()
	if err != nil {
		log.Print("Failed to set ro with newOverlapped")
		return
	}
	wo, err := newOverlapped()
	if err != nil {
		log.Print("Failed to set wo with newOverlapped")
		return
	}
	port := new(serialPort)
	port.f = f
	port.fd = h
	port.ro = ro
	port.wo = wo
	var timeouts structTimeouts
	port.st = &timeouts
	port.SetTimeouts(100)

	return port, nil
}

func (p *serialPort) Close() error {
	return p.f.Close()
}

func (p *serialPort) SetTimeouts(msec uint32){
	timeouts := p.st
	timeouts.ReadIntervalTimeout = msec/10
	timeouts.ReadTotalTimeoutMultiplier = msec
	timeouts.ReadTotalTimeoutConstant = msec

	/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx

		 For blocking I/O see below:

		 Remarks:

		 If an application sets ReadIntervalTimeout and
		 ReadTotalTimeoutMultiplier to MAXDWORD and sets
		 ReadTotalTimeoutConstant to a value greater than zero and
		 less than MAXDWORD, one of the following occurs when the
		 ReadFile function is called:

		 If there are any bytes in the input buffer, ReadFile returns
		       immediately with the bytes in the buffer.

		 If there are no bytes in the input buffer, ReadFile waits
	               until a byte arrives and then returns immediately.

		 If no bytes arrive within the time specified by
		       ReadTotalTimeoutConstant, ReadFile times out.
	*/

    p.st = timeouts
    setCommTimeouts(p.h, timeouts)
}

func (p *serialPort) Write(buf []byte) (int, error) {
	p.wl.Lock()
	defer p.wl.Unlock()

	if err := resetEvent(p.wo.HEvent); err != nil {
		return 0, err
	}
	var n uint32
	err := syscall.WriteFile(p.fd, buf, &n, p.wo)
	if err != nil && err != syscall.ERROR_IO_PENDING {
		return int(n), err
	}
	return getOverlappedResult(p.fd, p.wo)
}

func (p *serialPort) Read(buf []byte) (int, error) {
	if p == nil || p.f == nil {
		return 0, fmt.Errorf("Invalid port on read %v %v", p, p.f)
	}

	p.rl.Lock()
	defer p.rl.Unlock()

	if err := resetEvent(p.ro.HEvent); err != nil {
		return 0, err
	}
	var done uint32
	err := syscall.ReadFile(p.fd, buf, &done, p.ro)
	if err != nil && err != syscall.ERROR_IO_PENDING {
		return int(done), err
	}
	return getOverlappedResult(p.fd, p.ro)
}

var (
	nSetCommState,
	nSetCommTimeouts,
	nSetCommMask,
	nSetupComm,
	nGetOverlappedResult,
	nCreateEvent,
	nResetEvent uintptr
)

func init() {
	k32, err := syscall.LoadLibrary("kernel32.dll")
	if err != nil {
		panic("LoadLibrary " + err.Error())
	}
	defer syscall.FreeLibrary(k32)

	nSetCommState = getProcAddr(k32, "SetCommState")
	nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
	nSetCommMask = getProcAddr(k32, "SetCommMask")
	nSetupComm = getProcAddr(k32, "SetupComm")
	nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
	nCreateEvent = getProcAddr(k32, "CreateEventW")
	nResetEvent = getProcAddr(k32, "ResetEvent")
}

func getProcAddr(lib syscall.Handle, name string) uintptr {
	addr, err := syscall.GetProcAddress(lib, name)
	if err != nil {
		panic(name + " " + err.Error())
	}
	return addr
}

func setCommState(h syscall.Handle, baud int, byteSize, stopBits, parity byte, flow []bool) error {
	var params structDCB
	params.DCBlength = uint32(unsafe.Sizeof(params))

	params.flags[0] = SERIAL_FLAGS_CLEAR
	params.flags[0] |= 1  // fBinary (0b01)

        if flow[DTR_FLAG] {
		params.flags[FLAG_DTRCONTROL] |= DTR_CONTROL_ENABLE // Assert DSR
        } else {
		params.flags[FLAG_DTRCONTROL] |= DTR_CONTROL_HANDSHAKE // Assert DSR
	}

        if flow[RTS_FLAG] {
		params.flags[FLAG_RTSCONTROL] |= RTS_CONTROL_ENABLE // Assert RTS/CTS
        } else {
		params.flags[FLAG_RTSCONTROL] |= RTS_CONTROL_HANDSHAKE // Assert RTS/CTS
	}

	params.BaudRate = uint32(baud)
	params.ByteSize = byteSize
	params.Parity = parity
	params.StopBits = stopBits

	r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(&params)), 0)
	if r == 0 {
		return err
	}
	return nil
}

func setCommTimeouts(h syscall.Handle, timeouts *structTimeouts) error {
	r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(timeouts)), 0)
	if r == 0 {
		return err
	}
	return nil
}

func setupComm(h syscall.Handle, in, out int) error {
	r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
	if r == 0 {
		return err
	}
	return nil
}

func setCommMask(h syscall.Handle) error {
	const EV_RXCHAR = 1 /* 0b0001 */
	r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
	if r == 0 {
		return err
	}
	return nil
}

func resetEvent(h syscall.Handle) error {
	r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
	if r == 0 {
		return err
	}
	return nil
}

func newOverlapped() (*syscall.Overlapped, error) {
	var overlapped syscall.Overlapped
	r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
	if r == 0 {
		return nil, err
	}
	overlapped.HEvent = syscall.Handle(r)
	return &overlapped, nil
}

func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
	var n int
	r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
		uintptr(h),
		uintptr(unsafe.Pointer(overlapped)),
		uintptr(unsafe.Pointer(&n)), 1, 0, 0)
	if r == 0 {
		return n, err
	}

	return n, nil
}
<|endoftext|>


<commit_before>package db

import "github.com/go-redis/redis"
import "fmt"
import "errors"

var client *redis.Client

//ConnectRedis ...
func ConnectRedis(addr string, pwd string) error {
	options := &redis.Options{}
	options.Addr = addr
	options.Password = pwd
	client = redis.NewClient(options)

	pong, err := client.Ping().Result()
	fmt.Println(pong, err)
	return err
}

//QueryWithRedis ...
func QueryWithRedis(s func(*redis.Client) error) error {
	if client != nil {
		return s(client)
	}
	return errors.New("redis not connected")
}
<commit_msg>feat(redis):add redis key rw lock<commit_after>package db

import (
	"errors"
	"fmt"
	"strconv"
	"time"

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

var client *redis.Client

//ConnectRedis ...
func ConnectRedis(addr string, pwd string) error {
	options := &redis.Options{}
	options.Addr = addr
	options.Password = pwd
	client = redis.NewClient(options)

	pong, err := client.Ping().Result()
	fmt.Println(pong, err)
	return err
}

//QueryWithRedis ...
func QueryWithRedis(s func(*redis.Client) error) error {
	if client != nil {
		return s(client)
	}
	return errors.New("redis not connected")
}

//KeyLock ...
func KeyLock(key string) {
	keylock := key + ":lock"
	redisQuery := func(r *redis.Client) error {
		err := r.SetNX(keylock, strconv.FormatInt(time.Now().Unix(), 10), 3*time.Second).Err()
		return err
	}
	err := QueryWithRedis(redisQuery)
	for err != nil {
		time.Sleep(10 * time.Millisecond)
		err = QueryWithRedis(redisQuery)
	}
}

//KeyUnLock ...
func KeyUnLock(key string) {
	keylock := key + ":lock"
	redisQuery := func(r *redis.Client) error {
		err := r.Del(keylock).Err()
		return err
	}
	QueryWithRedis(redisQuery)
}
<|endoftext|>


<commit_before>package VK

import (
	"encoding/json"
	"github.com/gorilla/feeds"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
)

type VK struct{}

type VKItem struct {
	Id        int
	From_id   int
	Owner_id  int
	Date      int
	Post_type string
	Text      string
}

type VKProfile struct {
	Id          int
	First_name  string
	Last_name   string
	Screen_name string
	Photo_200   string
}

type VKGroup struct {
	Id          int
	Name        string
	Screen_name string
	Is_closed   int
	Type        string
	Photo_200   string
}

type VKResponseBody struct {
	Count    int
	Items    []VKItem
	Profiles []VKProfile
	Groups   []VKGroup
}

type VKResponse struct {
	Response VKResponseBody
}

func GetPosts(feedId string) (string, error) {
	var requestUrl string = "https://api.vk.com/method/wall.get?v=5.12&extended=1&owner_id=" + feedId

	resp, err := http.Get(requestUrl)

	if err != nil {
		return "", err
	}

	defer resp.Body.Close()

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

	var encoded VKResponse

	err = json.Unmarshal(body, &encoded)

	if err != nil {
		return "", err
	}
	var name, screenName string

	if len(encoded.Response.Groups) > 0 {
		sourceInfo := encoded.Response.Groups[0]
		name = sourceInfo.Name
		screenName = sourceInfo.Screen_name
	} else {
		sourceInfo := encoded.Response.Profiles[0]
		name = sourceInfo.First_name + " " + sourceInfo.Last_name
		screenName = sourceInfo.Screen_name
	}

	feed := &feeds.Feed{
		Title: name,
		Link:  &feeds.Link{Href: "https://vk.com/" + screenName},
	}

	for _, elem := range encoded.Response.Items {
		feed.Add(&feeds.Item{
			Title:       strings.Split(elem.Text, ".")[0] + "...",
			Link:        &feeds.Link{Href: "http://vk.com/wall" + strconv.Itoa(elem.Owner_id) + "_" + strconv.Itoa(elem.Id)},
			Description: elem.Text,
		})
	}

	return feed.ToRss()
}
<commit_msg>check if group feed and render proper source info<commit_after>package VK

import (
	"encoding/json"
	"github.com/gorilla/feeds"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
)

type VK struct{}

type VKItem struct {
	Id        int
	From_id   int
	Owner_id  int
	Date      int
	Post_type string
	Text      string
}

type VKProfile struct {
	Id          int
	First_name  string
	Last_name   string
	Screen_name string
	Photo_200   string
}

type VKGroup struct {
	Id          int
	Name        string
	Screen_name string
	Is_closed   int
	Type        string
	Photo_200   string
}

type VKResponseBody struct {
	Count    int
	Items    []VKItem
	Profiles []VKProfile
	Groups   []VKGroup
}

type VKResponse struct {
	Response VKResponseBody
}

func GetPosts(feedId string) (string, error) {
	var requestUrl string = "https://api.vk.com/method/wall.get?v=5.12&extended=1&owner_id=" + feedId
	var isGroup bool = strings.Contains(feedId, "-")

	resp, err := http.Get(requestUrl)

	if err != nil {
		return "", err
	}

	defer resp.Body.Close()

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

	var encoded VKResponse

	err = json.Unmarshal(body, &encoded)

	if err != nil {
		return "", err
	}
	var name, screenName string

	if isGroup {
		sourceInfo := encoded.Response.Groups[0]
		name = sourceInfo.Name
		screenName = sourceInfo.Screen_name
	} else {
		sourceInfo := encoded.Response.Profiles[0]
		name = sourceInfo.First_name + " " + sourceInfo.Last_name
		screenName = sourceInfo.Screen_name
	}

	feed := &feeds.Feed{
		Title: name,
		Link:  &feeds.Link{Href: "https://vk.com/" + screenName},
	}

	for _, elem := range encoded.Response.Items {
		feed.Add(&feeds.Item{
			Title:       strings.Split(elem.Text, ".")[0] + "...",
			Link:        &feeds.Link{Href: "http://vk.com/wall" + strconv.Itoa(elem.Owner_id) + "_" + strconv.Itoa(elem.Id)},
			Description: elem.Text,
		})
	}

	return feed.ToRss()
}
<|endoftext|>


<commit_before>package engine

import (
    "cgl.tideland.biz/applog"
    "time"
    "vector"
)

var (
    PLAYER_UPDATE_TIMER, _ = time.ParseDuration("50ms")
)

// TODO: Make this a non-global variable
var world = MakeWorld(10, 10)

type WorldUpdateFunc func (*World)

type World struct {
    players map[string] *Player
    worldMap *WorldMap
    currentId uint

    register chan *connection
    unregister chan *Client
    update chan WorldUpdateFunc
}

func MakeWorld(width, height uint) *World {
    world := &World {
        make(map [string] *Player),
        MakeWorldMap(width, height),
        0,
        make(chan *connection),
        make(chan *Client),
        make(chan WorldUpdateFunc),
    }
    gm := MakeGameChunk(width, height, vector.Vector2{0, 0})
    world.worldMap.SetGameChunk(vector.Vector2{0, 0}, gm)
    return world
}

func (w *World) StartPlayerLogin(name, token string) bool {
    p, found := w.GetPlayer(name)
    if found {
        p.Login(token)
        return true
    } else {
        return false
    }
}

func (w *World) GetPlayer(name string) (*Player, bool) {
    p := w.players[name]
    if p != nil {
        return p, true
    } else {
        return nil, false
    }
}

func (w *World) NewPlayer(name string, token string) *Player {
    path := "players/" + name
    if res, _ := fileSystem.FileExists(path); res {
        p, err := fileSystem.LoadPlayer(path)
        if err != nil {
            applog.Errorf("Could not load player at path: %s. Error: %s",
                          path, err)
            return nil
        }
        w.players[p.Name] = p
        return p
    } else {
        applog.Debugf("Creating a new player")
        p := MakePlayer(w.getNextId(), name, vector.Vector2{0, 0})
        w.players[p.Name] = p
        return p
    }
    return nil
}

func (w *World) Start() {
    go w.HandleConnections()
    go w.UpdateLoop()
    go w.HandleShutdown()
}

func (w *World) SendPlayerInitialInfo(p *Player) {
    p.write(MakeMapPacket(w.worldMap.grid))
    p.write(MakePlayerPacket(p))
    RepeatingTimer(PLAYER_UPDATE_TIMER, func () bool {
        p.write(MakeNearbyPlayerUpdatePacket(w))
        return p.IsOnline()
    })
}

func (w *World) HandleConnections() {
    for conn := range w.register {
        applog.Debugf("Received request to register new player")
        client := MakeClient()
        go conn.writePump(client)
        go conn.readPump(client)
        go client.read()
    }
}

func (w *World) HandleShutdown() {
    for client := range w.unregister {
        applog.Debugf("Client %s logging out", client)
        if client.player == nil {
            continue
        }
        path := "players/" + client.player.Name
        err := fileSystem.Save(path, client.player)
        if err != nil {
            applog.Errorf("Failed to save player at path: %s. Error: %s",
                          path, err)
        }
    }
}

func (w *World) Broadcast(payload GameWriter) {
    for _, player := range w.players {
        player.write(payload)
    }
}

func (w *World) UpdateLoop() {
    for updateFunction := range w.update {
        updateFunction(w)
    }
}

func (w *World) getNextId() uint {
    id := w.currentId
    w.currentId++
    return id
}
<commit_msg>Fix bug where duplicate IDs can occur when loading from disk<commit_after>package engine

import (
    "cgl.tideland.biz/applog"
    "time"
    "vector"
)

var (
    PLAYER_UPDATE_TIMER, _ = time.ParseDuration("50ms")
)

// TODO: Make this a non-global variable
var world = MakeWorld(10, 10)

type WorldUpdateFunc func (*World)

type World struct {
    players map[string] *Player
    worldMap *WorldMap
    currentId uint

    register chan *connection
    unregister chan *Client
    update chan WorldUpdateFunc
}

func MakeWorld(width, height uint) *World {
    world := &World {
        make(map [string] *Player),
        MakeWorldMap(width, height),
        0,
        make(chan *connection),
        make(chan *Client),
        make(chan WorldUpdateFunc),
    }
    gm := MakeGameChunk(width, height, vector.Vector2{0, 0})
    world.worldMap.SetGameChunk(vector.Vector2{0, 0}, gm)
    return world
}

func (w *World) StartPlayerLogin(name, token string) bool {
    p, found := w.GetPlayer(name)
    if found {
        p.Login(token)
        return true
    } else {
        return false
    }
}

func (w *World) GetPlayer(name string) (*Player, bool) {
    p := w.players[name]
    if p != nil {
        return p, true
    } else {
        return nil, false
    }
}

func (w *World) NewPlayer(name string, token string) *Player {
    path := "players/" + name
    if res, _ := fileSystem.FileExists(path); res {
        p, err := fileSystem.LoadPlayer(path)
        if err != nil {
            applog.Errorf("Could not load player at path: %s. Error: %s",
                          path, err)
            return nil
        }
        w.players[p.Name] = p
        p.Id = w.getNextId()
        return p
    } else {
        applog.Debugf("Creating a new player")
        p := MakePlayer(w.getNextId(), name, vector.Vector2{0, 0})
        w.players[p.Name] = p
        return p
    }
    return nil
}

func (w *World) Start() {
    go w.HandleConnections()
    go w.UpdateLoop()
    go w.HandleShutdown()
}

func (w *World) SendPlayerInitialInfo(p *Player) {
    p.write(MakeMapPacket(w.worldMap.grid))
    p.write(MakePlayerPacket(p))
    RepeatingTimer(PLAYER_UPDATE_TIMER, func () bool {
        p.write(MakeNearbyPlayerUpdatePacket(w))
        return p.IsOnline()
    })
}

func (w *World) HandleConnections() {
    for conn := range w.register {
        applog.Debugf("Received request to register new player")
        client := MakeClient()
        go conn.writePump(client)
        go conn.readPump(client)
        go client.read()
    }
}

func (w *World) HandleShutdown() {
    for client := range w.unregister {
        applog.Debugf("Client %s logging out", client)
        if client.player == nil {
            continue
        }
        path := "players/" + client.player.Name
        err := fileSystem.Save(path, client.player)
        if err != nil {
            applog.Errorf("Failed to save player at path: %s. Error: %s",
                          path, err)
        }
    }
}

func (w *World) Broadcast(payload GameWriter) {
    for _, player := range w.players {
        player.write(payload)
    }
}

func (w *World) UpdateLoop() {
    for updateFunction := range w.update {
        updateFunction(w)
    }
}

func (w *World) getNextId() uint {
    id := w.currentId
    w.currentId++
    return id
}
<|endoftext|>


<commit_before>package rbt

import (
	"fmt"
	"math/rand"
	"testing"

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

func less(x1, x2 interface{}) bool {
	i1, i2 := x1.(int), x2.(int)
	return i1 < i2
}

func TestNode(t *testing.T) {
	k := 3

	n := (*node)(nil)
	t.Log(n)
	n = n.insert(k, less)
	t.Log(n)

	kk, ok := n.search(k, less)
	assert.True(t, ok)
	assert.Equal(t, kk, k)
}

func TestNodeBalance(t *testing.T) {
	ks := []int{1, 2, 3, 4, 5, 6, 7, 8}
	n := (*node)(nil)

	for _, k := range ks {
		n.dump()
		n = n.insert(k, less)
	}

	n.dump()
}

func TestNodeTakeMax(t *testing.T) {
	ks := []int{1, 2, 3, 4, 5, 6, 7, 8}
	n := (*node)(nil)

	for _, k1 := range ks {
		n = n.insert(k1, less)
		n.dump()

		k2, m, _ := n.takeMax()
		assert.Equal(t, k1, k2.(int))
		m.dump()
	}
}

func TestNodeRemove(t *testing.T) {
	ks := []int{1, 2, 3, 4, 5, 6, 7, 8}
	n := (*node)(nil)

	for _, k := range ks {
		n = n.insert(k, less)
	}

	n.dump()

	for _, k := range ks {
		n = n.remove(k, less)
		n.dump()
	}
}

const (
	MaxIters = 2000
	MaxKey   = MaxIters / 2
)

func generateKey() int {
	return rand.Int() % MaxKey
}

func TestNodeInsertRandomly(t *testing.T) {
	n := (*node)(nil)

	for i := 0; i < MaxIters; i++ {
		k := generateKey()
		old := n

		n = n.insert(k, less)

		n.rank() // check ranks

		if !n.checkColors() {
			failWithDump(t, true, k, old, n)
		}
	}
}

func TestNodeRemoveRandomly(t *testing.T) {
	n := (*node)(nil)

	for i := 0; i < MaxIters; i++ {
		k := generateKey()
		old := n

		var insert bool
		n, insert = n.insertOrRemove(t, k)

		n.rank() // check ranks

		if !n.checkColors() {
			failWithDump(t, insert, k, old, n)
		}
	}
}

func TestInsertRemovePersistency(t *testing.T) {
	n := (*node)(nil)

	for i := 0; i < MaxIters; i++ {
		k := generateKey()
		old := n
		oldCopy := n.deepCopy()

		var insert bool
		n, insert = n.insertOrRemove(t, k)

		n.rank() // check ranks

		if !(old.equal(oldCopy) && n.checkColors()) {
			failWithDump(t, insert, k, old, n)
		}
	}
}

func (n *node) insertOrRemove(t *testing.T, x interface{}) (*node, bool) {
	insert := rand.Int()%2 == 0

	if insert {
		n = n.insert(x, less)
	} else {
		n = n.remove(x, less)
	}

	_, ok := n.search(x, less)
	assert.True(t, insert && ok || !insert && !ok)

	return n, insert
}

func failWithDump(t *testing.T, insert bool, k int, old, new *node) {
	if insert {
		fmt.Println("INSERT")
	} else {
		fmt.Println("REMOVE")
	}

	fmt.Println("KEY:", k)
	fmt.Println("OLD:")
	old.dump()
	fmt.Println("NEW:")
	new.dump()
	t.FailNow()
}

func TestNodeEqual(t *testing.T) {
	n0 := (*node)(nil)
	n1 := n0.insert(1, less)
	n2 := n0.insert(2, less)
	n3 := n1.insert(2, less)
	n4 := n3.insert(3, less)

	for _, test := range []struct {
		n1, n2 *node
		equal  bool
	}{
		{n0, n0, true},
		{n1, n1, true},
		{n2, n2, true},
		{n3, n3, true},
		{n4, n4, true},
		{n0, n1, false},
		{n0, n2, false},
		{n0, n3, false},
		{n0, n4, false},
		{n1, n2, false},
		{n1, n3, false},
		{n2, n3, false},
		{n2, n4, false},
		{n3, n4, false},
	} {
		assert.Equal(t, test.equal, test.n1.equal(test.n2))
	}
}
<commit_msg>Add node dump logs<commit_after>package rbt

import (
	"fmt"
	"math/rand"
	"testing"

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

func less(x1, x2 interface{}) bool {
	i1, i2 := x1.(int), x2.(int)
	return i1 < i2
}

func TestNode(t *testing.T) {
	k := 3

	n := (*node)(nil)
	t.Log(n)
	n = n.insert(k, less)
	t.Log(n)

	kk, ok := n.search(k, less)
	assert.True(t, ok)
	assert.Equal(t, kk, k)
}

func TestNodeBalance(t *testing.T) {
	ks := []int{1, 2, 3, 4, 5, 6, 7, 8}
	n := (*node)(nil)

	for _, k := range ks {
		n.dump()
		n = n.insert(k, less)
	}

	n.dump()
}

func TestNodeTakeMax(t *testing.T) {
	ks := []int{1, 2, 3, 4, 5, 6, 7, 8}
	n := (*node)(nil)

	for _, k1 := range ks {
		n = n.insert(k1, less)
		n.dump()

		k2, m, _ := n.takeMax()
		assert.Equal(t, k1, k2.(int))
		m.dump()
	}
}

func TestNodeRemove(t *testing.T) {
	ks := []int{1, 2, 3, 4, 5, 6, 7, 8}
	n := (*node)(nil)

	for _, k := range ks {
		n = n.insert(k, less)
	}

	n.dump()

	for _, k := range ks {
		n = n.remove(k, less)
		n.dump()
	}
}

const (
	MaxIters = 2000
	MaxKey   = MaxIters / 2
)

func generateKey() int {
	return rand.Int() % MaxKey
}

func TestNodeInsertRandomly(t *testing.T) {
	n := (*node)(nil)

	for i := 0; i < MaxIters; i++ {
		k := generateKey()
		old := n

		n = n.insert(k, less)

		n.rank() // check ranks

		if !n.checkColors() {
			failWithDump(t, true, k, old, n)
		}
	}
}

func TestNodeRemoveRandomly(t *testing.T) {
	n := (*node)(nil)

	for i := 0; i < MaxIters; i++ {
		k := generateKey()
		old := n

		var insert bool
		n, insert = n.insertOrRemove(t, k)

		n.dump()
		n.rank() // check ranks

		if !n.checkColors() {
			failWithDump(t, insert, k, old, n)
		}
	}
}

func TestInsertRemovePersistency(t *testing.T) {
	n := (*node)(nil)

	for i := 0; i < MaxIters; i++ {
		k := generateKey()
		old := n
		oldCopy := n.deepCopy()

		var insert bool
		n, insert = n.insertOrRemove(t, k)

		n.dump()
		n.rank() // check ranks

		if !(old.equal(oldCopy) && n.checkColors()) {
			failWithDump(t, insert, k, old, n)
		}
	}
}

func (n *node) insertOrRemove(t *testing.T, x interface{}) (*node, bool) {
	insert := rand.Int()%2 == 0

	if insert {
		n = n.insert(x, less)
	} else {
		n = n.remove(x, less)
	}

	_, ok := n.search(x, less)
	assert.True(t, insert && ok || !insert && !ok)

	return n, insert
}

func failWithDump(t *testing.T, insert bool, k int, old, new *node) {
	if insert {
		fmt.Println("INSERT")
	} else {
		fmt.Println("REMOVE")
	}

	fmt.Println("KEY:", k)
	fmt.Println("OLD:")
	old.dump()
	fmt.Println("NEW:")
	new.dump()
	t.FailNow()
}

func TestNodeEqual(t *testing.T) {
	n0 := (*node)(nil)
	n1 := n0.insert(1, less)
	n2 := n0.insert(2, less)
	n3 := n1.insert(2, less)
	n4 := n3.insert(3, less)

	for _, test := range []struct {
		n1, n2 *node
		equal  bool
	}{
		{n0, n0, true},
		{n1, n1, true},
		{n2, n2, true},
		{n3, n3, true},
		{n4, n4, true},
		{n0, n1, false},
		{n0, n2, false},
		{n0, n3, false},
		{n0, n4, false},
		{n1, n2, false},
		{n1, n3, false},
		{n2, n3, false},
		{n2, n4, false},
		{n3, n4, false},
	} {
		assert.Equal(t, test.equal, test.n1.equal(test.n2))
	}
}
<|endoftext|>


<commit_before>package gelf

import (
	"compress/flate"
	"log"
	"os"
	"strings"
	"time"

	"github.com/Graylog2/go-gelf/gelf"
	"github.com/gliderlabs/logspout/router"
)

var hostname string

func init() {
	hostname, _ = os.Hostname()
	router.AdapterFactories.Register(New, "gelf")
}

// Adapter is an adapter that streams UDP JSON to Graylog
type Adapter struct {
	route *router.Route
}

// New creates a GelfAdapter with UDP as the default transport.
func New(route *router.Route) (router.LogAdapter, error) {
	// test the address, but don't use this writer
	writer, err := gelf.NewWriter(route.Address)
	if err != nil {
		return nil, err
	}
	writer.Close()

	return &Adapter{
		route: route,
	}, nil
}

func streamSome(writer *gelf.Writer, logstream chan *router.Message, cancel <-chan time.Time) {
	for {
		select {
		case msg := <-logstream:
			if err := writer.WriteMessage(&gelf.Message{
				Version:  "1.1",
				Host:     hostname, // Running as a container cannot discover the Docker Hostname
				Short:    msg.Data,
				TimeUnix: float64(msg.Time.UnixNano()/int64(time.Millisecond)) / 1000.0,
				Level:    gelf.LOG_INFO, // TODO: be smarter about this? stdout vs. stderr are not log levels
				Extra: map[string]interface{}{
					"_container_id":   msg.Container.ID,
					"_container_name": msg.Container.Name,
					"_image_id":       msg.Container.Image,
					"_image_name":     msg.Container.Config.Image,
					"_command":        strings.Join(msg.Container.Config.Cmd, " "),
					"_created":        msg.Container.Created,
				},
			}); err != nil {
				log.Printf("error sending GELF message: %s", err)
			}

		case <-cancel:
			return
		}
	}
}

func dropSome(logstream chan *router.Message, cancel <-chan time.Time) {
	for {
		select {
		case msg := <-logstream:
			log.Printf("dropping log message: %s", msg.Data)
		case <-cancel:
			return
		}
	}
}

// Stream implements the router.LogAdapter interface.
func (a *Adapter) Stream(logstream chan *router.Message) {
	for {
		writer, err := gelf.NewWriter(a.route.Address)
		if err != nil {
			log.Printf("error dialing %s: %s", a.route.Address, err)

			// Try redial after 5 seconds
			dropSome(logstream, time.After(5*time.Second))
		} else {
			log.Printf("dialed %s", a.route.Address)
		// intended to forward logs to a service on localhost
		writer.CompressionLevel = flate.NoCompression

			// Redial after 1 minute. Maybe DNS changed?
			streamSome(writer, logstream, time.After(1*time.Minute))
		}
	}
}
<commit_msg>Refactor error cases.<commit_after>package gelf

import (
	"compress/flate"
	"log"
	"os"
	"strings"
	"time"

	"github.com/Graylog2/go-gelf/gelf"
	"github.com/gliderlabs/logspout/router"
)

var hostname string

func init() {
	hostname, _ = os.Hostname()
	router.AdapterFactories.Register(New, "gelf")
}

// Adapter is an adapter that streams UDP JSON to Graylog
type Adapter struct {
	address string
}

// New creates a GelfAdapter with UDP as the default transport.
func New(route *router.Route) (router.LogAdapter, error) {
	// test the address, but don't use this writer
	writer, err := gelf.NewWriter(route.Address)
	if err != nil {
		return nil, err
	}
	writer.Close()

	return &Adapter{address: route.Address}, nil
}

func streamSome(writer *gelf.Writer, logstream chan *router.Message) error {
	for msg := range logstream {
		if err := writer.WriteMessage(&gelf.Message{
			Version:  "1.1",
			Host:     hostname, // Running as a container cannot discover the Docker Hostname
			Short:    msg.Data,
			TimeUnix: float64(msg.Time.UnixNano()/int64(time.Millisecond)) / 1000.0,
			Level:    gelf.LOG_INFO, // TODO: be smarter about this? stdout vs. stderr are not log levels
			Extra: map[string]interface{}{
				"_container_id":   msg.Container.ID,
				"_container_name": msg.Container.Name,
				"_image_id":       msg.Container.Image,
				"_image_name":     msg.Container.Config.Image,
				"_command":        strings.Join(msg.Container.Config.Cmd, " "),
				"_created":        msg.Container.Created,
			},
		}); err != nil {
			log.Printf("gelf: error sending message: %s", err)
			drop(msg)
			return err
		}
	}

	return nil
}

func drop(msg *router.Message) {
	log.Printf("gelf: dropping log message: %s", msg.Data)
}

func dropSome(logstream chan *router.Message, cancel <-chan time.Time) {
	for {
		select {
		case msg := <-logstream:
			drop(msg)
		case <-cancel:
			return
		}
	}
}

// newGoodWriter makes a new gelf writer. On error dialing, the logstream is
// drained for 5 seconds, dropping logs during that period.
func newGoodWriter(address string, logstream chan *router.Message) *gelf.Writer {
	for {
		writer, err := gelf.NewWriter(address)
		if err != nil {
			log.Printf("gelf: error dialing %s: %s", address, err)

			// Try redial after 5 seconds
			dropSome(logstream, time.After(5*time.Second))
		}

		log.Printf("gelf: dialed %s", address)

		// intended to forward logs to a service on localhost
		writer.CompressionLevel = flate.NoCompression

		return writer
	}
}

// Stream implements the router.LogAdapter interface. It redials the server if
// writes ever fail.
func (a *Adapter) Stream(logstream chan *router.Message) {
	f := func() error {
		writer := newGoodWriter(a.address, logstream)
		defer writer.Close()
		return streamSome(writer, logstream)
	}

	for {
		if err := f(); err != nil {
			continue // try again with new writer
		}
		return // end of stream
	}
}
<|endoftext|>