You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
4.8 KiB

2 years ago
2 years ago
  1. package httpapiv1
  2. import (
  3. lucifer3 "git.aiterp.net/lucifer3/server"
  4. "git.aiterp.net/lucifer3/server/commands"
  5. "git.aiterp.net/lucifer3/server/effects"
  6. "git.aiterp.net/lucifer3/server/events"
  7. "git.aiterp.net/lucifer3/server/services/script"
  8. "git.aiterp.net/lucifer3/server/services/uistate"
  9. "github.com/google/uuid"
  10. "github.com/gorilla/websocket"
  11. "github.com/labstack/echo/v4"
  12. "github.com/labstack/echo/v4/middleware"
  13. "log"
  14. "net"
  15. "net/http"
  16. "sync"
  17. )
  18. var zeroUUID = uuid.UUID{
  19. 0, 0, 0, 0,
  20. 0, 0, 0, 0,
  21. 0, 0, 0, 0,
  22. 0, 0, 0, 0,
  23. }
  24. func New(addr string) (lucifer3.Service, error) {
  25. svc := &service{}
  26. upgrader := websocket.Upgrader{
  27. CheckOrigin: func(r *http.Request) bool {
  28. return true
  29. },
  30. }
  31. e := echo.New()
  32. e.HideBanner = true
  33. e.HidePort = true
  34. e.Use(middleware.CORS())
  35. e.GET("/state", func(c echo.Context) error {
  36. svc.mu.Lock()
  37. data := svc.data
  38. svc.mu.Unlock()
  39. return c.JSON(200, data)
  40. })
  41. e.GET("/subscribe", func(c echo.Context) error {
  42. ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
  43. if err != nil {
  44. return err
  45. }
  46. sub := make(chan uistate.Patch, 64)
  47. svc.addSub(sub)
  48. defer svc.removeSub(sub)
  49. defer ws.Close()
  50. for patch := range sub {
  51. err := ws.WriteJSON(patch)
  52. if err != nil {
  53. break
  54. }
  55. }
  56. return nil
  57. })
  58. e.POST("/command", func(c echo.Context) error {
  59. var input commandInput
  60. err := c.Bind(&input)
  61. if err != nil {
  62. return err
  63. }
  64. svc.mu.Lock()
  65. bus := svc.bus
  66. svc.mu.Unlock()
  67. if bus == nil {
  68. return c.String(413, "Waiting for bus")
  69. }
  70. switch {
  71. case input.Assign != nil:
  72. bus.RunCommand(commands.Assign{
  73. ID: nil,
  74. Match: input.Assign.Match,
  75. Effect: input.Assign.Effect.Effect,
  76. })
  77. case input.PairDevice != nil:
  78. bus.RunCommand(*input.PairDevice)
  79. case input.ConnectDevice != nil:
  80. bus.RunCommand(*input.ConnectDevice)
  81. case input.ForgetDevice != nil:
  82. bus.RunCommand(*input.ForgetDevice)
  83. case input.SearchDevices != nil:
  84. bus.RunCommand(*input.SearchDevices)
  85. case input.AddAlias != nil:
  86. bus.RunCommand(*input.AddAlias)
  87. case input.RemoveAlias != nil:
  88. bus.RunCommand(*input.RemoveAlias)
  89. case input.UpdateScript != nil:
  90. bus.RunCommand(*input.UpdateScript)
  91. case input.ExecuteScript != nil:
  92. bus.RunCommand(*input.ExecuteScript)
  93. case input.UpdateTrigger != nil:
  94. if input.UpdateTrigger.ID == zeroUUID {
  95. input.UpdateTrigger.ID = uuid.New()
  96. }
  97. if input.UpdateTrigger.ScriptPre == nil {
  98. input.UpdateTrigger.ScriptPre = make([]script.Line, 0)
  99. }
  100. if input.UpdateTrigger.ScriptPost == nil {
  101. input.UpdateTrigger.ScriptPost = make([]script.Line, 0)
  102. }
  103. bus.RunCommand(*input.UpdateTrigger)
  104. case input.DeleteTrigger != nil:
  105. bus.RunCommand(*input.DeleteTrigger)
  106. default:
  107. return c.String(400, "No supported command found in input")
  108. }
  109. return c.JSON(200, input)
  110. })
  111. listener, err := net.Listen("tcp", addr)
  112. if err != nil {
  113. return nil, err
  114. }
  115. e.Listener = listener
  116. go func() {
  117. err := e.Start(addr)
  118. if err != nil {
  119. log.Fatalln("Failed to listen to webserver")
  120. }
  121. }()
  122. return svc, nil
  123. }
  124. type service struct {
  125. mu sync.Mutex
  126. data uistate.Data
  127. bus *lucifer3.EventBus
  128. subs []chan uistate.Patch
  129. }
  130. func (s *service) Active() bool {
  131. return true
  132. }
  133. func (s *service) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
  134. switch event := event.(type) {
  135. case events.Started:
  136. s.mu.Lock()
  137. s.bus = bus
  138. s.mu.Unlock()
  139. case uistate.Patch:
  140. s.mu.Lock()
  141. s.data = s.data.WithPatch(event)
  142. subs := s.subs
  143. s.mu.Unlock()
  144. for _, sub := range subs {
  145. select {
  146. case sub <- event:
  147. default:
  148. }
  149. }
  150. }
  151. }
  152. func (s *service) addSub(ch chan uistate.Patch) {
  153. s.mu.Lock()
  154. s.subs = append(s.subs, ch)
  155. s.mu.Unlock()
  156. }
  157. func (s *service) removeSub(ch chan uistate.Patch) {
  158. s.mu.Lock()
  159. s.subs = append([]chan uistate.Patch{}, s.subs...)
  160. for i, sub := range s.subs {
  161. if sub == ch {
  162. s.subs = append(s.subs[:i], s.subs[i+1:]...)
  163. break
  164. }
  165. }
  166. s.mu.Unlock()
  167. }
  168. type commandInput struct {
  169. Assign *assignInput `json:"assign,omitempty"`
  170. AddAlias *commands.AddAlias `json:"addAlias,omitempty"`
  171. RemoveAlias *commands.RemoveAlias `json:"removeAlias,omitempty"`
  172. PairDevice *commands.PairDevice `json:"pairDevice,omitempty"`
  173. ConnectDevice *commands.ConnectDevice `json:"connectDevice,omitempty"`
  174. SearchDevices *commands.SearchDevices `json:"searchDevices,omitempty"`
  175. ForgetDevice *commands.ForgetDevice `json:"forgetDevice,omitempty"`
  176. UpdateScript *script.Update `json:"updateScript,omitempty"`
  177. ExecuteScript *script.Execute `json:"executeScript,omitempty"`
  178. UpdateTrigger *script.UpdateTrigger `json:"updateTrigger,omitempty"`
  179. DeleteTrigger *script.DeleteTrigger `json:"deleteTrigger,omitempty"`
  180. }
  181. type assignInput struct {
  182. Match string `json:"match"`
  183. Effect effects.Serializable `json:"effect"`
  184. }