package main import ( "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "os" "path" "runtime" "sync" "time" "srs.epita.fr/fic-server/libfic" ) var parallelJobs = runtime.NumCPU() var genQueue chan *fic.GenStruct var inQueueMutex sync.RWMutex var inGenQueue map[fic.GenerateType]bool func init() { genQueue = make(chan *fic.GenStruct) inGenQueue = map[fic.GenerateType]bool{} } func launchWorkers() { log.Println("Running with", parallelJobs, "worker(s)") for i := parallelJobs; i > 0; i-- { go consumer() } } func enqueueHandler(w http.ResponseWriter, r *http.Request) { var gs fic.GenStruct dec := json.NewDecoder(r.Body) err := dec.Decode(&gs) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } log.Printf("POST /enqueue | %v", gs) appendGenQueue(gs) http.Error(w, "OK", http.StatusOK) } func performHandler(w http.ResponseWriter, r *http.Request) { var gs fic.GenStruct dec := json.NewDecoder(r.Body) err := dec.Decode(&gs) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } log.Printf("POST /perform | %v", gs) err = <-appendGenQueue(gs).GenEnded() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { http.Error(w, "OK", http.StatusOK) } } func performFullResyncHandler(w http.ResponseWriter, r *http.Request) { log.Printf("POST /full-resync started") waitList := genAll() var errs string for _, e := range waitList { err := <-e if err != nil { errs += err.Error() + "\n" } } if errs != "" { lastRegeneration = time.Now() http.Error(w, errs, http.StatusInternalServerError) } else { http.Error(w, "done", http.StatusOK) } log.Printf("POST /full-resync done") } func appendGenQueue(gs fic.GenStruct) *fic.GenStruct { if gs.Type == fic.GenTeam || gs.Type == fic.GenTeamIssues { genQueue <- &gs return &gs } // Append only if not already in queue inQueueMutex.RLock() if v, ok := inGenQueue[gs.Type]; !ok || !v { inQueueMutex.RUnlock() inQueueMutex.Lock() inGenQueue[gs.Type] = true inQueueMutex.Unlock() genQueue <- &gs } else { inQueueMutex.RUnlock() } return &gs } func consumer() { var id string var err error for { gs := <-genQueue id = gs.Id inQueueMutex.Lock() inGenQueue[gs.Type] = false inQueueMutex.Unlock() switch gs.Type { case fic.GenPublic: err = genMyPublicFile() case fic.GenEvents: err = genEventsFile() case fic.GenTeam: err = genTeamMyFile(gs.TeamId) case fic.GenTeams: err = genTeamsFile() case fic.GenThemes: err = genThemesFile() case fic.GenTeamIssues: err = genTeamIssuesFile(gs.TeamId) } if err != nil { log.Println(id, "[ERR] Unable to generate:", err) } gs.End(err) } } // Generate issues.json for a given team func genTeamIssuesFile(teamid int64) error { team, err := fic.GetTeam(teamid) if err != nil { return fmt.Errorf("Unable to GetTeam: %w", err) } dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id)) my, err := team.MyIssueFile() if err != nil { return err } if my == nil { if _, err := os.Stat(path.Join(dirPath, "issues.json")); !os.IsNotExist(err) { err = os.Remove(path.Join(dirPath, "issues.json")) if err != nil { log.Printf("Unable to remove empty issues file: %s", path.Join(dirPath, "issues.json")) } } return nil } if s, err := os.Stat(dirPath); os.IsNotExist(err) { os.MkdirAll(dirPath, 0751) } else if !s.IsDir() { return fmt.Errorf("%s is not a directory", dirPath) } if j, err := json.Marshal(my); err != nil { return err } else if err = ioutil.WriteFile(path.Join(dirPath, "issues.json"), j, 0644); err != nil { return err } return nil } // Generate my.json, wait.json and scores.json for a given team func genTeamMyFile(teamid int64) error { team, err := fic.GetTeam(teamid) if err != nil { return fmt.Errorf("Unable to GetTeam: %w", err) } dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id)) if s, err := os.Stat(dirPath); os.IsNotExist(err) { os.MkdirAll(dirPath, 0751) } else if !s.IsDir() { return fmt.Errorf("%s is not a directory", dirPath) } if my, err := fic.MyJSONTeam(team, true); err != nil { return err } else if j, err := json.Marshal(my); err != nil { return err } else if err = ioutil.WriteFile(path.Join(dirPath, "my.json"), j, 0666); err != nil { return err } // Speed up generation when challenge is started if !ChStarted { if my, err := fic.MyJSONTeam(team, false); err != nil { return err } else if j, err := json.Marshal(my); err != nil { return err } else if err = ioutil.WriteFile(path.Join(dirPath, "wait.json"), j, 0666); err != nil { return err } } else { if scores, err := team.ScoreGrid(); err != nil { return err } else if j, err := json.Marshal(scores); err != nil { return err } else if err = ioutil.WriteFile(path.Join(dirPath, "scores.json"), j, 0666); err != nil { return err } } return nil } // Generate public my.json file func genMyPublicFile() error { dirPath := path.Join(TeamsDir, "public") if s, err := os.Stat(dirPath); os.IsNotExist(err) { os.MkdirAll(dirPath, 0751) } else if !s.IsDir() { return fmt.Errorf("%s is not a directory", dirPath) } if my, err := fic.MyJSONTeam(nil, true); err != nil { return err } else if j, err := json.Marshal(my); err != nil { return err } else if err = ioutil.WriteFile(path.Join(dirPath, "my.json"), j, 0666); err != nil { return err } os.Symlink("my.json", path.Join(dirPath, "wait.json")) if teams, err := fic.ExportTeams(true); err != nil { return err } else if j, err := json.Marshal(teams); err != nil { return err } else if err = ioutil.WriteFile(path.Join(dirPath, "teams.json"), j, 0666); err != nil { return err } return nil } // Generate general evemts.json file func genEventsFile() error { if evts, err := fic.GetLastEvents(); err != nil { return err } else if j, err := json.Marshal(evts); err != nil { return err } else if err = ioutil.WriteFile(path.Join(TeamsDir, "events.json"), j, 0666); err != nil { return err } return nil } // Generate general teams.json file func genTeamsFile() error { if teams, err := fic.ExportTeams(false); err != nil { return err } else if j, err := json.Marshal(teams); err != nil { return err } else if err = ioutil.WriteFile(path.Join(TeamsDir, "teams.json"), j, 0666); err != nil { return err } if teams, err := fic.ExportTeams(true); err != nil { return err } else if j, err := json.Marshal(teams); err != nil { return err } else if err = ioutil.WriteFile(path.Join(TeamsDir, "public", "teams.json"), j, 0666); err != nil { return err } return nil } // Generate general themes.json file func genThemesFile() error { themes, err := fic.ExportThemes() if err != nil { return fmt.Errorf("unable to generate themes: %w", err) } var wr io.Writer themesfd, err := os.Create(path.Join(TeamsDir, "themes.json")) if err != nil { return fmt.Errorf("unable to re-create themes.json: %w", err) } defer themesfd.Close() themeswait, err := os.Create(path.Join(TeamsDir, "themes-wait.json")) if err != nil { return fmt.Errorf("unable to re-create themes-wait.json: %w", err) } defer themeswait.Close() if allowRegistration { wr = io.MultiWriter(themesfd, themeswait) } else { wr = themesfd } enc := json.NewEncoder(wr) err = enc.Encode(themes) if err != nil { return fmt.Errorf("unable to encode themes.json: %w", err) } if !allowRegistration { enc = json.NewEncoder(themeswait) err = enc.Encode(map[string]string{}) if err != nil { return fmt.Errorf("unable to encode themes-wait.json: %w", err) } } return nil } func genAll() (waitList []chan error) { waitList = append( waitList, appendGenQueue(fic.GenStruct{Type: fic.GenThemes}).GenEnded(), appendGenQueue(fic.GenStruct{Type: fic.GenTeams}).GenEnded(), appendGenQueue(fic.GenStruct{Type: fic.GenEvents}).GenEnded(), appendGenQueue(fic.GenStruct{Type: fic.GenPublic}).GenEnded(), ) if teams, err := fic.GetActiveTeams(); err != nil { log.Println("Team retrieval error: ", err) } else { for _, team := range teams { waitList = append( waitList, appendGenQueue(fic.GenStruct{Type: fic.GenTeam, TeamId: team.Id}).GenEnded(), appendGenQueue(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id}).GenEnded(), ) } } return }