It compiles!

This commit is contained in:
Neale Pickett 2018-05-04 23:20:51 +00:00
parent f950dacf5e
commit 2fd03f390f
4 changed files with 245 additions and 53 deletions

View File

@ -3,5 +3,109 @@ package main
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"os"
"regexp"
"strings"
"strconv"
) )
func registerHandler(w http.ResponseWriter, req *http.Request) {
teamname := req.FormValue("n")
teamid := req.FormValue("h")
if matched, _ := regexp.MatchString("[^0-9a-z]", teamid); matched {
teamid = ""
}
if (teamid == "") || (teamname == "") {
showPage(w, "Invalid Entry", "Oops! Are you sure you got that right?")
return
}
if ! anchoredSearch(statePath("assigned.txt"), teamid, 0) {
showPage(w, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?")
return
}
f, err := os.OpenFile(statePath("state", teamid), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
showPage(
w,
"Registration failed",
"Unable to register. Perhaps a teammate has already registered?",
)
return
}
defer f.Close()
fmt.Fprintln(f, teamname)
showPage(w, "Success", "Okay, your team has been named and you may begin using your team ID!")
}
func tokenHandler(w http.ResponseWriter, req *http.Request) {
teamid := req.FormValue("t")
token := req.FormValue("k")
// Check answer
if ! anchoredSearch(token, statePath("tokens.txt"), 0) {
showPage(w, "Unrecognized token", "I don't recognize that token. Did you type in the whole thing?")
return
}
parts := strings.Split(token, ":")
category := ""
pointstr := ""
if len(parts) >= 2 {
category = parts[0]
pointstr = parts[1]
}
points, err := strconv.Atoi(pointstr)
if err != nil {
points = 0
}
// Defang category name; prevent directory traversal
if matched, _ := regexp.MatchString("^[A-Za-z0-9_-]", category); matched {
category = ""
}
if (category == "") || (points == 0) {
showPage(w, "Unrecognized token", "Something doesn't look right about that token")
return
}
if err := awardPoints(teamid, category, points); err != nil {
showPage(w, "Error awarding points", err.Error())
return
}
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
}
func answerHandler(w http.ResponseWriter, req *http.Request) {
teamid := req.FormValue("t")
category := req.FormValue("c")
pointstr := req.FormValue("p")
answer := req.FormValue("a")
points, err := strconv.Atoi(pointstr)
if err != nil {
points = 0
}
// Defang category name; prevent directory traversal
if matched, _ := regexp.MatchString("^[A-Za-z0-9_-]", category); matched {
category = ""
}
// Check answer
needle := fmt.Sprintf("%s %s", points, answer)
haystack := mothPath("packages", category, "answers.txt")
if ! anchoredSearch(haystack, needle, 0) {
showPage(w, "Wrong answer", err.Error())
}
if err := awardPoints(teamid, category, points); err != nil {
showPage(w, "Error awarding points", err.Error())
return
}
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
}

View File

@ -4,19 +4,14 @@ import (
"log" "log"
"io/ioutil" "io/ioutil"
"time" "time"
"os"
"strings" "strings"
) )
func allfiles(dirpath) []string { func cacheMothball(filepath string, categoryName string) {
files, err := ioutil.ReadDir(dirpath) log.Printf("I'm exploding a mothball %s %s", filepath, categoryName)
if (err != nil) {
log.Printf("Error reading directory %s: %s", dirpath, err)
return []
}
return files
} }
// maintenance runs // maintenance runs
func tidy() { func tidy() {
// Skip if we've been disabled // Skip if we've been disabled
@ -37,11 +32,17 @@ func tidy() {
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{}
for f := range(allfiles(mothPath("packages"))) { files, err := ioutil.ReadDir(mothPath("packages"))
if err != nil {
log.Printf("Error reading packages: %s", err)
}
for _, f := range files {
filename := f.Name() filename := f.Name()
filepath := mothPath("packages", filename) filepath := mothPath("packages", filename)
if ! strings.HasSuffix(filename, ".mb") { if ! strings.HasSuffix(filename, ".mb") {

View File

@ -1,41 +1,90 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"html"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"io/ioutil"
"os" "os"
"path" "path"
"strconv"
"strings" "strings"
"time" "time"
) )
var basePath = "." var basePath = "/home/neale/src/moth"
var maintenanceInterval = 20 * time.Second var maintenanceInterval = 20 * time.Second
var categories = []string{} var categories = []string{}
// 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
}
}
func mooHandler(w http.ResponseWriter, req *http.Request) { return false
moo := req.FormValue("moo")
fmt.Fprintf(w, "Hello, %q. %s", html.EscapeString(req.URL.Path), html.EscapeString(moo))
} }
func rootHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" { func awardPoints(teamid string, category string, points int) error {
http.NotFound(w, req) fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
return 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) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "<!DOCTYPE html>")
fmt.Fprintf(w, "<html><head>")
fmt.Fprintf(w, "<title>%s</title>", title)
fmt.Fprintf(w, "<link rel=\"stylesheet\" href=\"../style.css\">")
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\"></head>")
fmt.Fprintf(w, "<body><h1>%s</h1>", title)
fmt.Fprintf(w, "<section>%s</section>", body)
fmt.Fprintf(w, "<nav>")
fmt.Fprintf(w, "<ul>")
fmt.Fprintf(w, "<li><a href=\"../register.html\">Register</a></li>")
fmt.Fprintf(w, "<li><a href=\"../puzzles.html\">Puzzles</a></li>")
fmt.Fprintf(w, "<li><a href=\"../scoreboard.html\">Scoreboard</a></li>")
fmt.Fprintf(w, "</ul>")
fmt.Fprintf(w, "</nav>")
fmt.Fprintf(w, "</body></html>")
} }
func mothPath(parts ...string) string { func mothPath(parts ...string) string {
return path.Join(basePath, parts...) tail := path.Join(parts...)
return path.Join(basePath, tail)
} }
func statePath(parts ...string) string { func statePath(parts ...string) string {
return path.Join(basePath, "state", parts...) tail := path.Join(parts...)
return path.Join(basePath, "state", tail)
} }
func exists(filename string) bool { func exists(filename string) bool {
@ -48,8 +97,9 @@ func exists(filename string) bool {
func main() { func main() {
log.Print("Sup") log.Print("Sup")
go maintenance(); go maintenance();
http.HandleFunc("/", rootHandler) http.HandleFunc("/register", registerHandler)
http.HandleFunc("/moo/", mooHandler) http.HandleFunc("/token", tokenHandler)
http.HandleFunc("/answer", answerHandler)
log.Fatal(http.ListenAndServe(":8080", nil)) log.Fatal(http.ListenAndServe(":8080", nil))
} }

View File

@ -1,17 +1,21 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"strconv"
"strings"
"time"
) )
type Award struct { type Award struct {
when time.Time, when time.Time
team string, team string
category string, category string
points int, points int
comment string
} }
func ParseAward(s string) (*Award, error) { func ParseAward(s string) (*Award, error) {
@ -19,53 +23,86 @@ func ParseAward(s string) (*Award, error) {
parts := strings.SplitN(s, " ", 5) parts := strings.SplitN(s, " ", 5)
if len(parts) < 4 { if len(parts) < 4 {
return nil, Error("Malformed award string") return nil, fmt.Errorf("Malformed award string")
} }
whenEpoch, err = strconv.Atoi(parts[0]) whenEpoch, err := strconv.ParseInt(parts[0], 10, 64)
if (err != nil) { if (err != nil) {
return nil, Errorf("Malformed timestamp: %s", parts[0]) return nil, fmt.Errorf("Malformed timestamp: %s", parts[0])
} }
ret.when = time.Unix(whenEpoch, 0) ret.when = time.Unix(whenEpoch, 0)
ret.team = parts[1] ret.team = parts[1]
ret.category = parts[2] ret.category = parts[2]
points, err = strconv.Atoi(parts[3]) points, err := strconv.Atoi(parts[3])
if (err != nil) { if (err != nil) {
return nil, Errorf("Malformed points: %s", parts[3]) return nil, fmt.Errorf("Malformed points: %s", parts[3])
} }
ret.points = points
if len(parts) == 5 {
ret.comment = parts[4] return &ret, nil
}
return &ret
} }
func (a *Award) String() string { func (a *Award) String() string {
return fmt.Sprintf("%d %s %s %d %s", a.when.Unix(), a.team, a.category, a.points, a.comment) return fmt.Sprintf("%d %s %s %d", a.when.Unix(), a.team, a.category, a.points)
}
func pointsLog() []Award {
var ret []Award
fn := statePath("points.log")
f, err := os.Open(fn)
if err != nil {
log.Printf("Unable to open %s: %s", fn, err)
return ret
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
cur, err := ParseAward(line)
if err != nil {
log.Printf("Skipping malformed award line %s: %s", line, err)
continue
}
ret = append(ret, *cur)
}
return ret
} }
// 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() {
pointsLog = os.OpenFile(statePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) logf, err := os.OpenFile(statePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
defer pointsLog.Close() if err != nil {
log.Printf("Can't append to points log: %s", err)
return
}
defer logf.Close()
for f := range allfiles(statePath("points.new")) { files, err := ioutil.ReadDir(statePath("points.new"))
if err != nil {
log.Printf("Error reading packages: %s", err)
}
for _, f := range files {
filename := statePath("points.new", f.Name()) filename := statePath("points.new", f.Name())
s := ioutil.ReadFile(filename) s, err := ioutil.ReadFile(filename)
award, err := ParseAward(s) if err != nil {
if (err != nil) { log.Printf("Can't read points file %s: %s", filename, err)
continue
}
award, err := ParseAward(string(s))
if err != nil {
log.Printf("Can't parse award file %s: %s", filename, err) log.Printf("Can't parse award file %s: %s", filename, err)
continue continue
} }
fmt.Fprintf(pointsLog, "%s\n", award.String()) fmt.Fprintf(logf, "%s\n", award.String())
log.Print(award.String()) log.Print(award.String())
pointsLog.Sync() logf.Sync()
err := os.Remove(filename) if err := os.Remove(filename); err != nil {
if (err != nil) {
log.Printf("Unable to remove %s: %s", filename, err) log.Printf("Unable to remove %s: %s", filename, err)
} }
} }