491 lines
12 KiB
Go
491 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var upgrader = websocket.Upgrader{}
|
|
|
|
type WsClient struct {
|
|
SendUpdates bool
|
|
}
|
|
|
|
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)
|
|
}
|
|
saveConfig("ports", &portConfig)
|
|
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)
|
|
|
|
saveConfig("ports", &portConfig)
|
|
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!"}
|
|
}
|
|
|
|
if body.Mute != nil {
|
|
port.State.Mute = *body.Mute
|
|
}
|
|
if body.Volume != nil {
|
|
port.State.Volume = *body.Volume
|
|
}
|
|
if body.Balance != nil {
|
|
port.State.Balance = *body.Balance
|
|
}
|
|
|
|
saveConfig("ports", &portConfig)
|
|
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
|
|
}
|
|
|
|
saveConfig("ports", &portConfig)
|
|
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})
|
|
saveConfig("ports", &portConfig)
|
|
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
|
|
}
|
|
|
|
if body.Mute != nil {
|
|
from.Route[i].Mute = *body.Mute
|
|
}
|
|
if body.Volume != nil {
|
|
from.Route[i].Volume = *body.Volume
|
|
}
|
|
if body.Balance != nil {
|
|
from.Route[i].Balance = *body.Balance
|
|
}
|
|
|
|
saveConfig("ports", &portConfig)
|
|
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:]...)
|
|
saveConfig("ports", &portConfig)
|
|
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,
|
|
})
|
|
conn.WriteMessage(1, data)
|
|
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)
|
|
conn.WriteMessage(1, data)
|
|
}
|
|
|
|
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!"})
|
|
}
|
|
|
|
wsClients[conn] = WsClient{SendUpdates: false}
|
|
|
|
for {
|
|
var content WebsocketMessage
|
|
jsonErr := conn.ReadJSON(&content)
|
|
if jsonErr != nil {
|
|
delete(wsClients, conn)
|
|
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":
|
|
if clientOpts, ok := wsClients[conn]; ok {
|
|
clientOpts.SendUpdates = true
|
|
wsClients[conn] = clientOpts
|
|
}
|
|
default:
|
|
err = gin.H{"success": false, "error": "Method is not implemented"}
|
|
}
|
|
wsSendRes(conn, res, err, &content)
|
|
}
|
|
}
|
|
|
|
func onPortChange(port *Port) {
|
|
for conn := range wsClients {
|
|
if !wsClients[conn].SendUpdates {
|
|
continue
|
|
}
|
|
jsonData, _ := json.Marshal(WebsocketResponse{Type: "portChange", ResponseData: port})
|
|
conn.WriteMessage(1, jsonData)
|
|
}
|
|
}
|
|
|
|
func onPortListChange() {
|
|
for conn := range wsClients {
|
|
if !wsClients[conn].SendUpdates {
|
|
continue
|
|
}
|
|
jsonData, _ := json.Marshal(WebsocketResponse{
|
|
Type: "portListChange",
|
|
ResponseData: gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output},
|
|
})
|
|
conn.WriteMessage(1, jsonData)
|
|
}
|
|
}
|