First pass on integrating PIDs in a more native way

This commit is contained in:
Donaldson 2022-01-26 16:12:20 -08:00
parent b6eea388d9
commit afb3cde38d
3 changed files with 112 additions and 0 deletions

View File

@ -125,6 +125,22 @@ func (h *HTTPServer) RegisterHandler(mh MothRequestHandler, w http.ResponseWrite
} }
} }
// AssignParticipantHandler handles attempts to associate a participant with a team
func (h *HTTPServer) AssignParticipantHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
if mh.participantID == "" {
jsend.Sendf(w, jsend.Fail, "empty name", "Participant ID may not be empty")
return
}
if err := mh.AssignParticipant(); err != ErrAlreadyRegistered {
jsend.Sendf(w, jsend.Success, "already assigned", "participant and team have already been associated")
} else if err != nil {
jsend.Sendf(w, jsend.Fail, "unable to associate participant and team", err.Error())
} else {
jsend.Sendf(w, jsend.Success, "assigned", "participant and team have been associated")
}
}
// AnswerHandler checks answer correctness and awards points // AnswerHandler checks answer correctness and awards points
func (h *HTTPServer) AnswerHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) { func (h *HTTPServer) AnswerHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
cat := req.FormValue("cat") cat := req.FormValue("cat")

View File

@ -57,6 +57,7 @@ type StateProvider interface {
PointsLog() award.List PointsLog() award.List
TeamName(teamID string) (string, error) TeamName(teamID string) (string, error)
SetTeamName(teamID, teamName string) error SetTeamName(teamID, teamName string) error
AssignParticipant(participantID string, teamID string) error
AwardPoints(teamID string, cat string, points int) error AwardPoints(teamID string, cat string, points int) error
LogEvent(event, participantID, teamID, cat string, points int, extra ...string) LogEvent(event, participantID, teamID, cat string, points int, extra ...string)
Maintainer Maintainer
@ -176,6 +177,20 @@ func (mh *MothRequestHandler) Register(teamName string) error {
return mh.State.SetTeamName(mh.teamID, teamName) return mh.State.SetTeamName(mh.teamID, teamName)
} }
// AssignParticipant associates a participant with a team
func (mh *MothRequestHandler) AssignParticipant() error {
if mh.participantID == "" {
return fmt.Errorf("empty participant ID")
}
if mh.teamID == "" {
return fmt.Errorf("empty participant ID")
}
mh.State.LogEvent("assign", mh.participantID, mh.teamID, "", 0)
return mh.State.AssignParticipant(mh.participantID, mh.teamID)
}
// ExportState anonymizes team IDs and returns StateExport. // ExportState anonymizes team IDs and returns StateExport.
// If a teamID has been specified for this MothRequestHandler, // If a teamID has been specified for this MothRequestHandler,
// the anonymized team name for this teamID has the special value "self". // the anonymized team name for this teamID has the special value "self".

View File

@ -46,6 +46,7 @@ type State struct {
// Caches, so we're not hammering NFS with metadata operations // Caches, so we're not hammering NFS with metadata operations
teamNames map[string]string teamNames map[string]string
participantTeams map[string]string
pointsLog award.List pointsLog award.List
messages string messages string
lock sync.RWMutex lock sync.RWMutex
@ -60,6 +61,7 @@ func NewState(fs afero.Fs) *State {
eventStream: make(chan []string, 80), eventStream: make(chan []string, 80),
teamNames: make(map[string]string), teamNames: make(map[string]string),
participantTeams: make(map[string]string),
} }
if err := s.reopenEventLog(); err != nil { if err := s.reopenEventLog(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -175,6 +177,48 @@ func (s *State) SetTeamName(teamID, teamName string) error {
return nil return nil
} }
// AssignParticipant associated a participant with a team
// A participant can only be associated with one team at a time
func (s *State) AssignParticipant(participantID string, teamID string) error {
idsFile, err := s.Open("participantids.txt")
if err != nil {
return fmt.Errorf("participant IDs file does not exist")
}
defer idsFile.Close()
found := false
scanner := bufio.NewScanner(idsFile)
for scanner.Scan() {
if scanner.Text() == participantID {
found = true
break
}
}
if !found {
return fmt.Errorf("participant ID not found in list of valid participant IDs")
}
_, err = s.TeamName(teamID)
if (err != nil) {
return fmt.Errorf("Provided team does not exist, or is not registered")
}
teamParticipantFilename := filepath.Join("participants", participantID)
teamParticipantFile, err := s.Fs.OpenFile(teamParticipantFilename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
if os.IsExist(err) {
return ErrAlreadyRegistered
} else if err != nil {
return err
}
defer teamParticipantFile.Close()
log.Printf("Adding participant [%s] to team [%s]", participantID, teamID)
fmt.Fprintln(teamParticipantFile, teamID)
s.refreshNow <- true
return nil
}
// PointsLog retrieves the current points log. // PointsLog retrieves the current points log.
func (s *State) PointsLog() award.List { func (s *State) PointsLog() award.List {
s.lock.RLock() s.lock.RLock()
@ -308,6 +352,7 @@ func (s *State) maybeInitialize() {
s.RemoveAll("points.tmp") s.RemoveAll("points.tmp")
s.RemoveAll("points.new") s.RemoveAll("points.new")
s.RemoveAll("teams") s.RemoveAll("teams")
s.RemoveAll("participants")
// Open log file // Open log file
if err := s.reopenEventLog(); err != nil { if err := s.reopenEventLog(); err != nil {
@ -319,6 +364,7 @@ func (s *State) maybeInitialize() {
s.Mkdir("points.tmp", 0755) s.Mkdir("points.tmp", 0755)
s.Mkdir("points.new", 0755) s.Mkdir("points.new", 0755)
s.Mkdir("teams", 0755) s.Mkdir("teams", 0755)
s.Mkdir("participants", 0755)
// Preseed available team ids if file doesn't exist // Preseed available team ids if file doesn't exist
if f, err := s.OpenFile("teamids.txt", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644); err == nil { if f, err := s.OpenFile("teamids.txt", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644); err == nil {
@ -333,6 +379,19 @@ func (s *State) maybeInitialize() {
f.Close() f.Close()
} }
// Preseed available participants if file doesn't exist
if f, err := s.OpenFile("participantids.txt", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644); err == nil {
id := make([]byte, 16)
for i := 0; i < 100; i++ {
for i := range id {
char := rand.Intn(len(DistinguishableChars))
id[i] = DistinguishableChars[char]
}
fmt.Fprintln(f, string(id))
}
f.Close()
}
// Create some files // Create some files
if f, err := s.Create("initialized"); err == nil { if f, err := s.Create("initialized"); err == nil {
fmt.Fprintln(f, "initialized: remove to re-initialize the contest.") fmt.Fprintln(f, "initialized: remove to re-initialize the contest.")
@ -451,6 +510,28 @@ func (s *State) updateCaches() {
} }
{
// Update participant records
for k := range s.participantTeams {
delete(s.participantTeams, k)
}
participantsFs := afero.NewBasePathFs(s.Fs, "participants")
if dirents, err := afero.ReadDir(participantsFs, "."); err != nil {
log.Printf("Reading participant ids: %v", err)
} else {
for _, dirent := range dirents {
participantID := dirent.Name()
if participantTeamBytes, err := afero.ReadFile(participantsFs, participantID); err != nil {
log.Printf("Reading participant %s: %v", participantID, err)
} else {
teamID := strings.TrimSpace(string(participantTeamBytes))
s.participantTeams[participantID] = teamID
}
}
}
}
if bMessages, err := afero.ReadFile(s, "messages.html"); err == nil { if bMessages, err := afero.ReadFile(s, "messages.html"); err == nil {
s.messages = string(bMessages) s.messages = string(bMessages)
} }