package main import ( "encoding/json" "fmt" "sync" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{} type WsClient struct { SendUpdates bool WriteMutex *sync.Mutex } var wsClientsMutex = new(sync.Mutex) var wsClients = make(map[*websocket.Conn]WsClient) func initApi() { gin.SetMode(gin.ReleaseMode) router := gin.Default() router.SetTrustedProxies(nil) router.GET("/api/ports", func(c *gin.Context) { c.JSON(200, portConfig) }) router.GET("/api/port", handleGetPort) router.POST("/api/port", handleCreatePort) router.PUT("/api/port", handleEditPort) router.DELETE("/api/port", handleDeletePort) router.GET("/api/state", handleGetState) router.PUT("/api/state", handleSetState) router.POST("/api/route", handleCreateRoute) router.PUT("/api/route", handleSetRoute) router.DELETE("/api/route", handleDeleteRoute) router.GET("/ws", handleWs) router.Run(fmt.Sprintf("%s:%d", config.Web.Bind, config.Web.Port)) } func httpSendRes(c *gin.Context, err gin.H, res gin.H) { if err != nil { c.JSON(400, err) return } if res == nil { c.JSON(200, gin.H{"success": true}) } else { c.JSON(200, res) } } func handleGetPort(c *gin.Context) { id := c.Query("UUID") if id == "" { c.JSON(400, gin.H{"success": false, "error": "`UUID` parameter missing!"}) return } port, portIndex, _ := findPort(id) if portIndex < 0 { c.JSON(400, gin.H{"success": false, "error": "Port with provided UUID does not exist!"}) return } c.JSON(200, port) } type CreatePortRequest struct { Name string `json:"name"` Backend string `json:"backend"` Channels uint8 `json:"channels"` Type string `json:"type"` } func handleCreatePort(c *gin.Context) { var body CreatePortRequest if err := c.BindJSON(&body); err != nil { c.JSON(500, gin.H{"success": false, "error": "Parsing request body failed!"}) return } res, err := createPort(body) httpSendRes(c, err, res) } func createPort(body CreatePortRequest) (gin.H, gin.H) { if body.Name == "" || body.Backend == "" || body.Channels == 0 || body.Type == "" { return nil, gin.H{"success": false, "error": "Required parameters missing!"} } if body.Type != "input" && body.Type != "output" { return nil, gin.H{"success": false, "error": "`Type` must either be `input` or `output`"} } id := uuid.New() newPort := Port{ UUID: id.String(), Name: body.Name, Properties: PortProperties{ Backend: body.Backend, Channels: body.Channels, }, State: PortState{ Mute: false, Volume: 0, Balance: 0, }, Route: []PortRoute{}, } if body.Type == "input" { registerPort(&newPort, "input") portConfig.Input = append(portConfig.Input, newPort) } else { registerPort(&newPort, "output") portConfig.Output = append(portConfig.Output, newPort) } markConfig("ports") onPortListChange() return gin.H{"success": true, "UUID": id}, nil } func handleDeletePort(c *gin.Context) { id := c.Query("UUID") res := deletePort(id) httpSendRes(c, res, nil) } func deletePort(id string) gin.H { if id == "" { return gin.H{"success": false, "error": "Required parameters missing!"} } port, portIndex, portType := findPort(id) if portIndex < 0 { return gin.H{"success": false, "error": "Port with provided UUID does not exist!"} } // Remove the port from the configuration if portType == "output" { portConfig.Output = append(portConfig.Output[:portIndex], portConfig.Output[portIndex+1:]...) } else { portConfig.Input = append(portConfig.Input[:portIndex], portConfig.Input[portIndex+1:]...) } // Unregister port unregisterPort(port) markConfig("ports") onPortListChange() return nil } func handleGetState(c *gin.Context) { id := c.Query("UUID") if id == "" { c.JSON(400, gin.H{"success": false, "error": "`UUID` parameter missing!"}) return } port, portIndex, _ := findPort(id) if portIndex < 0 { c.JSON(400, gin.H{"success": false, "error": "Port with provided UUID does not exist!"}) return } c.JSON(200, port.State) } type SetPortRequest struct { Mute *bool `json:"mute"` Volume *float32 `json:"volume"` Balance *float32 `json:"balance"` } func handleSetState(c *gin.Context) { var body SetPortRequest err := c.BindJSON(&body) if err != nil { c.JSON(500, gin.H{"success": false, "error": "Parsing request body failed!"}) return } id := c.Query("UUID") res, errMsg := setState(id, body) httpSendRes(c, errMsg, res) } func setState(id string, body SetPortRequest) (gin.H, gin.H) { if id == "" { return nil, gin.H{"success": false, "error": "`UUID` parameter missing!"} } port, portIndex, _ := findPort(id) if portIndex < 0 { return nil, gin.H{"success": false, "error": "Port with provided UUID does not exist!"} } didChange := false if body.Mute != nil && port.State.Mute != *body.Mute { port.State.Mute = *body.Mute didChange = true } if body.Volume != nil && port.State.Volume != *body.Volume { port.State.Volume = *body.Volume didChange = true } if body.Balance != nil && port.State.Balance != *body.Balance { port.State.Balance = *body.Balance didChange = true } if didChange { markConfig("ports") onPortChange(port) } return gin.H{"success": true, "state": port.State}, nil } type EditPortRequest struct { Name *string `json:"name"` } func handleEditPort(c *gin.Context) { var body EditPortRequest err := c.BindJSON(&body) if err != nil { c.JSON(500, gin.H{"success": false, "error": "Parsing request body failed!"}) return } id := c.Query("UUID") res, errMsg := editPort(id, body) httpSendRes(c, errMsg, res) } func editPort(id string, body EditPortRequest) (gin.H, gin.H) { if id == "" { return nil, gin.H{"success": false, "error": "`UUID` parameter missing!"} } port, portIndex, _ := findPort(id) if portIndex < 0 { return nil, gin.H{"success": false, "error": "Port with provided UUID does not exist!"} } if body.Name != nil { port.Name = *body.Name } markConfig("ports") onPortChange(port) return gin.H{"success": true, "port": port}, nil } type CreateRouteRequest struct { To string `json:"to"` } func handleCreateRoute(c *gin.Context) { var body CreateRouteRequest err := c.BindJSON(&body) if err != nil { c.JSON(500, gin.H{"success": false, "error": "Parsing request body failed!"}) return } id := c.Query("UUID") res := createRoute(id, body) httpSendRes(c, res, nil) } func createRoute(id string, body CreateRouteRequest) gin.H { if id == "" || body.To == "" { return gin.H{"success": false, "error": "Required parameters missing!"} } from, fromIndex, fromType := findPort(id) _, toIndex, toType := findPort(body.To) if fromIndex < 0 || toIndex < 0 { return gin.H{"success": false, "error": "One of `UUID` or `To` does not exist!"} } if fromType != "input" || toType != "output" { return gin.H{"success": false, "error": "`UUID` needs to be an input and `To` an output port!"} } from.Route = append(from.Route, PortRoute{ToUUID: body.To, Mute: true, Volume: 0, Balance: 0}) markConfig("ports") onPortChange(from) return nil } type SetRouteRequest struct { Mute *bool `json:"mute"` Volume *float32 `json:"volume"` Balance *float32 `json:"balance"` } func handleSetRoute(c *gin.Context) { var body SetRouteRequest err := c.BindJSON(&body) if err != nil { c.JSON(500, gin.H{"success": false, "error": "Parsing request body failed!"}) return } fromId := c.Query("UUID") toId := c.Query("To") res, errMsg := setRoute(fromId, toId, body) httpSendRes(c, errMsg, res) } func setRoute(fromId string, toId string, body SetRouteRequest) (gin.H, gin.H) { if fromId == "" || toId == "" { return nil, gin.H{"success": false, "error": "Required parameters missing!"} } from, fromIndex, _ := findPort(fromId) if fromIndex < 0 { return nil, gin.H{"success": false, "error": "Port with provided UUID does not exist!"} } for i, r := range from.Route { if r.ToUUID != toId { continue } didChange := false if body.Mute != nil && from.Route[i].Mute != *body.Mute { from.Route[i].Mute = *body.Mute didChange = true } if body.Volume != nil && from.Route[i].Volume != *body.Volume { from.Route[i].Volume = *body.Volume didChange = true } if body.Balance != nil && from.Route[i].Balance != *body.Balance { from.Route[i].Balance = *body.Balance didChange = true } if didChange { markConfig("ports") onPortChange(from) } return gin.H{"success": true, "state": from.Route[i]}, nil } return nil, gin.H{"success": false, "error": "No such route exists!"} } func handleDeleteRoute(c *gin.Context) { fromId := c.Query("UUID") toId := c.Query("To") res := deleteRoute(fromId, toId) httpSendRes(c, res, nil) } func deleteRoute(fromId string, toId string) gin.H { if fromId == "" || toId == "" { return gin.H{"success": false, "error": "Required parameters missing!"} } from, fromIndex, _ := findPort(fromId) if fromIndex < 0 { return gin.H{"success": false, "error": "Port with provided UUID does not exist!"} } for i, r := range from.Route { if r.ToUUID != toId { continue } from.Route = append(from.Route[:i], from.Route[i+1:]...) markConfig("ports") onPortListChange() return nil } return gin.H{"success": false, "error": "No such route exists!"} } type WebsocketMessage struct { RequestId string `json:"requestId"` Method string `json:"method"` UUID string `json:"UUID"` ToUUID string `json:"toUUID"` PortData CreatePortRequest `json:"portData"` StateData SetPortRequest `json:"stateData"` RouteData CreateRouteRequest `json:"routeData"` RouteStateData SetRouteRequest `json:"routeStateData"` } type WebsocketResponse struct { Type string `json:"type"` RequestId string `json:"requestId,omitempty"` RequestMethod string `json:"requestMethod,omitempty"` ResponseData interface{} `json:"responseData"` } func wsSendRes(conn *websocket.Conn, res gin.H, err gin.H, request *WebsocketMessage) { if err != nil { data, _ := json.Marshal(WebsocketResponse{ Type: "error", RequestId: request.RequestId, RequestMethod: request.Method, ResponseData: err, }) wsClients[conn].WriteMutex.Lock() conn.WriteMessage(1, data) wsClients[conn].WriteMutex.Unlock() return } response := WebsocketResponse{ Type: "response", RequestId: request.RequestId, RequestMethod: request.Method, ResponseData: gin.H{"success": true}, } if res != nil { response.ResponseData = res } data, _ := json.Marshal(response) wsClients[conn].WriteMutex.Lock() conn.WriteMessage(1, data) wsClients[conn].WriteMutex.Unlock() } func handleWs(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { fmt.Println("WARN! Handling websocket connection failed!") c.JSON(500, gin.H{"success": false, "error": "Handling websocket connection failed!"}) } wsClientsMutex.Lock() wsClients[conn] = WsClient{SendUpdates: false, WriteMutex: new(sync.Mutex)} wsClientsMutex.Unlock() for { var content WebsocketMessage jsonErr := conn.ReadJSON(&content) if jsonErr != nil { wsClientsMutex.Lock() delete(wsClients, conn) wsClientsMutex.Unlock() conn.Close() break } var res, err gin.H = nil, nil switch content.Method { case "listPorts": res = gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output} case "createPort": res, err = createPort(content.PortData) case "setPortState": res, err = setState(content.UUID, content.StateData) case "deletePort": err = deletePort(content.UUID) case "createRoute": err = createRoute(content.UUID, content.RouteData) case "setRouteState": res, err = setRoute(content.UUID, content.ToUUID, content.RouteStateData) case "deleteRoute": err = deleteRoute(content.UUID, content.ToUUID) case "enableUpdates": wsClientsMutex.Lock() if clientOpts, ok := wsClients[conn]; ok { clientOpts.SendUpdates = true wsClients[conn] = clientOpts } wsClientsMutex.Unlock() default: err = gin.H{"success": false, "error": "Method is not implemented"} } wsSendRes(conn, res, err, &content) } } func onPortChange(port *Port) { wsClientsMutex.Lock() for conn := range wsClients { if !wsClients[conn].SendUpdates { continue } jsonData, _ := json.Marshal(WebsocketResponse{Type: "portChange", ResponseData: port}) wsClients[conn].WriteMutex.Lock() conn.WriteMessage(1, jsonData) wsClients[conn].WriteMutex.Unlock() } wsClientsMutex.Unlock() } func onPortListChange() { wsClientsMutex.Lock() for conn := range wsClients { if !wsClients[conn].SendUpdates { continue } jsonData, _ := json.Marshal(WebsocketResponse{ Type: "portListChange", ResponseData: gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output}, }) wsClients[conn].WriteMutex.Lock() conn.WriteMessage(1, jsonData) wsClients[conn].WriteMutex.Unlock() } wsClientsMutex.Unlock() }