-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame.go
191 lines (172 loc) · 6.14 KB
/
game.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package gamedayapi
import (
"bytes"
"encoding/xml"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
s "strings"
"time"
)
// Game is the top-level abstraction, the starting point for clients.
// A game is obtained using the GameFor function.
// From a game, clients can navigate to all data: Innings, Boxscore, etc.
type Game struct {
AwayAPMP string `xml:"away_ampm,attr"`
AwayCode string `xml:"away_code,attr"`
AwayFileCode string `xml:"away_file_code,attr"`
AwayLoss string `xml:"away_loss,attr"`
AwayTeamCity string `xml:"away_team_city,attr"`
AwayTeamID string `xml:"away_team_id,attr"`
AwayTeamName string `xml:"away_team_name,attr"`
AwayTime string `xml:"away_time,attr"`
AwayTimezone string `xml:"away_time_zone,attr"`
AwayWin string `xml:"away_win,attr"`
CalendarEventID string `xml:"calendar_event_id,attr"`
HomeAMPM string `xml:"home_ampm,attr"`
HomeCode string `xml:"home_code,attr"`
HomeFileCode string `xml:"home_file_code,attr"`
HomeLoss string `xml:"home_loss,attr"`
HomeTeamCity string `xml:"home_team_city,attr"`
HomeTeamID string `xml:"home_team_id,attr"`
HomeTeamName string `xml:"home_team_name,attr"`
HomeTime string `xml:"home_time,attr"`
HomeTimezone string `xml:"home_time_zone,attr"`
HomeWin string `xml:"home_win,attr"`
ID string `xml:"id,attr"`
Gameday string `xml:"gameday,attr"`
GamePk string `xml:"game_pk,attr"`
GameType string `xml:"game_type,attr"`
Status string `xml:"status,attr"`
TimeDate string `xml:"time_date,attr"`
Timezone string `xml:"time_zone,attr"`
Venue string `xml:"venue,attr"`
// GameDataDirectory does not always point to where the files are.
// Use FetchableDataDirectory for building requests to gameday servers.
GameDataDirectory string `xml:"game_data_directory,attr"`
allInnings AllInnings
boxscore Boxscore
hitChart HitChart
players Players
year int
}
// GameFor will return a pointer to a game instance for the team code and date provided.
// This is the place to start for interacting with a game.
// Does not account for doubleheaders. Use GamesFor if doubleheader support is needed.
func GameFor(teamCode string, date time.Time) (*Game, error) {
epg := EpgFor(date)
game, err := epg.GameForTeam(teamCode)
if err != nil {
return &Game{}, err
}
return game, nil
}
// GamesFor will return a collection of pointers to games for the team code and date provided.
// Accounts for doubleheaders. In most cases, the collection will only have one game in it.
func GamesFor(teamCode string, date time.Time) ([]*Game, error) {
epg := EpgFor(date)
games, err := epg.GamesForTeam(teamCode)
if err != nil {
return games, err
}
return games, nil
}
// IsFinal returns true if the game status is Final.
func (game *Game) IsFinal() bool {
return game.Status == "Final"
}
// AllInnings fetches the inning/innings_all.xml file from gameday servers and fills in all the
// structs beneath, all the way down to the pitches.
func (game *Game) AllInnings() *AllInnings {
if game.IsFinal() && len(game.allInnings.AtBat) == 0 {
game.load("/inning/inning_all.xml", &game.allInnings)
}
return &game.allInnings
}
// Boxscore fetches the boxscore.xml file from the gameday servers and fills in all the structs beneath.
func (game *Game) Boxscore() *Boxscore {
if game.IsFinal() && len(game.boxscore.GameID) == 0 {
game.load("/boxscore.xml", &game.boxscore)
}
return &game.boxscore
}
// HitChart fetches the inning/inning_hit.xml file from gameday servers
func (game *Game) HitChart() *HitChart {
if game.IsFinal() && len(game.hitChart.Hips) == 0 {
game.load("/inning/inning_hit.xml", &game.hitChart)
}
return &game.hitChart
}
// Players fetches the players.xml file from gameday servers
func (game *Game) Players() *Players {
if game.IsFinal() && len(game.players.Date) == 0 {
game.load("/players.xml", &game.players)
}
return &game.players
}
//func (game *Game) InningScores() *InningScores {}
// EagerLoad will eagerly load all of the files that the library pulls from the MLB gameday servers.
// Otherwise, files are lazily loaded as clients interact with the API.
func (game *Game) EagerLoad() {
game.AllInnings()
game.Boxscore()
game.HitChart()
game.Players()
}
// Year returns the year in which the game was played.
// Convenience method. Not a direct Gameday attribute.
func (game *Game) Year() int {
if game.year == 0 {
idPieces := s.Split(game.ID, "/")
year, _ := strconv.Atoi(idPieces[0])
game.year = year
}
return game.year
}
func (game Game) load(fileName string, val interface{}) {
filePath := game.FetchableDataDirectory() + fileName
localFilePath := BaseCachePath() + filePath
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
log.Println("Cache miss on " + localFilePath)
fetchAndCache(filePath, val)
} else {
log.Println("Cache hit on " + localFilePath)
body, _ := ioutil.ReadFile(localFilePath)
xml.Unmarshal(body, val)
}
}
// FetchableDataDirectory builds a data directory to the game files using the game's ID.
// GameDataDirectory is not always reliable (see epg on 2013-04-11 vs 2013-04-10 as an example).
func (game Game) FetchableDataDirectory() string {
idPieces := s.Split(game.ID, "/")
var buffer bytes.Buffer
buffer.WriteString(GamedayBasePath)
buffer.WriteString(fmt.Sprintf("/year_%s/month_%s/day_%s/gid_", idPieces[0], idPieces[1], idPieces[2]))
buffer.WriteString(game.Gameday)
return buffer.String()
}
func fetchAndCache(filePath string, val interface{}) {
urlToFetch := GamedayHostname + filePath
log.Println("Fetching " + urlToFetch)
resp, err := http.Get(urlToFetch)
check(err)
if resp.StatusCode != 200 {
log.Fatal(resp.Status + " fetching " + urlToFetch)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
check(err)
xml.Unmarshal(body, val)
cacheFile(filePath, body)
}
func cacheFile(filePath string, body []byte) {
localCachePath := BaseCachePath() + filePath[0:s.LastIndex(filePath, "/")]
os.MkdirAll(localCachePath, (os.FileMode)(0775))
f, err := os.Create(localCachePath + filePath[s.LastIndex(filePath, "/"):])
f.Write(body)
check(err)
defer f.Close()
}