blob: d655708f57c6db2108d672ab04e6a21fb91b9e01 [file] [log] [blame]
Philipp Schrader5562df72022-02-16 20:56:51 -08001package main
2
3import (
Philipp Schrader7365d322022-03-06 16:40:08 -08004 "encoding/json"
Philipp Schraderd3fac192022-03-02 20:35:46 -08005 "errors"
Philipp Schrader5562df72022-02-16 20:56:51 -08006 "flag"
7 "fmt"
Philipp Schrader4953cc32022-02-25 18:09:02 -08008 "io/ioutil"
Philipp Schrader8747f1b2022-02-23 23:56:22 -08009 "log"
Philipp Schrader5562df72022-02-16 20:56:51 -080010 "os"
11 "os/signal"
Philipp Schrader4af5af42023-03-11 15:59:51 -080012 "path"
Philipp Schrader4e661d62022-03-13 22:15:56 -070013 "strconv"
Philipp Schrader5562df72022-02-16 20:56:51 -080014 "syscall"
Philipp Schrader7365d322022-03-06 16:40:08 -080015 "time"
Philipp Schrader5562df72022-02-16 20:56:51 -080016
Philipp Schradera9a79392023-03-25 13:28:31 -070017 "github.com/frc971/971-Robot-Code/scouting/background_task"
Philipp Schrader8747f1b2022-02-23 23:56:22 -080018 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schrader02473002023-03-07 19:10:17 -080019 "github.com/frc971/971-Robot-Code/scouting/webserver/driver_ranking"
Philipp Schrader43c730b2023-02-26 20:27:44 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/match_list"
Yash Chainani37261d42022-04-19 16:21:27 -070021 "github.com/frc971/971-Robot-Code/scouting/webserver/rankings"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests"
Philipp Schrader5562df72022-02-16 20:56:51 -080023 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
24 "github.com/frc971/971-Robot-Code/scouting/webserver/static"
25)
26
Philipp Schrader7365d322022-03-06 16:40:08 -080027type DatabaseConfig struct {
28 Username string `json:"username"`
29 Password string `json:"password"`
30 Port int `json:"port"`
31}
32
33func readDatabaseConfig(configPath string) (*DatabaseConfig, error) {
34 content, err := ioutil.ReadFile(configPath)
35 if err != nil {
36 return nil, errors.New(fmt.Sprint("Failed to open database config at ", configPath, ": ", err))
Philipp Schrader4953cc32022-02-25 18:09:02 -080037 }
Philipp Schrader7365d322022-03-06 16:40:08 -080038
39 var config DatabaseConfig
40 if err := json.Unmarshal([]byte(content), &config); err != nil {
41 return nil, errors.New(fmt.Sprint("Failed to parse database config file as JSON: ", err))
Philipp Schrader4953cc32022-02-25 18:09:02 -080042 }
Philipp Schrader7365d322022-03-06 16:40:08 -080043
44 return &config, nil
Philipp Schrader4953cc32022-02-25 18:09:02 -080045}
46
Philipp Schrader4e661d62022-03-13 22:15:56 -070047// Gets the default port to use for the webserver. If wrapped by
48// apache_wrapper(), we use the port dictated by the wrapper.
49func getDefaultPort() int {
50 port_str := os.Getenv("APACHE_WRAPPED_PORT")
51 if port_str != "" {
52 port, err := strconv.Atoi(port_str)
53 if err != nil {
54 log.Fatalf("Failed to parse \"%s\" as integer: %v", port_str, err)
55 }
56 return port
57 }
58
59 return 8080
60}
61
Philipp Schrader4af5af42023-03-11 15:59:51 -080062func getDefaultBlueAllianceConfig() string {
63 workspaceDirectory := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
64 if workspaceDirectory != "" {
65 return path.Join(workspaceDirectory, "scouting_config.json")
66 }
67 return "scouting_config.json"
68}
69
Philipp Schrader5562df72022-02-16 20:56:51 -080070func main() {
Philipp Schrader4e661d62022-03-13 22:15:56 -070071 portPtr := flag.Int("port", getDefaultPort(), "The port number to bind to.")
Philipp Schrader5562df72022-02-16 20:56:51 -080072 dirPtr := flag.String("directory", ".", "The directory to serve at /.")
Philipp Schrader7365d322022-03-06 16:40:08 -080073 dbConfigPtr := flag.String("db_config", "",
74 "The postgres database JSON config. It needs the following keys: "+
75 "\"username\", \"password\", and \"port\". "+
76 "This option cannot be used in conjunction with -testdb_port.")
77 testDbPortPtr := flag.Int("testdb_port", 0,
78 "If set, connects to an instance of //scouting/db/testdb_server at the "+
79 "specified port. This option cannot be used in conjunction with "+
80 "-db_config.")
81 dbConnectRetries := flag.Int("db_retries", 5,
82 "The number of seconds to retry connecting to the database on startup.")
Philipp Schrader4af5af42023-03-11 15:59:51 -080083 blueAllianceConfigPtr := flag.String("tba_config", getDefaultBlueAllianceConfig(),
Philipp Schraderd3fac192022-03-02 20:35:46 -080084 "The path to your The Blue Alliance JSON config. "+
85 "It needs an \"api_key\" field with your TBA API key. "+
Philipp Schrader4af5af42023-03-11 15:59:51 -080086 "It needs a \"year\" field with the event year. "+
87 "It needs an \"event_code\" field with the event code. "+
Philipp Schrader72beced2022-03-07 05:29:52 -080088 "Optionally, it can have a \"base_url\" field with the TBA API base URL.")
Philipp Schrader5562df72022-02-16 20:56:51 -080089 flag.Parse()
90
Philipp Schrader7365d322022-03-06 16:40:08 -080091 // Set up the database config. It's either specified via a JSON file on
92 // disk (-db_config) or we can generate an in-memory config when the
93 // user wants to connect to a `testdb_server` instance (-testdb_port).
94 var dbConfig *DatabaseConfig
95 var err error
96 if *dbConfigPtr != "" {
97 if *testDbPortPtr != 0 {
98 log.Fatal("Cannot specify both -db_config and -testdb_port. Choose one.")
99 }
100 dbConfig, err = readDatabaseConfig(*dbConfigPtr)
101 if err != nil {
102 log.Fatal("Failed to read ", *dbConfigPtr, ": ", err)
103 }
104 } else if *testDbPortPtr != 0 {
105 dbConfig = &DatabaseConfig{
106 Username: "test",
107 Password: "password",
108 Port: *testDbPortPtr,
109 }
110 } else {
111 log.Fatal("Must specify one of -db_config and -testdb_port. See " +
112 "https://github.com/frc971/971-Robot-Code/blob/master/scouting/README.md " +
113 "for more information.")
114 }
115
116 // Connect to the database. It may still be starting up so we do a
117 // bunch of retries here. The number of retries can be set on the
118 // command line.
119 var database *db.Database
120 for i := 0; i < *dbConnectRetries*10; i++ {
121 database, err = db.NewDatabase(dbConfig.Username, dbConfig.Password, dbConfig.Port)
122 if err == nil {
123 break
124 }
125 if i%10 == 0 {
126 log.Println("Failed to connect to the database. Retrying in a bit.")
127 }
128 time.Sleep(1 * time.Millisecond)
129 }
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800130 if err != nil {
131 log.Fatal("Failed to connect to database: ", err)
132 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700133 defer database.Delete()
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800134
Philipp Schrader5562df72022-02-16 20:56:51 -0800135 scoutingServer := server.NewScoutingServer()
Emily Markovafaecfe12023-07-01 12:40:03 -0700136 static.ServePages(scoutingServer, *dirPtr, database)
Philipp Schrader43c730b2023-02-26 20:27:44 -0800137 requests.HandleRequests(database, scoutingServer)
Philipp Schrader5562df72022-02-16 20:56:51 -0800138 scoutingServer.Start(*portPtr)
139 fmt.Println("Serving", *dirPtr, "on port", *portPtr)
140
Philipp Schrader43c730b2023-02-26 20:27:44 -0800141 // Since Go doesn't support default arguments, we use 0 and "" to
142 // indicate that we want to source the values from the config.
143
Philipp Schrader3f0e36f2023-03-25 13:49:41 -0700144 matchListScraper := background_task.New(10 * time.Minute)
Philipp Schrader43c730b2023-02-26 20:27:44 -0800145 matchListScraper.Start(func() {
146 match_list.GetMatchList(database, 0, "", *blueAllianceConfigPtr)
147 })
148
Philipp Schrader3f0e36f2023-03-25 13:49:41 -0700149 rankingsScraper := background_task.New(10 * time.Minute)
Philipp Schraderc49eaf72023-02-26 16:56:52 -0800150 rankingsScraper.Start(func() {
151 rankings.GetRankings(database, 0, "", *blueAllianceConfigPtr)
152 })
Yash Chainani37261d42022-04-19 16:21:27 -0700153
Philipp Schrader3f0e36f2023-03-25 13:49:41 -0700154 driverRankingParser := background_task.New(10 * time.Minute)
Philipp Schrader02473002023-03-07 19:10:17 -0800155 driverRankingParser.Start(func() {
156 // Specify "" as the script path here so that the default is
157 // used.
158 driver_ranking.GenerateFullDriverRanking(database, "")
159 })
160
Philipp Schrader5562df72022-02-16 20:56:51 -0800161 // Block until the user hits Ctrl-C.
162 sigint := make(chan os.Signal, 1)
163 signal.Notify(sigint, syscall.SIGINT)
Philipp Schrader7365d322022-03-06 16:40:08 -0800164 signal.Notify(sigint, syscall.SIGTERM)
165 fmt.Println("Waiting for CTRL-C or SIGTERM.")
Philipp Schrader5562df72022-02-16 20:56:51 -0800166 <-sigint
167
168 fmt.Println("Shutting down.")
169 scoutingServer.Stop()
Philipp Schraderc49eaf72023-02-26 16:56:52 -0800170 rankingsScraper.Stop()
Philipp Schrader02473002023-03-07 19:10:17 -0800171 driverRankingParser.Stop()
Philipp Schrader43c730b2023-02-26 20:27:44 -0800172 matchListScraper.Stop()
Philipp Schrader5562df72022-02-16 20:56:51 -0800173 fmt.Println("Successfully shut down.")
174}