2022-06-07 14:06:36 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-04-06 01:48:52 +00:00
|
|
|
"encoding/json"
|
2022-06-07 14:06:36 +00:00
|
|
|
"fmt"
|
2022-06-07 15:04:07 +00:00
|
|
|
"log"
|
2024-03-19 10:49:39 +00:00
|
|
|
"math"
|
2022-06-07 14:06:36 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-03-27 20:28:59 +00:00
|
|
|
"sync"
|
2023-04-06 13:37:05 +00:00
|
|
|
"time"
|
2022-06-07 14:06:36 +00:00
|
|
|
|
|
|
|
"srs.epita.fr/fic-server/libfic"
|
|
|
|
)
|
|
|
|
|
2023-04-03 10:04:48 +00:00
|
|
|
var (
|
|
|
|
noValidateChallenge bool
|
|
|
|
)
|
|
|
|
|
2022-06-07 14:06:36 +00:00
|
|
|
type Walker struct {
|
2023-04-06 01:48:52 +00:00
|
|
|
LastSync map[string]*TSValue
|
2024-03-27 20:28:59 +00:00
|
|
|
LastSyncLock sync.RWMutex
|
2023-04-06 01:48:52 +00:00
|
|
|
Exercices AirbusExercicesBindings
|
|
|
|
Teams map[string]fic.ExportedTeam
|
|
|
|
RevTeams map[string]string
|
|
|
|
TeamBindings map[string]*AirbusTeam
|
|
|
|
API AirbusAPI
|
|
|
|
Coeff float64
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
func (w *Walker) fetchTeams() error {
|
|
|
|
teams, err := w.API.GetTeams()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w.RevTeams = map[string]string{}
|
|
|
|
w.TeamBindings = map[string]*AirbusTeam{}
|
|
|
|
|
|
|
|
for tid, team := range w.Teams {
|
2023-11-22 08:55:09 +00:00
|
|
|
for i, t := range teams {
|
2023-04-06 01:48:52 +00:00
|
|
|
if team.Name == t.Name || team.ExternalId == t.Name {
|
2023-11-22 08:55:09 +00:00
|
|
|
w.TeamBindings[tid] = &teams[i]
|
2023-04-06 01:48:52 +00:00
|
|
|
break
|
|
|
|
}
|
2022-06-07 15:04:07 +00:00
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
if _, ok := w.TeamBindings[tid]; !ok {
|
|
|
|
log.Printf("Team binding not found: %s - %s", tid, team.Name)
|
2022-06-07 15:04:07 +00:00
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
w.RevTeams[team.Name] = tid
|
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-06 13:37:05 +00:00
|
|
|
func (w *Walker) treat(path string) error {
|
2023-04-06 01:48:52 +00:00
|
|
|
teamid := filepath.Base(filepath.Dir(path))
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
if _, ok := w.TeamBindings[teamid]; ok {
|
2023-04-06 13:37:05 +00:00
|
|
|
return w.TreatScoreGrid(path, w.TeamBindings[teamid])
|
2023-04-06 01:48:52 +00:00
|
|
|
}
|
2023-04-06 13:37:05 +00:00
|
|
|
|
|
|
|
return nil
|
2023-04-06 01:48:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (w *Walker) LoadScoreState(path string) (int64, error) {
|
|
|
|
mypath := filepath.Join(filepath.Dir(path), "airbus.json")
|
|
|
|
if _, err := os.Stat(mypath); os.IsNotExist(err) {
|
|
|
|
fd, err := os.Create(mypath)
|
2022-06-07 15:04:07 +00:00
|
|
|
if err != nil {
|
2023-04-06 01:48:52 +00:00
|
|
|
return 0, err
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
2023-04-06 01:48:52 +00:00
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
fd.Write([]byte("0"))
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fd, err := os.Open(mypath)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
2023-04-06 01:48:52 +00:00
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
var ret int64
|
|
|
|
|
|
|
|
jdec := json.NewDecoder(fd)
|
|
|
|
if err := jdec.Decode(&ret); err != nil {
|
|
|
|
return 0, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *Walker) LoadScoreGrid(path string) ([]fic.ScoreGridRow, error) {
|
|
|
|
fd, err := os.Open(filepath.Join(filepath.Dir(path), "scores.json"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
var ret []fic.ScoreGridRow
|
|
|
|
|
|
|
|
jdec := json.NewDecoder(fd)
|
|
|
|
if err := jdec.Decode(&ret); err != nil {
|
|
|
|
return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
2022-06-07 15:04:07 +00:00
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2022-06-07 22:09:49 +00:00
|
|
|
func (w *Walker) WalkScoreSync(path string, d os.DirEntry, err error) error {
|
|
|
|
if filepath.Base(path) == "scores.json" {
|
2023-04-06 13:37:05 +00:00
|
|
|
return w.treat(path)
|
2023-04-06 01:48:52 +00:00
|
|
|
}
|
|
|
|
|
2022-06-07 22:09:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
func (w *Walker) loadMyFile(path string) (*fic.MyTeam, error) {
|
|
|
|
fd, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
var ret fic.MyTeam
|
|
|
|
|
|
|
|
jdec := json.NewDecoder(fd)
|
|
|
|
if err := jdec.Decode(&ret); err != nil {
|
|
|
|
return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ret, nil
|
|
|
|
}
|
|
|
|
|
2022-06-07 15:04:07 +00:00
|
|
|
func (w *Walker) WalkScore(path string, d os.DirEntry, err error) error {
|
|
|
|
if filepath.Base(path) == "scores.json" {
|
|
|
|
go w.treat(path)
|
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-06 01:48:52 +00:00
|
|
|
func (w *Walker) TreatScoreGrid(path string, airbusTeam *AirbusTeam) error {
|
2022-06-07 14:06:36 +00:00
|
|
|
// Read score grid
|
|
|
|
fdscores, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fdscores.Close()
|
|
|
|
|
|
|
|
teamscores, err := fic.ReadScoreGrid(fdscores)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Found all new entries
|
2024-03-19 10:49:39 +00:00
|
|
|
maxts := TSValue{
|
2023-04-06 13:37:05 +00:00
|
|
|
Time: time.Time{},
|
|
|
|
}
|
2024-03-27 20:28:59 +00:00
|
|
|
w.LastSyncLock.RLock()
|
2024-03-19 10:49:39 +00:00
|
|
|
ts, ok := w.LastSync[airbusTeam.Name]
|
2024-03-27 20:28:59 +00:00
|
|
|
w.LastSyncLock.RUnlock()
|
2024-03-19 10:49:39 +00:00
|
|
|
if ok {
|
|
|
|
maxts = *ts
|
|
|
|
} else {
|
|
|
|
ts = &TSValue{Time: time.Time{}}
|
2023-04-06 01:48:52 +00:00
|
|
|
}
|
2024-03-19 13:00:12 +00:00
|
|
|
var expected_score float64
|
2022-06-07 14:06:36 +00:00
|
|
|
for _, row := range teamscores {
|
2024-03-19 13:00:12 +00:00
|
|
|
expected_score += row.Points * row.Coeff * w.Coeff
|
2024-03-19 10:49:39 +00:00
|
|
|
if row.Time.After(ts.Time) {
|
2023-04-03 10:04:48 +00:00
|
|
|
if !noValidateChallenge && row.Reason == "Validation" {
|
2023-04-06 01:48:52 +00:00
|
|
|
err = w.API.ValidateChallengeFromUser(airbusTeam, w.Exercices[row.IdExercice])
|
2024-03-19 12:58:37 +00:00
|
|
|
} else if row.Reason == "Tries" {
|
|
|
|
// Just add 1 try at a time as the field is updated
|
|
|
|
row.Points = fic.TermTriesSeq(fic.ReverseTriesPoints(int64(row.Points)))
|
|
|
|
err = w.API.AwardUser(airbusTeam, int64(math.Trunc(row.Points*row.Coeff*w.Coeff)), row.Reason)
|
2022-06-07 14:06:36 +00:00
|
|
|
} else {
|
2023-04-06 01:48:52 +00:00
|
|
|
err = w.API.AwardUser(airbusTeam, int64(row.Points*row.Coeff*w.Coeff), row.Reason)
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-04-06 01:48:52 +00:00
|
|
|
|
2024-03-19 13:00:12 +00:00
|
|
|
maxts.Score += int64(math.Trunc(row.Points * row.Coeff * w.Coeff))
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
2024-03-19 10:49:39 +00:00
|
|
|
if row.Time.After(maxts.Time) {
|
|
|
|
maxts.Time = row.Time
|
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
|
|
|
|
2024-03-19 13:00:12 +00:00
|
|
|
if int64(math.Trunc(expected_score)) != maxts.Score {
|
|
|
|
log.Printf("Team %q need balancing: expected: %f, saved: %d", airbusTeam.Name, expected_score, maxts.Score)
|
|
|
|
err = w.API.AwardUser(airbusTeam, int64(math.Trunc(expected_score))-maxts.Score, "Balancing")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to balance score for %q: %w", airbusTeam.Name, err)
|
|
|
|
}
|
|
|
|
maxts.Score = int64(math.Trunc(expected_score))
|
|
|
|
}
|
|
|
|
|
2024-03-27 20:28:59 +00:00
|
|
|
w.LastSyncLock.Lock()
|
2024-03-19 10:49:39 +00:00
|
|
|
w.LastSync[airbusTeam.Name] = &maxts
|
2024-03-27 20:28:59 +00:00
|
|
|
w.LastSyncLock.Unlock()
|
2022-06-07 15:04:07 +00:00
|
|
|
|
2022-06-07 14:06:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-06 13:37:05 +00:00
|
|
|
func (w *Walker) BalanceScores() error {
|
2024-03-19 13:00:12 +00:00
|
|
|
for team_id := range w.Teams {
|
2023-04-06 13:37:05 +00:00
|
|
|
myteam, err := w.loadMyFile(filepath.Join(TeamsDir, team_id, "my.json"))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to open %s/my.json: %w", team_id, err)
|
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2023-04-06 13:37:05 +00:00
|
|
|
airbusTeam := w.TeamBindings[fmt.Sprintf("%d", myteam.Id)]
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2024-03-19 12:59:28 +00:00
|
|
|
expected_score := int64(math.Floor(float64(myteam.Points100) * w.Coeff / 100))
|
2024-03-19 10:49:39 +00:00
|
|
|
if airbusTeam == nil {
|
|
|
|
log.Printf("Skip team %q (tid=%d): no binding found", myteam.Name, myteam.Id)
|
|
|
|
} else if airbusTeam.Score != expected_score {
|
2024-03-19 13:00:12 +00:00
|
|
|
err := w.API.AwardUser(airbusTeam, expected_score-airbusTeam.Score, "Balancing")
|
2023-04-06 13:37:05 +00:00
|
|
|
if err != nil {
|
2024-03-19 13:00:12 +00:00
|
|
|
return fmt.Errorf("Unable to award team %s: %w", myteam.Name, err)
|
2023-04-06 13:37:05 +00:00
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
|
2024-03-27 20:28:59 +00:00
|
|
|
w.LastSyncLock.Lock()
|
2024-03-19 10:49:39 +00:00
|
|
|
if _, ok := w.LastSync[airbusTeam.Name]; !ok {
|
|
|
|
w.LastSync[airbusTeam.Name] = &TSValue{}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.LastSync[airbusTeam.Name].Score = expected_score
|
2023-04-06 13:37:05 +00:00
|
|
|
w.LastSync[airbusTeam.Name].Time = time.Now()
|
2024-03-27 20:28:59 +00:00
|
|
|
w.LastSyncLock.Unlock()
|
2023-04-06 13:37:05 +00:00
|
|
|
}
|
2022-06-07 14:06:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|