Force points.log to be sorted chronologically

This commit is contained in:
John Donaldson 2020-03-02 19:23:51 +00:00
parent f3beab4e89
commit d0ccdd2a72
3 changed files with 64 additions and 1 deletions

View File

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Handle cases where non-legacy puzzles don't have an `author` attribute - Handle cases where non-legacy puzzles don't have an `author` attribute
- Handle YAML-formatted file and script lists as expected - Handle YAML-formatted file and script lists as expected
- YAML-formatted example puzzle actually works as expected - YAML-formatted example puzzle actually works as expected
- points.log will now always be sorted chronologically
## [3.4.3] - 2019-11-20 ## [3.4.3] - 2019-11-20
### Fixed ### Fixed

View File

@ -36,6 +36,7 @@ type Instance struct {
nextAttempt map[string]time.Time nextAttempt map[string]time.Time
nextAttemptMutex *sync.RWMutex nextAttemptMutex *sync.RWMutex
mux *http.ServeMux mux *http.ServeMux
PointsMux *sync.RWMutex
} }
func (ctx *Instance) Initialize() error { func (ctx *Instance) Initialize() error {
@ -53,6 +54,7 @@ func (ctx *Instance) Initialize() error {
ctx.nextAttempt = map[string]time.Time{} ctx.nextAttempt = map[string]time.Time{}
ctx.nextAttemptMutex = new(sync.RWMutex) ctx.nextAttemptMutex = new(sync.RWMutex)
ctx.mux = http.NewServeMux() ctx.mux = http.NewServeMux()
ctx.PointsMux = new(sync.RWMutex)
ctx.BindHandlers() ctx.BindHandlers()
ctx.MaybeInitialize() ctx.MaybeInitialize()
@ -82,7 +84,11 @@ func (ctx *Instance) MaybeInitialize() {
// Remove any extant control and state files // Remove any extant control and state files
os.Remove(ctx.StatePath("until")) os.Remove(ctx.StatePath("until"))
os.Remove(ctx.StatePath("disabled")) os.Remove(ctx.StatePath("disabled"))
ctx.PointsMux.Lock()
os.Remove(ctx.StatePath("points.log")) os.Remove(ctx.StatePath("points.log"))
ctx.PointsMux.Unlock()
os.RemoveAll(ctx.StatePath("points.tmp")) os.RemoveAll(ctx.StatePath("points.tmp"))
os.RemoveAll(ctx.StatePath("points.new")) os.RemoveAll(ctx.StatePath("points.new"))
os.RemoveAll(ctx.StatePath("teams")) os.RemoveAll(ctx.StatePath("teams"))
@ -155,6 +161,10 @@ func (ctx *Instance) PointsLog(teamId string) []*Award {
var ret []*Award var ret []*Award
fn := ctx.StatePath("points.log") fn := ctx.StatePath("points.log")
ctx.PointsMux.RLock()
defer ctx.PointsMux.RUnlock()
f, err := os.Open(fn) f, err := os.Open(fn)
if err != nil { if err != nil {
log.Printf("Unable to open %s: %s", fn, err) log.Printf("Unable to open %s: %s", fn, err)

View File

@ -4,9 +4,11 @@ import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -95,7 +97,7 @@ func (ctx *Instance) generatePointsLog(teamId string) []byte {
log.Printf("Marshalling points.js: %v", err) log.Printf("Marshalling points.js: %v", err)
return nil return nil
} }
if len(teamId) == 0 { if len(teamId) == 0 {
ctx.jPointsLog = jpl ctx.jPointsLog = jpl
} }
@ -203,6 +205,9 @@ func (ctx *Instance) readTeams() {
// 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 (ctx *Instance) collectPoints() { func (ctx *Instance) collectPoints() {
ctx.PointsMux.Lock()
defer ctx.PointsMux.Unlock()
logf, err := os.OpenFile(ctx.StatePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) logf, err := os.OpenFile(ctx.StatePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Printf("Can't append to points log: %s", err) log.Printf("Can't append to points log: %s", err)
@ -248,6 +253,52 @@ func (ctx *Instance) collectPoints() {
} }
} }
// Ensure that points.log is sorted chronologically
func (ctx *Instance) sortPoints() {
var points []*Award
ctx.PointsMux.Lock()
defer ctx.PointsMux.Unlock()
logf, err := os.OpenFile(ctx.StatePath("points.log"), os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
log.Printf("Can't sort points.log: %s", err)
return
}
defer logf.Close()
scanner := bufio.NewScanner(logf)
for scanner.Scan() {
line := scanner.Text()
cur, err := ParseAward(line)
if err != nil {
log.Printf("Encountered malformed award line, not sorting %s: %s", line, err)
return
}
points = append(points, cur)
}
// Only sort and write to file if we need to
if ! sort.SliceIsSorted(points, func( i, j int) bool { return points[i].When.Before(points[j].When) }) {
sort.SliceStable(points, func(i, j int) bool { return points[i].When.Before(points[j].When) })
fmt.Println("By time: \n")
for i := range points {
fmt.Println(points[i])
}
logf.Seek(0, io.SeekStart)
for i := range points {
point := points[i]
fmt.Fprintf(logf, "%s\n", point.String())
}
}
}
func (ctx *Instance) isEnabled() bool { func (ctx *Instance) isEnabled() bool {
// Skip if we've been disabled // Skip if we've been disabled
if _, err := os.Stat(ctx.StatePath("disabled")); err == nil { if _, err := os.Stat(ctx.StatePath("disabled")); err == nil {
@ -293,6 +344,7 @@ func (ctx *Instance) Maintenance(maintenanceInterval time.Duration) {
ctx.tidy() ctx.tidy()
ctx.readTeams() ctx.readTeams()
ctx.collectPoints() ctx.collectPoints()
ctx.sortPoints()
ctx.generatePuzzleList() ctx.generatePuzzleList()
ctx.generatePointsLog("") ctx.generatePointsLog("")
} }