State: add "Enabeled", remove "Messages"

Fixes #164
This commit is contained in:
Neale Pickett 2023-09-29 15:37:18 -06:00
parent 077dc261e4
commit 79799bf1c2
9 changed files with 31 additions and 52 deletions

View File

@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reworked the built-in theme - Reworked the built-in theme
- [moth.mjs](theme/moth.mjs) is now the standard MOTH library for ECMAScript - [moth.mjs](theme/moth.mjs) is now the standard MOTH library for ECMAScript
- Devel mode no longer accepts an empty team ID - Devel mode no longer accepts an empty team ID
- messages.html moved into theme
- Exported state now includes "Enabled" boolean
## [v4.4.9] - 2022-05-12 ## [v4.4.9] - 2022-05-12
### Changed ### Changed

View File

@ -45,7 +45,7 @@ func TestHttpd(t *testing.T) {
if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 { if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 {
t.Error(r.Result()) t.Error(r.Result())
} else if r.Body.String() != `{"Config":{"Devel":false},"Messages":"messages.html","TeamNames":{},"PointsLog":[],"Puzzles":{}}` { } else if r.Body.String() != `{"Config":{"Devel":false},"TeamNames":{},"PointsLog":[],"Puzzles":{}}` {
t.Error("Unexpected state", r.Body.String()) t.Error("Unexpected state", r.Body.String())
} }
@ -71,7 +71,7 @@ func TestHttpd(t *testing.T) {
if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 { if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 {
t.Error(r.Result()) t.Error(r.Result())
} else if r.Body.String() != `{"Config":{"Devel":false},"Messages":"messages.html","TeamNames":{"self":"GoTeam"},"PointsLog":[],"Puzzles":{"pategory":[1]}}` { } else if r.Body.String() != `{"Config":{"Devel":false},"TeamNames":{"self":"GoTeam"},"PointsLog":[],"Puzzles":{"pategory":[1]}}` {
t.Error("Unexpected state", r.Body.String()) t.Error("Unexpected state", r.Body.String())
} }

View File

@ -30,7 +30,7 @@ type Configuration struct {
// StateExport is given to clients requesting the current state. // StateExport is given to clients requesting the current state.
type StateExport struct { type StateExport struct {
Config Configuration Config Configuration
Messages string Enabled bool
TeamNames map[string]string TeamNames map[string]string
PointsLog award.List PointsLog award.List
Puzzles map[string][]int Puzzles map[string][]int
@ -53,7 +53,7 @@ type ThemeProvider interface {
// StateProvider defines what's required to provide MOTH state. // StateProvider defines what's required to provide MOTH state.
type StateProvider interface { type StateProvider interface {
Messages() string Enabled() bool
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
@ -194,7 +194,7 @@ func (mh *MothRequestHandler) exportStateIfRegistered(forceRegistered bool) *Sta
teamName, err := mh.State.TeamName(mh.teamID) teamName, err := mh.State.TeamName(mh.teamID)
registered := forceRegistered || mh.Config.Devel || (err == nil) registered := forceRegistered || mh.Config.Devel || (err == nil)
export.Messages = mh.State.Messages() export.Enabled = mh.State.Enabled()
export.TeamNames = make(map[string]string) export.TeamNames = make(map[string]string)
// Anonymize team IDs in points log, and write out team names // Anonymize team IDs in points log, and write out team names

View File

@ -22,7 +22,6 @@ func NewTestServer() TestServer {
state := NewTestState() state := NewTestState()
afero.WriteFile(state, "teamids.txt", []byte("teamID\n"), 0644) afero.WriteFile(state, "teamids.txt", []byte("teamID\n"), 0644)
afero.WriteFile(state, "messages.html", []byte("messages.html"), 0644)
state.refresh() state.refresh()
theme := NewTestTheme() theme := NewTestTheme()
@ -101,9 +100,6 @@ func TestProdServer(t *testing.T) {
if len(es.Puzzles) != 1 { if len(es.Puzzles) != 1 {
t.Error("Puzzle categories wrong length", len(es.Puzzles)) t.Error("Puzzle categories wrong length", len(es.Puzzles))
} }
if es.Messages != "messages.html" {
t.Error("Messages has wrong contents")
}
if len(es.PointsLog) != 0 { if len(es.PointsLog) != 0 {
t.Error("Points log not empty") t.Error("Points log not empty")
} }

View File

@ -37,7 +37,7 @@ type State struct {
afero.Fs afero.Fs
// Enabled tracks whether the current State system is processing updates // Enabled tracks whether the current State system is processing updates
Enabled bool enabled bool
enabledWhy string enabledWhy string
refreshNow chan bool refreshNow chan bool
@ -49,7 +49,6 @@ type State struct {
teamNamesLastChange time.Time teamNamesLastChange time.Time
teamNames map[string]string teamNames map[string]string
pointsLog award.List pointsLog award.List
messages string
lock sync.RWMutex lock sync.RWMutex
} }
@ -57,7 +56,7 @@ type State struct {
func NewState(fs afero.Fs) *State { func NewState(fs afero.Fs) *State {
s := &State{ s := &State{
Fs: fs, Fs: fs,
Enabled: true, enabled: true,
refreshNow: make(chan bool, 5), refreshNow: make(chan bool, 5),
eventStream: make(chan []string, 80), eventStream: make(chan []string, 80),
@ -117,11 +116,11 @@ func (s *State) updateEnabled() {
} }
} }
if (nextEnabled != s.Enabled) || (why != s.enabledWhy) { if (nextEnabled != s.enabled) || (why != s.enabledWhy) {
s.Enabled = nextEnabled s.enabled = nextEnabled
s.enabledWhy = why s.enabledWhy = why
log.Printf("Setting enabled=%v: %s", s.Enabled, s.enabledWhy) log.Printf("Setting enabled=%v: %s", s.enabled, s.enabledWhy)
if s.Enabled { if s.enabled {
s.LogEvent("enabled", "", "", 0, s.enabledWhy) s.LogEvent("enabled", "", "", 0, s.enabledWhy)
} else { } else {
s.LogEvent("disabled", "", "", 0, s.enabledWhy) s.LogEvent("disabled", "", "", 0, s.enabledWhy)
@ -193,11 +192,9 @@ func (s *State) PointsLog() award.List {
return ret return ret
} }
// Messages retrieves the current messages. // Enabled returns true if the server is in "enabled" state
func (s *State) Messages() string { func (s *State) Enabled() bool {
s.lock.RLock() // It's not clear to me that this actually needs to happen return s.enabled
defer s.lock.RUnlock()
return s.messages
} }
// AwardPoints gives points to teamID in category. // AwardPoints gives points to teamID in category.
@ -313,7 +310,6 @@ func (s *State) maybeInitialize() {
s.Remove("hours.txt") s.Remove("hours.txt")
s.Remove("points.log") s.Remove("points.log")
s.Remove("events.csv") s.Remove("events.csv")
s.Remove("messages.html")
s.Remove("mothd.log") s.Remove("mothd.log")
s.RemoveAll("points.tmp") s.RemoveAll("points.tmp")
s.RemoveAll("points.new") s.RemoveAll("points.new")
@ -369,11 +365,6 @@ func (s *State) maybeInitialize() {
f.Close() f.Close()
} }
if f, err := s.Create("messages.html"); err == nil {
fmt.Fprintln(f, "<!-- messages.html: put client broadcast messages here. -->")
f.Close()
}
if f, err := s.Create("points.log"); err == nil { if f, err := s.Create("points.log"); err == nil {
f.Close() f.Close()
} }
@ -465,16 +456,12 @@ func (s *State) updateCaches() {
} }
} }
} }
if bMessages, err := afero.ReadFile(s, "messages.html"); err == nil {
s.messages = string(bMessages)
}
} }
func (s *State) refresh() { func (s *State) refresh() {
s.maybeInitialize() s.maybeInitialize()
s.updateEnabled() s.updateEnabled()
if s.Enabled { if s.enabled {
s.collectPoints() s.collectPoints()
} }
s.updateCaches() s.updateCaches()

View File

@ -185,7 +185,7 @@ func TestStateDisabled(t *testing.T) {
s := NewTestState() s := NewTestState()
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("Brand new state is disabled") t.Error("Brand new state is disabled")
} }
@ -195,35 +195,35 @@ func TestStateDisabled(t *testing.T) {
} }
defer hoursFile.Close() defer hoursFile.Close()
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("Empty hours.txt not enabled") t.Error("Empty hours.txt not enabled")
} }
fmt.Fprintln(hoursFile, "- 1970-01-01T01:01:01Z") fmt.Fprintln(hoursFile, "- 1970-01-01T01:01:01Z")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if s.Enabled { if s.Enabled() {
t.Error("1970-01-01") t.Error("1970-01-01")
} }
fmt.Fprintln(hoursFile, "+ 1970-01-02 01:01:01+05:00") fmt.Fprintln(hoursFile, "+ 1970-01-02 01:01:01+05:00")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("1970-01-02") t.Error("1970-01-02")
} }
fmt.Fprintln(hoursFile, "-") fmt.Fprintln(hoursFile, "-")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if s.Enabled { if s.Enabled() {
t.Error("bare -") t.Error("bare -")
} }
fmt.Fprintln(hoursFile, "+") fmt.Fprintln(hoursFile, "+")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("bare +") t.Error("bare +")
} }
@ -231,21 +231,21 @@ func TestStateDisabled(t *testing.T) {
fmt.Fprintln(hoursFile, "# Comment") fmt.Fprintln(hoursFile, "# Comment")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("Comment") t.Error("Comment")
} }
fmt.Fprintln(hoursFile, "intentional parse error") fmt.Fprintln(hoursFile, "intentional parse error")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("intentional parse error") t.Error("intentional parse error")
} }
fmt.Fprintln(hoursFile, "- 1980-01-01T01:01:01Z") fmt.Fprintln(hoursFile, "- 1980-01-01T01:01:01Z")
hoursFile.Sync() hoursFile.Sync()
s.refresh() s.refresh()
if s.Enabled { if s.Enabled() {
t.Error("1980-01-01") t.Error("1980-01-01")
} }
@ -253,13 +253,13 @@ func TestStateDisabled(t *testing.T) {
t.Error(err) t.Error(err)
} }
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("Removing `hours.txt` disabled event") t.Error("Removing `hours.txt` disabled event")
} }
s.Remove("initialized") s.Remove("initialized")
s.refresh() s.refresh()
if !s.Enabled { if !s.Enabled() {
t.Error("Re-initializing didn't start event") t.Error("Re-initializing didn't start event")
} }
} }

View File

@ -53,7 +53,6 @@ Returns the current Moth event state as a JSON object.
"Config": { "Config": {
"Devel": false // true means this is a development server "Devel": false // true means this is a development server
}, },
"Messages: "HTML to be rendered as broadcast messages",
"TeamNames": { "TeamNames": {
"self": "Requesting team name", // Only if regestered team id is a provided "self": "Requesting team name", // Only if regestered team id is a provided
"0": "Team 1 Name", "0": "Team 1 Name",
@ -91,7 +90,6 @@ Content-Type: application/json
{"Config": {"Config":
{"Devel":false}, {"Devel":false},
"Messages":"<p>Welcome to the event!</p><p>Event ends at 19:00!</p>",
"TeamNames":{ "TeamNames":{
"0":"Mike and Jack", "0":"Mike and Jack",
"12":"Team 2", "12":"Team 2",

View File

@ -167,9 +167,7 @@ class App {
} }
function init() { function init() {
window.app = { window.app = new App()
server: new App()
}
} }
common.WhenDOMLoaded(init) common.WhenDOMLoaded(init)

View File

@ -367,10 +367,8 @@ class State {
Devel: obj.Config.Devel, Devel: obj.Config.Devel,
} }
/** Global messages, in HTML /** True if the server is in enabled state */
* @type {string} this.Enabled = obj.Enabled
*/
this.Messages = obj.Messages
/** Map from Team ID to Team Name /** Map from Team ID to Team Name
* @type {Object.<string,string>} * @type {Object.<string,string>}