2018-05-02 15:45:45 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-05-04 17:20:51 -06:00
|
|
|
"bufio"
|
2018-05-08 12:45:50 -06:00
|
|
|
"github.com/namsral/flag"
|
2018-05-02 15:45:45 -06:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2018-05-06 21:37:52 -06:00
|
|
|
var moduleDir string
|
|
|
|
var stateDir string
|
|
|
|
var cacheDir string
|
2018-05-02 15:45:45 -06:00
|
|
|
var categories = []string{}
|
|
|
|
|
2018-05-04 17:20:51 -06:00
|
|
|
// anchoredSearch looks for needle in filename,
|
|
|
|
// skipping the first skip space-delimited words
|
|
|
|
func anchoredSearch(filename string, needle string, skip int) bool {
|
|
|
|
f, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
log.Print("Can't open %s: %s", filename, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
parts := strings.SplitN(" ", line, skip+1)
|
|
|
|
if parts[skip+1] == needle {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2018-05-02 17:00:53 -06:00
|
|
|
|
2018-05-04 17:20:51 -06:00
|
|
|
return false
|
2018-05-02 15:45:45 -06:00
|
|
|
}
|
|
|
|
|
2018-05-04 17:20:51 -06:00
|
|
|
func showPage(w http.ResponseWriter, title string, body string) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "<!DOCTYPE html>")
|
|
|
|
fmt.Fprintf(w, "<html><head>")
|
|
|
|
fmt.Fprintf(w, "<title>%s</title>", title)
|
2018-05-08 12:45:50 -06:00
|
|
|
fmt.Fprintf(w, "<link rel=\"stylesheet\" href=\"static/style.css\">")
|
2018-05-04 17:20:51 -06:00
|
|
|
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\"></head>")
|
2018-05-08 12:45:50 -06:00
|
|
|
fmt.Fprintf(w, "<link rel=\"icon\" href=\"res/luna-moth.svg\" type=\"image/svg+xml\">")
|
|
|
|
fmt.Fprintf(w, "<link rel=\"icon\" href=\"res/luna-moth.png\" type=\"image/png\">")
|
2018-05-04 17:20:51 -06:00
|
|
|
fmt.Fprintf(w, "<body><h1>%s</h1>", title)
|
|
|
|
fmt.Fprintf(w, "<section>%s</section>", body)
|
|
|
|
fmt.Fprintf(w, "<nav>")
|
|
|
|
fmt.Fprintf(w, "<ul>")
|
2018-05-08 12:45:50 -06:00
|
|
|
fmt.Fprintf(w, "<li><a href=\"static/puzzles.html\">Puzzles</a></li>")
|
|
|
|
fmt.Fprintf(w, "<li><a href=\"static/scoreboard.html\">Scoreboard</a></li>")
|
2018-05-04 17:20:51 -06:00
|
|
|
fmt.Fprintf(w, "</ul>")
|
|
|
|
fmt.Fprintf(w, "</nav>")
|
|
|
|
fmt.Fprintf(w, "</body></html>")
|
2018-05-02 15:45:45 -06:00
|
|
|
}
|
|
|
|
|
2018-05-06 21:37:52 -06:00
|
|
|
func modulesPath(parts ...string) string {
|
2018-05-04 17:20:51 -06:00
|
|
|
tail := path.Join(parts...)
|
2018-05-06 21:37:52 -06:00
|
|
|
return path.Join(moduleDir, tail)
|
2018-05-02 15:45:45 -06:00
|
|
|
}
|
|
|
|
|
2018-05-02 17:00:53 -06:00
|
|
|
func statePath(parts ...string) string {
|
2018-05-04 17:20:51 -06:00
|
|
|
tail := path.Join(parts...)
|
2018-05-06 21:37:52 -06:00
|
|
|
return path.Join(stateDir, tail)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cachePath(parts ...string) string {
|
|
|
|
tail := path.Join(parts...)
|
|
|
|
return path.Join(cacheDir, tail)
|
2018-05-02 15:45:45 -06:00
|
|
|
}
|
|
|
|
|
2018-05-08 12:45:50 -06:00
|
|
|
func logRequest(handler http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log.Printf("HTTP %s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
|
|
|
handler.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-05-06 21:37:52 -06:00
|
|
|
func setup() error {
|
|
|
|
// Roll over and die if directories aren't even set up
|
|
|
|
if _, err := os.Stat(modulesPath()); os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := os.Stat(statePath()); os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := os.Stat(cachePath()); os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure points directories exist
|
|
|
|
os.Mkdir(statePath("points.tmp"), 0755)
|
|
|
|
os.Mkdir(statePath("points.new"), 0755)
|
|
|
|
|
|
|
|
// Preseed available team ids if file doesn't exist
|
|
|
|
if f, err := os.OpenFile(statePath("teamids.txt"), os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0644); err == nil {
|
|
|
|
defer f.Close()
|
|
|
|
for i := 0; i <= 9999; i += 1 {
|
|
|
|
fmt.Fprintf(f, "%04d\n", i)
|
|
|
|
}
|
2018-05-02 15:45:45 -06:00
|
|
|
}
|
2018-05-06 21:37:52 -06:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-05-02 15:45:45 -06:00
|
|
|
func main() {
|
2018-05-08 12:45:50 -06:00
|
|
|
var maintenanceInterval time.Duration
|
|
|
|
var listen string
|
|
|
|
|
|
|
|
fs := flag.NewFlagSetWithEnvPrefix(os.Args[0], "MOTH", flag.ExitOnError)
|
|
|
|
fs.StringVar(
|
2018-05-06 21:37:52 -06:00
|
|
|
&moduleDir,
|
|
|
|
"modules",
|
2018-05-08 12:45:50 -06:00
|
|
|
"/moth/modules",
|
2018-05-06 21:37:52 -06:00
|
|
|
"Path where your moth modules live",
|
|
|
|
)
|
2018-05-08 12:45:50 -06:00
|
|
|
fs.StringVar(
|
2018-05-06 21:37:52 -06:00
|
|
|
&stateDir,
|
|
|
|
"state",
|
2018-05-08 12:45:50 -06:00
|
|
|
"/moth/state",
|
2018-05-06 21:37:52 -06:00
|
|
|
"Path where state should be written",
|
|
|
|
)
|
2018-05-08 12:45:50 -06:00
|
|
|
fs.StringVar(
|
2018-05-06 21:37:52 -06:00
|
|
|
&cacheDir,
|
|
|
|
"cache",
|
2018-05-08 12:45:50 -06:00
|
|
|
"/moth/cache",
|
2018-05-06 21:37:52 -06:00
|
|
|
"Path for ephemeral cache",
|
|
|
|
)
|
2018-05-08 12:45:50 -06:00
|
|
|
fs.DurationVar(
|
|
|
|
&maintenanceInterval,
|
2018-05-06 21:37:52 -06:00
|
|
|
"maint",
|
|
|
|
20 * time.Second,
|
|
|
|
"Maintenance interval",
|
|
|
|
)
|
2018-05-08 12:45:50 -06:00
|
|
|
fs.StringVar(
|
|
|
|
&listen,
|
2018-05-06 21:37:52 -06:00
|
|
|
"listen",
|
|
|
|
":8080",
|
|
|
|
"[host]:port to bind and listen",
|
|
|
|
)
|
2018-05-08 12:45:50 -06:00
|
|
|
fs.Parse(os.Args[1:])
|
2018-05-06 21:37:52 -06:00
|
|
|
|
|
|
|
if err := setup(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2018-05-08 12:45:50 -06:00
|
|
|
go maintenance(maintenanceInterval)
|
2018-05-06 21:37:52 -06:00
|
|
|
|
2018-05-08 12:45:50 -06:00
|
|
|
fileserver := http.FileServer(http.Dir(cacheDir))
|
2018-05-06 21:37:52 -06:00
|
|
|
http.HandleFunc("/", rootHandler)
|
2018-05-08 12:45:50 -06:00
|
|
|
http.Handle("/static/", http.StripPrefix("/static", fileserver))
|
2018-05-06 21:37:52 -06:00
|
|
|
|
2018-05-04 17:20:51 -06:00
|
|
|
http.HandleFunc("/register", registerHandler)
|
|
|
|
http.HandleFunc("/token", tokenHandler)
|
|
|
|
http.HandleFunc("/answer", answerHandler)
|
2018-05-08 12:45:50 -06:00
|
|
|
|
|
|
|
http.HandleFunc("/puzzles.json", puzzlesHandler)
|
|
|
|
http.HandleFunc("/points.json", pointsHandler)
|
2018-05-02 15:45:45 -06:00
|
|
|
|
2018-05-08 12:45:50 -06:00
|
|
|
log.Printf("Listening on %s", listen)
|
|
|
|
log.Fatal(http.ListenAndServe(listen, logRequest(http.DefaultServeMux)))
|
2018-05-06 21:37:52 -06:00
|
|
|
}
|