blob: a4d549a14228f29d1e853a3b2c314a8a1fcf2356 [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"
12 "syscall"
Philipp Schrader7365d322022-03-06 16:40:08 -080013 "time"
Philipp Schrader5562df72022-02-16 20:56:51 -080014
Philipp Schrader8747f1b2022-02-23 23:56:22 -080015 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schraderd3fac192022-03-02 20:35:46 -080016 "github.com/frc971/971-Robot-Code/scouting/scraping"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080017 "github.com/frc971/971-Robot-Code/scouting/webserver/requests"
Philipp Schrader5562df72022-02-16 20:56:51 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/static"
20)
21
Philipp Schrader7365d322022-03-06 16:40:08 -080022type DatabaseConfig struct {
23 Username string `json:"username"`
24 Password string `json:"password"`
25 Port int `json:"port"`
26}
27
28func readDatabaseConfig(configPath string) (*DatabaseConfig, error) {
29 content, err := ioutil.ReadFile(configPath)
30 if err != nil {
31 return nil, errors.New(fmt.Sprint("Failed to open database config at ", configPath, ": ", err))
Philipp Schrader4953cc32022-02-25 18:09:02 -080032 }
Philipp Schrader7365d322022-03-06 16:40:08 -080033
34 var config DatabaseConfig
35 if err := json.Unmarshal([]byte(content), &config); err != nil {
36 return nil, errors.New(fmt.Sprint("Failed to parse database config file as JSON: ", err))
Philipp Schrader4953cc32022-02-25 18:09:02 -080037 }
Philipp Schrader7365d322022-03-06 16:40:08 -080038
39 return &config, nil
Philipp Schrader4953cc32022-02-25 18:09:02 -080040}
41
Philipp Schrader5562df72022-02-16 20:56:51 -080042func main() {
43 portPtr := flag.Int("port", 8080, "The port number to bind to.")
44 dirPtr := flag.String("directory", ".", "The directory to serve at /.")
Philipp Schrader7365d322022-03-06 16:40:08 -080045 dbConfigPtr := flag.String("db_config", "",
46 "The postgres database JSON config. It needs the following keys: "+
47 "\"username\", \"password\", and \"port\". "+
48 "This option cannot be used in conjunction with -testdb_port.")
49 testDbPortPtr := flag.Int("testdb_port", 0,
50 "If set, connects to an instance of //scouting/db/testdb_server at the "+
51 "specified port. This option cannot be used in conjunction with "+
52 "-db_config.")
53 dbConnectRetries := flag.Int("db_retries", 5,
54 "The number of seconds to retry connecting to the database on startup.")
Philipp Schraderd3fac192022-03-02 20:35:46 -080055 blueAllianceConfigPtr := flag.String("tba_config", "",
56 "The path to your The Blue Alliance JSON config. "+
57 "It needs an \"api_key\" field with your TBA API key. "+
Philipp Schrader72beced2022-03-07 05:29:52 -080058 "Optionally, it can have a \"base_url\" field with the TBA API base URL.")
Philipp Schrader5562df72022-02-16 20:56:51 -080059 flag.Parse()
60
Philipp Schrader7365d322022-03-06 16:40:08 -080061 // Set up the database config. It's either specified via a JSON file on
62 // disk (-db_config) or we can generate an in-memory config when the
63 // user wants to connect to a `testdb_server` instance (-testdb_port).
64 var dbConfig *DatabaseConfig
65 var err error
66 if *dbConfigPtr != "" {
67 if *testDbPortPtr != 0 {
68 log.Fatal("Cannot specify both -db_config and -testdb_port. Choose one.")
69 }
70 dbConfig, err = readDatabaseConfig(*dbConfigPtr)
71 if err != nil {
72 log.Fatal("Failed to read ", *dbConfigPtr, ": ", err)
73 }
74 } else if *testDbPortPtr != 0 {
75 dbConfig = &DatabaseConfig{
76 Username: "test",
77 Password: "password",
78 Port: *testDbPortPtr,
79 }
80 } else {
81 log.Fatal("Must specify one of -db_config and -testdb_port. See " +
82 "https://github.com/frc971/971-Robot-Code/blob/master/scouting/README.md " +
83 "for more information.")
84 }
85
86 // Connect to the database. It may still be starting up so we do a
87 // bunch of retries here. The number of retries can be set on the
88 // command line.
89 var database *db.Database
90 for i := 0; i < *dbConnectRetries*10; i++ {
91 database, err = db.NewDatabase(dbConfig.Username, dbConfig.Password, dbConfig.Port)
92 if err == nil {
93 break
94 }
95 if i%10 == 0 {
96 log.Println("Failed to connect to the database. Retrying in a bit.")
97 }
98 time.Sleep(1 * time.Millisecond)
99 }
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800100 if err != nil {
101 log.Fatal("Failed to connect to database: ", err)
102 }
Philipp Schrader7365d322022-03-06 16:40:08 -0800103 defer database.Close()
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800104
Philipp Schraderd3fac192022-03-02 20:35:46 -0800105 scrapeMatchList := func(year int32, eventCode string) ([]scraping.Match, error) {
106 if *blueAllianceConfigPtr == "" {
107 return nil, errors.New("Cannot scrape TBA's match list without a config file.")
108 }
109 return scraping.AllMatches(year, eventCode, *blueAllianceConfigPtr)
110 }
111
Philipp Schrader5562df72022-02-16 20:56:51 -0800112 scoutingServer := server.NewScoutingServer()
113 static.ServePages(scoutingServer, *dirPtr)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800114 requests.HandleRequests(database, scrapeMatchList, scoutingServer)
Philipp Schrader5562df72022-02-16 20:56:51 -0800115 scoutingServer.Start(*portPtr)
116 fmt.Println("Serving", *dirPtr, "on port", *portPtr)
117
118 // Block until the user hits Ctrl-C.
119 sigint := make(chan os.Signal, 1)
120 signal.Notify(sigint, syscall.SIGINT)
Philipp Schrader7365d322022-03-06 16:40:08 -0800121 signal.Notify(sigint, syscall.SIGTERM)
122 fmt.Println("Waiting for CTRL-C or SIGTERM.")
Philipp Schrader5562df72022-02-16 20:56:51 -0800123 <-sigint
124
125 fmt.Println("Shutting down.")
126 scoutingServer.Stop()
127 fmt.Println("Successfully shut down.")
128}