package main import ( "encoding/json" "fmt" "log" "math" "os" "path/filepath" "sync" "time" "srs.epita.fr/fic-server/libfic" ) var ( noValidateChallenge bool ) type Walker struct { LastSync map[string]*TSValue LastSyncLock sync.RWMutex Exercices AirbusExercicesBindings Teams map[string]fic.ExportedTeam RevTeams map[string]string TeamBindings map[string]*AirbusTeam API AirbusAPI Coeff float64 } 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 { for i, t := range teams { if team.Name == t.Name || team.ExternalId == t.Name { w.TeamBindings[tid] = &teams[i] break } } if _, ok := w.TeamBindings[tid]; !ok { log.Printf("Team binding not found: %s - %s", tid, team.Name) } w.RevTeams[team.Name] = tid } return nil } func (w *Walker) treat(path string) error { teamid := filepath.Base(filepath.Dir(path)) if _, ok := w.TeamBindings[teamid]; ok { return w.TreatScoreGrid(path, w.TeamBindings[teamid]) } return nil } 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) if err != nil { return 0, err } defer fd.Close() fd.Write([]byte("0")) return 0, nil } fd, err := os.Open(mypath) if err != nil { return 0, err } 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 } func (w *Walker) WalkScoreSync(path string, d os.DirEntry, err error) error { if filepath.Base(path) == "scores.json" { return w.treat(path) } return nil } 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 } func (w *Walker) WalkScore(path string, d os.DirEntry, err error) error { if filepath.Base(path) == "scores.json" { go w.treat(path) } return nil } func (w *Walker) TreatScoreGrid(path string, airbusTeam *AirbusTeam) error { // 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 maxts := TSValue{ Time: time.Time{}, } w.LastSyncLock.RLock() ts, ok := w.LastSync[airbusTeam.Name] w.LastSyncLock.RUnlock() if ok { maxts = *ts } else { ts = &TSValue{Time: time.Time{}} } var expected_score float64 for _, row := range teamscores { expected_score += row.Points * row.Coeff * w.Coeff if row.Time.After(ts.Time) { if !noValidateChallenge && row.Reason == "Validation" { err = w.API.ValidateChallengeFromUser(airbusTeam, w.Exercices[row.IdExercice]) } 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) } else { err = w.API.AwardUser(airbusTeam, int64(row.Points*row.Coeff*w.Coeff), row.Reason) } if err != nil { return err } maxts.Score += int64(math.Trunc(row.Points * row.Coeff * w.Coeff)) } if row.Time.After(maxts.Time) { maxts.Time = row.Time } } 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)) } w.LastSyncLock.Lock() w.LastSync[airbusTeam.Name] = &maxts w.LastSyncLock.Unlock() return nil } func (w *Walker) BalanceScores() error { for team_id := range w.Teams { 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) } airbusTeam := w.TeamBindings[fmt.Sprintf("%d", myteam.Id)] expected_score := int64(math.Floor(float64(myteam.Points100) * w.Coeff / 100)) if airbusTeam == nil { log.Printf("Skip team %q (tid=%d): no binding found", myteam.Name, myteam.Id) } else if airbusTeam.Score != expected_score { err := w.API.AwardUser(airbusTeam, expected_score-airbusTeam.Score, "Balancing") if err != nil { return fmt.Errorf("Unable to award team %s: %w", myteam.Name, err) } w.LastSyncLock.Lock() if _, ok := w.LastSync[airbusTeam.Name]; !ok { w.LastSync[airbusTeam.Name] = &TSValue{} } w.LastSync[airbusTeam.Name].Score = expected_score w.LastSync[airbusTeam.Name].Time = time.Now() w.LastSyncLock.Unlock() } } return nil }