mirror of https://github.com/dirtbags/moth.git
Check for duplicate points
This commit is contained in:
parent
78f2e2a79c
commit
4c3fe34936
44
src/award.go
44
src/award.go
|
@ -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
|
|
||||||
}
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
fmt.Fprintf(logf, "%s\n", award.String())
|
||||||
log.Print("XXX: check for duplicates", 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)
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
Loading…
Reference in New Issue