Check for duplicate points

This commit is contained in:
Neale Pickett 2018-09-18 00:02:44 +00:00
parent 8b1441a591
commit f914f1d65d
5 changed files with 93 additions and 52 deletions

View File

@ -14,6 +14,25 @@ type Award struct {
Points int Points int
} }
func ParseAward(s string) (*Award, error) {
ret := Award{}
s = strings.Trim(s, " \t\n")
var whenEpoch int64
n, err := fmt.Sscanf(s, "%d %s %s %d", &whenEpoch, &ret.TeamId, &ret.Category, &ret.Points)
if err != nil {
return nil, err
} else if n != 4 {
return nil, fmt.Errorf("Malformed award string: only parsed %d fields", n)
}
ret.When = time.Unix(whenEpoch, 0)
return &ret, nil
}
func (a *Award) String() string { func (a *Award) String() string {
return fmt.Sprintf("%d %s %s %d", a.When.Unix(), a.TeamId, a.Category, a.Points) return fmt.Sprintf("%d %s %s %d", a.When.Unix(), a.TeamId, a.Category, a.Points)
} }
@ -40,21 +59,14 @@ func (a *Award) MarshalJSON() ([]byte, error) {
return []byte(ret), nil return []byte(ret), nil
} }
func ParseAward(s string) (*Award, error) { func (a *Award) Same(o *Award) bool {
ret := Award{} switch {
case a.TeamId != o.TeamId:
s = strings.Trim(s, " \t\n") return false
case a.Category != o.Category:
var whenEpoch int64 return false
case a.Points != o.Points:
n, err := fmt.Sscanf(s, "%d %s %s %d", &whenEpoch, &ret.TeamId, &ret.Category, &ret.Points) return false
if err != nil {
return nil, err
} else if n != 4 {
return nil, fmt.Errorf("Malformed award string: only parsed %d fields", n)
} }
return true
ret.When = time.Unix(whenEpoch, 0)
return &ret, nil
} }

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -22,6 +23,32 @@ func respond(w http.ResponseWriter, req *http.Request, status Status, short stri
} }
} }
// anchoredSearch looks for needle in r,
// skipping the first skip space-delimited words
func anchoredSearch(r io.Reader, needle string, skip int) bool {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, " ", skip+1)
if (len(parts) > skip) && (parts[skip] == needle) {
return true
}
}
return false
}
// anchoredSearchFile performs an anchoredSearch on a given filename
func anchoredSearchFile(filename string, needle string, skip int) bool {
r, err := os.Open(filename)
if err != nil {
return false
}
defer r.Close()
return anchoredSearch(r, needle, skip)
}
func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) { func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) {
teamname := req.FormValue("name") teamname := req.FormValue("name")
teamid := req.FormValue("id") teamid := req.FormValue("id")
@ -172,7 +199,7 @@ func (ctx Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
return return
} }
if err := ctx.AwardPoints(teamid, category, points); err != nil { if err := ctx.AwardPointsUniquely(teamid, category, points); err != nil {
respond( respond(
w, req, Error, w, req, Error,
"Error awarding points", "Error awarding points",

View File

@ -89,8 +89,8 @@ func (ctx *Instance) StatePath(parts ...string) string {
return path.Join(ctx.StateDir, tail) return path.Join(ctx.StateDir, tail)
} }
func (ctx *Instance) PointsLog() []Award { func (ctx *Instance) PointsLog() []*Award {
var ret []Award var ret []*Award
fn := ctx.StatePath("points.log") fn := ctx.StatePath("points.log")
f, err := os.Open(fn) f, err := os.Open(fn)
@ -108,14 +108,14 @@ func (ctx *Instance) PointsLog() []Award {
log.Printf("Skipping malformed award line %s: %s", line, err) log.Printf("Skipping malformed award line %s: %s", line, err)
continue continue
} }
ret = append(ret, *cur) ret = append(ret, cur)
} }
return ret return ret
} }
// awardPoints gives points points to team teamid in category category // awardPoints gives points points to team teamid in category category
func (ctx *Instance) AwardPoints(teamid string, category string, points int) error { func (ctx *Instance) AwardPoints(teamid, category string, points int) error {
fn := fmt.Sprintf("%s-%s-%d", teamid, category, points) fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
tmpfn := ctx.StatePath("points.tmp", fn) tmpfn := ctx.StatePath("points.tmp", fn)
newfn := ctx.StatePath("points.new", fn) newfn := ctx.StatePath("points.new", fn)
@ -134,6 +134,23 @@ func (ctx *Instance) AwardPoints(teamid string, category string, points int) err
return nil return nil
} }
func (ctx *Instance) AwardPointsUniquely(teamid, category string, points int) error {
a := Award{
When: time.Now(),
TeamId: teamid,
Category: category,
Points: points,
}
for _, e := range ctx.PointsLog() {
if a.Same(e) {
return fmt.Errorf("Points already awarded to this team in this category")
}
}
return ctx.AwardPoints(teamid, category, points)
}
func (ctx *Instance) OpenCategoryFile(category string, parts ...string) (io.ReadCloser, error) { func (ctx *Instance) OpenCategoryFile(category string, parts ...string) (io.ReadCloser, error) {
mb, ok := ctx.Categories[category] mb, ok := ctx.Categories[category]
if !ok { if !ok {

View File

@ -90,8 +90,21 @@ func (ctx *Instance) CollectPoints() {
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(logf, "%s\n", award.String())
log.Print("XXX: check for duplicates", award.String()) duplicate := false
for _, e := range ctx.PointsLog() {
if award.Same(e) {
duplicate = true
break
}
}
if duplicate {
log.Printf("Skipping duplicate points: %s", award.String())
} else {
fmt.Fprintf(logf, "%s\n", award.String())
}
logf.Sync() logf.Sync()
if err := os.Remove(filename); err != nil { if err := os.Remove(filename); err != nil {
log.Printf("Unable to remove %s: %s", filename, err) log.Printf("Unable to remove %s: %s", filename, err)

View File

@ -1,42 +1,14 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
// anchoredSearch looks for needle in r,
// skipping the first skip space-delimited words
func anchoredSearch(r io.Reader, needle string, skip int) bool {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, " ", skip+1)
if (len(parts) > skip) && (parts[skip] == needle) {
return true
}
}
return false
}
// anchoredSearchFile performs an anchoredSearch on a given filename
func anchoredSearchFile(filename string, needle string, skip int) bool {
r, err := os.Open(filename)
if err != nil {
return false
}
defer r.Close()
return anchoredSearch(r, needle, skip)
}
type Status int type Status int
const ( const (