diff --git a/field/arena.go b/field/arena.go index 1e8f82d..3978928 100644 --- a/field/arena.go +++ b/field/arena.go @@ -873,6 +873,9 @@ func (arena *Arena) runPeriodicTasks() { arena.EventStatusMessage = newEventStatusMessage arena.EventStatusNotifier.Notify() } + + // Clean up the list of displays. + arena.purgeDisconnectedDisplays() } // Updates the string that indicates how early or late the event is running. diff --git a/field/display.go b/field/display.go index dbda76d..acca26a 100644 --- a/field/display.go +++ b/field/display.go @@ -13,10 +13,12 @@ import ( "strconv" "strings" "sync" + "time" ) const ( - minDisplayId = 100 + minDisplayId = 100 + displayPurgeTtlMin = 30 ) type DisplayType int @@ -58,12 +60,13 @@ var displayTypePaths = map[DisplayType]string{ var displayRegistryMutex sync.Mutex type Display struct { - Id string - Nickname string - Type DisplayType - Configuration map[string]string - IpAddress string - ConnectionCount int + Id string + Nickname string + Type DisplayType + Configuration map[string]string + IpAddress string + ConnectionCount int + lastConnectedTime time.Time } // Parses the given display URL path and query string to extract the configuration. @@ -159,6 +162,7 @@ func (arena *Arena) RegisterDisplay(display *Display) { } else { display.ConnectionCount = 1 } + display.lastConnectedTime = time.Now() arena.Displays[display.Id] = display } arena.DisplayConfigurationNotifier.Notify() @@ -194,6 +198,25 @@ func (arena *Arena) MarkDisplayDisconnected(display *Display) { } else { existingDisplay.ConnectionCount -= 1 } + existingDisplay.lastConnectedTime = time.Now() + arena.DisplayConfigurationNotifier.Notify() + } +} + +// Removes any displays from the list that haven't had any active connections for a while and don't have a nickname. +func (arena *Arena) purgeDisconnectedDisplays() { + displayRegistryMutex.Lock() + defer displayRegistryMutex.Unlock() + + deleted := false + for id, display := range arena.Displays { + if display.ConnectionCount == 0 && display.Nickname == "" && + time.Now().Sub(display.lastConnectedTime).Minutes() >= displayPurgeTtlMin { + delete(arena.Displays, id) + deleted = true + } + } + if deleted { arena.DisplayConfigurationNotifier.Notify() } } diff --git a/field/display_test.go b/field/display_test.go index b39b48f..0048c01 100644 --- a/field/display_test.go +++ b/field/display_test.go @@ -6,6 +6,7 @@ package field import ( "github.com/stretchr/testify/assert" "testing" + "time" ) func TestDisplayFromUrl(t *testing.T) { @@ -123,3 +124,47 @@ func TestDisplayUpdateError(t *testing.T) { assert.Contains(t, err.Error(), "doesn't exist") } } + +func TestDisplayPurge(t *testing.T) { + arena := setupTestArena(t) + + // Unnamed placeholder gets immediately purged upon disconnection. + display := &Display{Id: "254", Type: PlaceholderDisplay, Configuration: map[string]string{}} + arena.RegisterDisplay(display) + assert.Contains(t, arena.Displays, "254") + arena.MarkDisplayDisconnected(display) + assert.NotContains(t, arena.Displays, "254") + + // Named placeholder does not get immediately purged upon disconnection. + display.Nickname = "Bob" + arena.RegisterDisplay(display) + assert.Contains(t, arena.Displays, "254") + arena.MarkDisplayDisconnected(display) + assert.Contains(t, arena.Displays, "254") + + // Unnamed configured display does not get immediately purged upon disconnection. + display = &Display{Id: "1114", Type: FieldMonitorDisplay, Configuration: map[string]string{}} + arena.RegisterDisplay(display) + assert.Contains(t, arena.Displays, "1114") + arena.MarkDisplayDisconnected(display) + assert.Contains(t, arena.Displays, "1114") + arena.purgeDisconnectedDisplays() + assert.Contains(t, arena.Displays, "1114") + + // Unnamed configured display gets purged by periodic task. + arena.RegisterDisplay(display) + assert.Contains(t, arena.Displays, "1114") + arena.MarkDisplayDisconnected(display) + arena.Displays["1114"].lastConnectedTime = time.Now().Add(-displayPurgeTtlMin * time.Minute) + arena.purgeDisconnectedDisplays() + assert.NotContains(t, arena.Displays, "1114") + + // Named configured display does not get purged by periodic task. + display.Nickname = "Brunhilda" + arena.RegisterDisplay(display) + assert.Contains(t, arena.Displays, "1114") + arena.MarkDisplayDisconnected(display) + arena.Displays["1114"].lastConnectedTime = time.Now().Add(-displayPurgeTtlMin * time.Minute) + arena.purgeDisconnectedDisplays() + assert.Contains(t, arena.Displays, "1114") +} diff --git a/templates/setup_displays.html b/templates/setup_displays.html index dbb26f3..cb7e0a9 100644 --- a/templates/setup_displays.html +++ b/templates/setup_displays.html @@ -30,7 +30,7 @@