mirror of https://github.com/dirtbags/moth.git
First pass on integrating PIDs in a more native way
This commit is contained in:
parent
b6eea388d9
commit
afb3cde38d
|
@ -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")
|
||||||
|
|
|
@ -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".
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue