This commit is contained in:
Neale Pickett 2018-05-07 03:37:52 +00:00
parent f84b23ace4
commit 0b8db09752
4 changed files with 144 additions and 49 deletions

View File

@ -22,7 +22,7 @@ func registerHandler(w http.ResponseWriter, req *http.Request) {
return return
} }
if ! anchoredSearch(statePath("assigned.txt"), teamid, 0) { if ! anchoredSearch(statePath("teamids.txt"), teamid, 0) {
showPage(w, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?") showPage(w, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?")
return return
} }
@ -97,7 +97,7 @@ func answerHandler(w http.ResponseWriter, req *http.Request) {
// Check answer // Check answer
needle := fmt.Sprintf("%s %s", points, answer) needle := fmt.Sprintf("%s %s", points, answer)
haystack := mothPath("packages", category, "answers.txt") haystack := cachePath(category, "answers.txt")
if ! anchoredSearch(haystack, needle, 0) { if ! anchoredSearch(haystack, needle, 0) {
showPage(w, "Wrong answer", err.Error()) showPage(w, "Wrong answer", err.Error())
} }
@ -109,3 +109,30 @@ func answerHandler(w http.ResponseWriter, req *http.Request) {
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid)) showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
} }
// staticHandler serves up static files.
func rootHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/" {
showPage(
w,
"Welcome",
`
<h2>Register your team</h2>
<form action="register" action="post">
Team ID: <input name="h"> <br>
Team name: <input name="n">
<input type="submit" value="Register">
</form>
<div>
If someone on your team has already registered,
proceed to the
<a href="puzzles">puzzles overview</a>.
</div>
`,
)
return
}
http.NotFound(w, req)
}

View File

@ -15,36 +15,36 @@ func cacheMothball(filepath string, categoryName string) {
// maintenance runs // maintenance runs
func tidy() { func tidy() {
// Skip if we've been disabled // Skip if we've been disabled
if exists(statePath("disabled")) { if _, err := os.Stat(statePath("disabled")); err == nil {
log.Print("disabled file found, suspending maintenance") log.Print("disabled file found, suspending maintenance")
return return
} }
// Skip if we've expired // Skip if we've expired
untilspec, _ := ioutil.ReadFile(statePath("until")) untilspec, err := ioutil.ReadFile(statePath("until"))
until, err := time.Parse(time.RFC3339, string(untilspec))
if err == nil { if err == nil {
until, err := time.Parse(time.RFC3339, string(untilspec))
if err != nil {
log.Print("Unparseable date in until file: %s", until)
} else {
if until.Before(time.Now()) { if until.Before(time.Now()) {
log.Print("until file time reached, suspending maintenance") log.Print("until file time reached, suspending maintenance")
return return
} }
} }
}
log.Print("Hello, I'm maintaining!") log.Print("Hello, I'm maintaining!")
// Make sure points directories exist
os.Mkdir(statePath("points.tmp"), 0755)
os.Mkdir(statePath("points.new"), 0755)
// Get current list of categories // Get current list of categories
newCategories := []string{} newCategories := []string{}
files, err := ioutil.ReadDir(mothPath("packages")) files, err := ioutil.ReadDir(modulesPath())
if err != nil { if err != nil {
log.Printf("Error reading packages: %s", err) log.Printf("Error reading packages: %s", err)
} }
for _, f := range files { for _, f := range files {
filename := f.Name() filename := f.Name()
filepath := mothPath("packages", filename) filepath := modulesPath(filename)
if ! strings.HasSuffix(filename, ".mb") { if ! strings.HasSuffix(filename, ".mb") {
continue continue
} }
@ -61,7 +61,7 @@ func tidy() {
} }
// maintenance is the goroutine that runs a periodic maintenance task // maintenance is the goroutine that runs a periodic maintenance task
func maintenance() { func maintenance(maintenanceInterval time.Duration) {
for ;; time.Sleep(maintenanceInterval) { for ;; time.Sleep(maintenanceInterval) {
tidy() tidy()
} }

117
mothd.go
View File

@ -2,18 +2,19 @@ package main
import ( import (
"bufio" "bufio"
"flag"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"io/ioutil"
"os" "os"
"path" "path"
"strings" "strings"
"time" "time"
) )
var basePath = "/home/neale/src/moth" var moduleDir string
var maintenanceInterval = 20 * time.Second var stateDir string
var cacheDir string
var categories = []string{} var categories = []string{}
// anchoredSearch looks for needle in filename, // anchoredSearch looks for needle in filename,
@ -38,25 +39,6 @@ func anchoredSearch(filename string, needle string, skip int) bool {
return false return false
} }
func awardPoints(teamid string, category string, points int) error {
fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
tmpfn := statePath("points.tmp", fn)
newfn := statePath("points.new", fn)
contents := fmt.Sprintf("%d %s %s %d\n", time.Now().Unix(), teamid, points)
if err := ioutil.WriteFile(tmpfn, []byte(contents), 0644); err != nil {
return err
}
if err := os.Rename(tmpfn, newfn); err != nil {
return err
}
return nil
}
func showPage(w http.ResponseWriter, title string, body string) { func showPage(w http.ResponseWriter, title string, body string) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@ -77,30 +59,97 @@ func showPage(w http.ResponseWriter, title string, body string) {
fmt.Fprintf(w, "</body></html>") fmt.Fprintf(w, "</body></html>")
} }
func mothPath(parts ...string) string { func modulesPath(parts ...string) string {
tail := path.Join(parts...) tail := path.Join(parts...)
return path.Join(basePath, tail) return path.Join(moduleDir, tail)
} }
func statePath(parts ...string) string { func statePath(parts ...string) string {
tail := path.Join(parts...) tail := path.Join(parts...)
return path.Join(basePath, "state", tail) return path.Join(stateDir, tail)
} }
func exists(filename string) bool { func cachePath(parts ...string) string {
if _, err := os.Stat(filename); os.IsNotExist(err) { tail := path.Join(parts...)
return false; return path.Join(cacheDir, tail)
} }
return true;
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)
}
}
return nil
}
func logRequest(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
handler.ServeHTTP(w, r)
})
} }
func main() { func main() {
log.Print("Sup") flag.StringVar(
go maintenance(); &moduleDir,
"modules",
"/modules",
"Path where your moth modules live",
)
flag.StringVar(
&stateDir,
"state",
"/state",
"Path where state should be written",
)
flag.StringVar(
&cacheDir,
"cache",
"/cache",
"Path for ephemeral cache",
)
maintenanceInterval := flag.Duration(
"maint",
20 * time.Second,
"Maintenance interval",
)
listen := flag.String(
"listen",
":8080",
"[host]:port to bind and listen",
)
if err := setup(); err != nil {
log.Fatal(err)
}
go maintenance(*maintenanceInterval)
http.HandleFunc("/", rootHandler)
http.Handle("/static/", http.FileServer(http.Dir(cacheDir)))
http.HandleFunc("/register", registerHandler) http.HandleFunc("/register", registerHandler)
http.HandleFunc("/token", tokenHandler) http.HandleFunc("/token", tokenHandler)
http.HandleFunc("/answer", answerHandler) http.HandleFunc("/answer", answerHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
// docker run --rm -it -p 5880:8080 -v $HOME:$HOME:ro -w $(pwd) golang go run mothd.go log.Printf("Listening on %s", *listen)
log.Fatal(http.ListenAndServe(*listen, logRequest(http.DefaultServeMux)))
}

View File

@ -73,6 +73,25 @@ func pointsLog() []Award {
return ret return ret
} }
// awardPoints gives points points to team teamid in category category
func awardPoints(teamid string, category string, points int) error {
fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
tmpfn := statePath("points.tmp", fn)
newfn := statePath("points.new", fn)
contents := fmt.Sprintf("%d %s %s %d\n", time.Now().Unix(), teamid, points)
if err := ioutil.WriteFile(tmpfn, []byte(contents), 0644); err != nil {
return err
}
if err := os.Rename(tmpfn, newfn); err != nil {
return err
}
return nil
}
// collectPoints gathers up files in points.new/ and appends their contents to points.log, // collectPoints gathers up files in points.new/ and appends their contents to points.log,
// removing each points.new/ file as it goes. // removing each points.new/ file as it goes.
func collectPoints() { func collectPoints() {