+package main
+
+import (
+ "fmt"
+ "html/template"
+ "log"
+ "sort"
+ "strconv"
+ "strings"
+
+ "code.octet-stream.net/broadcaster/internal/protocol"
+ "golang.org/x/net/websocket"
+)
+
+func WebSync(ws *websocket.Conn) {
+ log.Println("A web user connected with WebSocket")
+ buf := make([]byte, 16384)
+
+ badRead := false
+ isAuthenticated := false
+ var user User
+ for {
+ // Ignore any massively oversize messages
+ n, err := ws.Read(buf)
+ if err != nil {
+ if user.Username != "" {
+ log.Println("Lost websocket to user:", user)
+ } else {
+ log.Println("Lost unauthenticated website websocket")
+ }
+ return
+ }
+ if n == len(buf) {
+ badRead = true
+ continue
+ } else if badRead {
+ badRead = false
+ continue
+ }
+
+ if !isAuthenticated {
+ token := string(buf[:n])
+ u, err := users.GetUserForSession(token)
+ if err != nil {
+ log.Println("Could not find user for offered token", token, err)
+ ws.Close()
+ return
+ }
+ user = u
+ log.Println("User authenticated:", user)
+ isAuthenticated = true
+
+ go KeepWebUpdated(ws)
+
+ // send initial playlists message
+ err = sendRadioStatusToWeb(ws)
+ if err != nil {
+ return
+ }
+ }
+ }
+}
+
+type WebStatusData struct {
+ Radios []WebRadioStatus
+}
+
+type WebRadioStatus struct {
+ Name string
+ LocalTime string
+ TimeZone string
+ ChannelClass string
+ ChannelState string
+ Playlist string
+ File string
+ Status string
+ Id string
+ DisableCancel bool
+ FilesInSync bool
+}
+
+func sendRadioStatusToWeb(ws *websocket.Conn) error {
+ webStatuses := make([]WebRadioStatus, 0)
+ radioStatuses := status.Statuses()
+ keys := make([]int, 0)
+ for i := range radioStatuses {
+ keys = append(keys, i)
+ }
+ sort.Ints(keys)
+ for _, i := range keys {
+ v := radioStatuses[i]
+ radio, err := db.GetRadio(i)
+ if err != nil {
+ continue
+ }
+ var channelClass, channelState string
+ if v.PTT {
+ channelClass = "ptt"
+ channelState = "PTT"
+ } else if v.COS {
+ channelClass = "cos"
+ channelState = "RX"
+ } else {
+ channelClass = "clear"
+ channelState = "CLEAR"
+ }
+ var statusText string
+ var disableCancel bool
+ if v.Status == protocol.StatusIdle {
+ statusText = "Idle"
+ disableCancel = true
+ } else if v.Status == protocol.StatusDelay {
+ statusText = fmt.Sprintf("Performing delay before transmit: %ds remain", v.DelaySecondsRemaining)
+ disableCancel = false
+ } else if v.Status == protocol.StatusChannelInUse {
+ statusText = fmt.Sprintf("Waiting for channel to clear: %ds", v.WaitingForChannelSeconds)
+ disableCancel = false
+ } else if v.Status == protocol.StatusPlaying {
+ statusText = fmt.Sprintf("Playing: %d:%02d", v.PlaybackSecondsElapsed/60, v.PlaybackSecondsElapsed%60)
+ disableCancel = false
+ }
+ playlist := v.Playlist
+ if playlist == "" {
+ playlist = "-"
+ }
+ filename := v.Filename
+ if filename == "" {
+ filename = "-"
+ }
+ webStatuses = append(webStatuses, WebRadioStatus{
+ Name: radio.Name,
+ LocalTime: v.LocalTime,
+ TimeZone: v.TimeZone,
+ ChannelClass: channelClass,
+ ChannelState: channelState,
+ Playlist: playlist,
+ File: filename,
+ Status: statusText,
+ Id: strconv.Itoa(i),
+ DisableCancel: disableCancel,
+ FilesInSync: v.FilesInSync,
+ })
+ }
+ data := WebStatusData{
+ Radios: webStatuses,
+ }
+ buf := new(strings.Builder)
+ tmpl := template.Must(template.ParseFS(content, "templates/radios.partial.html"))
+ tmpl.Execute(buf, data)
+ _, err := ws.Write([]byte(buf.String()))
+ return err
+}
+
+func KeepWebUpdated(ws *websocket.Conn) {
+ for {
+ <-status.ChangeChannel()
+ err := sendRadioStatusToWeb(ws)
+ if err != nil {
+ return
+ }
+ }
+}