From 500105b2a3102c6c1b539dcf7b36e077a3bb68a3 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:17:47 +0100 Subject: [PATCH 01/94] Add Has method to disk storage --- pkg/internal/stringslice/contains.go | 11 ++++++ pkg/internal/stringslice/contains_test.go | 15 ++++++++ pkg/storage/storage.go | 18 +++++++--- pkg/storage/storage_test.go | 44 +++++++++++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 pkg/internal/stringslice/contains.go create mode 100644 pkg/internal/stringslice/contains_test.go diff --git a/pkg/internal/stringslice/contains.go b/pkg/internal/stringslice/contains.go new file mode 100644 index 0000000..fecc3de --- /dev/null +++ b/pkg/internal/stringslice/contains.go @@ -0,0 +1,11 @@ +package stringslice + +func Contains(elem string, slice []string) bool { + for _, s := range slice { + if s == elem { + return true + } + } + + return false +} diff --git a/pkg/internal/stringslice/contains_test.go b/pkg/internal/stringslice/contains_test.go new file mode 100644 index 0000000..eecbd2e --- /dev/null +++ b/pkg/internal/stringslice/contains_test.go @@ -0,0 +1,15 @@ +package stringslice + +import "testing" + +func TestContains(t *testing.T) { + slice := []string{"a", "b", "c"} + + if !Contains("b", slice) { + t.Errorf("Expected contains to returns true as the slice contains the element, but it returned false") + } + + if Contains("x", slice) { + t.Errorf("Expected contains to returns false as the slice does not contain the element, but it returned true") + } +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index a5cdb3f..50e5b3c 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -3,6 +3,7 @@ package storage import ( "encoding/json" "fmt" + "github.com/StageAutoControl/controller/pkg/internal/stringslice" "os" "reflect" "strings" @@ -39,6 +40,11 @@ func (s *Storage) buildFileName(key string, value interface{}) string { return fmt.Sprintf("%s_%s.json", s.getType(value), key) } +func (s *Storage) Has(key string, kind interface{}) bool { + keys := s.listWithPrefix(s.buildFileName(key, kind), kind) + return stringslice.Contains(key, keys) +} + // Write a given value with the given fileName to disk func (s *Storage) Write(key string, value interface{}) error { b, err := json.Marshal(value) @@ -60,11 +66,11 @@ func (s *Storage) Read(key string, value interface{}) error { b, err := s.disk.Read(fileName) if err != nil { - return fmt.Errorf("failed to read value of type %s from disk: %v", err) + return fmt.Errorf("failed to read value of type %s from disk: %v", s.getType(value), err) } if err := json.Unmarshal(b, value); err != nil { - return fmt.Errorf("failed to unmarshal value of type %s: %v", err) + return fmt.Errorf("failed to unmarshal value of type %s: %v", s.getType(value), err) } return nil @@ -72,13 +78,15 @@ func (s *Storage) Read(key string, value interface{}) error { // List the keys of a given kind func (s *Storage) List(kind interface{}) []string { - fileName := s.buildFileName("", kind) + return s.listWithPrefix("", kind) +} +func (s *Storage) listWithPrefix(prefix string, kind interface{}) []string { var keys []string - for key := range s.disk.Keys(nil) { + for key := range s.disk.KeysPrefix(prefix, nil) { // Remove the custom file schema from the name, which should only return the pure key key = strings.TrimSuffix(key, ".json") - key = strings.TrimPrefix(key, fileName) + key = strings.TrimPrefix(key, fmt.Sprintf("%s_", s.getType(kind))) keys = append(keys, key) } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 907da9e..d6d80c7 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -3,6 +3,7 @@ package storage import ( "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/internal/fixtures" + "github.com/StageAutoControl/controller/pkg/internal/stringslice" "io/ioutil" "os" "path/filepath" @@ -94,6 +95,42 @@ func TestStorage_Read(t *testing.T) { } } +func TestStorage_Has_Existing(t *testing.T) { + defer cleanup(t, path) + storage := New(path) + + if err := os.MkdirAll(filepath.Dir(expectedFileName), 0755); err != nil { + t.Fatalf("failed to prepare disk directory path: %v", err) + } + + if err := ioutil.WriteFile(expectedFileName, []byte(expectedContent), 0755); err != nil { + t.Fatalf("failed to prepare disk file: %v", err) + } + + expDevice := &cntl.DMXDevice{} + has := storage.Has(key, expDevice) + if !has { + t.Errorf("expected storage to have id %q, but doesn't.", key) + return + } +} + +func TestStorage_Has_NotExisting(t *testing.T) { + defer cleanup(t, path) + storage := New(path) + + if err := os.MkdirAll(filepath.Dir(expectedFileName), 0755); err != nil { + t.Fatalf("failed to prepare disk directory path: %v", err) + } + + expDevice := &cntl.DMXDevice{} + has:= storage.Has(key, expDevice) + if has { + t.Errorf("expected storage to NOT have id %q, but does.", key) + return + } +} + func TestStorage_List(t *testing.T) { defer cleanup(t, path) storage := New(path) @@ -109,6 +146,13 @@ func TestStorage_List(t *testing.T) { if len(keys) != len(ds.DMXDevices) { t.Errorf("expected to get %d keys, got %d keys", len(ds.DMXDevices), len(keys)) } + + for k := range ds.DMXDevices { + if !stringslice.Contains(k, keys) { + t.Errorf("Ecpected result list %s to have key %s", keys, k) + } + } + } func TestStorage_Delete(t *testing.T) { From e745bcaa3926260237a349e8c717be301b888aaf Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:38:58 +0100 Subject: [PATCH 02/94] Add explicit DMXDevice api controller --- cmd/api.go | 8 ++- pkg/api/controller.go | 104 ------------------------------- pkg/api/dmx_device_controller.go | 101 ++++++++++++++++++++++++++++++ pkg/api/server.go | 15 ++--- pkg/api/types.go | 19 ++++++ 5 files changed, 133 insertions(+), 114 deletions(-) delete mode 100644 pkg/api/controller.go create mode 100644 pkg/api/dmx_device_controller.go diff --git a/cmd/api.go b/cmd/api.go index d66c33b..0570da9 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -16,7 +16,12 @@ var apiCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { logger := Logger.WithField("module", "api") - store := storage.New("/var/controller/data") + storagePath, err := cmd.Flags().GetString("storage-path") + if err != nil { + logger.Fatal(err) + } + + store := storage.New(storagePath) server, err := api.NewServer(logger, store) if err != nil { logger.Fatal(err) @@ -39,4 +44,5 @@ func init() { RootCmd.AddCommand(apiCmd) apiCmd.Flags().Uint16P("port", "p", 8080, "TCP port the API should listen on") + apiCmd.Flags().StringP("storage-path", "s", "/var/controller/data", "path where the storage should store the data") } diff --git a/pkg/api/controller.go b/pkg/api/controller.go deleted file mode 100644 index 78afe62..0000000 --- a/pkg/api/controller.go +++ /dev/null @@ -1,104 +0,0 @@ -package api - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/satori/go.uuid" - "github.com/sirupsen/logrus" - "net/http" - "reflect" -) - -type Controller struct { - logger *logrus.Entry - storage storage - controlledType interface{} -} - -//type Arg json.RawMessage - -type Response struct { - value interface{} -} - -type listResponse struct { - value []interface{} -} - -func newController(logger *logrus.Entry, storage storage, controlledType interface{}) *Controller { - return &Controller{ - logger: logger, - storage: storage, - controlledType: controlledType, - } -} - -func (c *Controller) Create(r *http.Request, args *json.RawMessage, reply *Response) error { - if args == nil || len(*args) == 0 { - return errors.New("no parameter given") - } - - t := reflect.ValueOf(c.controlledType).Type() - target := reflect.New(t).Elem() - if err := json.Unmarshal(*args, &target); err != nil { - return fmt.Errorf("failed to unmarshal json content: %v", err) - } - - id, err := c.getID(&target) - if err != nil { - return err - } - - if err := c.storage.Write(id, &target); err != nil { - return fmt.Errorf("failed to write to disk: %v", err) - } - - reply.value = target - return nil -} - -func (c *Controller) checkType(value interface{}) error { - t := reflect.TypeOf(value) - if t.Kind() != reflect.Ptr { - return fmt.Errorf("parameter %s of type %s is no pointer", t.Name(), t.Kind()) - } - - s := t.Elem() - if s.Kind() != reflect.Struct { - return fmt.Errorf("parameter pointer %v of type %v is no struct", s.Name(), s.Kind()) - } - - expected := reflect.TypeOf(c.controlledType) - if s.Name() != expected.Name() { - return fmt.Errorf("expected to get value of type %v, got type %v", expected.Name(), s.Name()) - } - - return nil -} - -// getID expects a parameter "value" which is a pointer to a struct, which has a field called ID. -// if the ID field is set the value is returned, otherwise a new UUID v4 is generated and set as field value. -func (c *Controller) getID(value interface{}) (string, error) { - v := reflect.ValueOf(value).Elem() - field := v.FieldByName("ID") - if !field.IsValid() { - return "", fmt.Errorf("field ID of struct %s is not valid", v.Kind()) - } - - if !field.CanSet() { - return "", fmt.Errorf("field ID of struct %s is not settable", v.Kind()) - } - - if field.Kind() != reflect.String { - return "", fmt.Errorf("field ID of struct %s is not a string", v.Kind()) - } - - id := field.String() - if id == "" { - id = uuid.NewV4().String() - field.SetString(id) - } - - return id, nil -} diff --git a/pkg/api/dmx_device_controller.go b/pkg/api/dmx_device_controller.go new file mode 100644 index 0000000..634617e --- /dev/null +++ b/pkg/api/dmx_device_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxDeviceController struct { + logger *logrus.Entry + storage storage +} + +func newDMXDeviceController(logger *logrus.Entry, storage storage) *dmxDeviceController { + return &dmxDeviceController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXDevice +func (c *dmxDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXDevice +func (c *dmxDeviceController) Update(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXDevice +func (c *dmxDeviceController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXDevice) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXDevice{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXDevice +func (c *dmxDeviceController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXDevice) error { + for _, id := range c.storage.List(&cntl.DMXDevice{}) { + entity := &cntl.DMXDevice{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXDevice +func (c *dmxDeviceController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXDevice{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXDevice{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index dfc78bb..ab3cafc 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -3,18 +3,17 @@ package api import ( "context" "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" "github.com/sirupsen/logrus" "net/http" - "reflect" ) type Server struct { *rpc.Server logger *logrus.Entry storage storage + controller map[string]interface{} } func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { @@ -32,15 +31,13 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { } func (s *Server) registerControllers() error { - types := []interface{}{ - &cntl.DMXDevice{}, &cntl.DMXDeviceGroup{}, &cntl.DMXDeviceType{}, + s.controller = map[string]interface{}{ + "DMXDevice": newDMXDeviceController(s.logger, s.storage), } - for _, t := range types { - name := reflect.TypeOf(t).Elem().Name() - err := s.RegisterService(newController(s.logger, s.storage, t), name) - if err != nil { - return fmt.Errorf("failed to register RPC controller for type %s: %v", name, err) + for name, controller := range s.controller { + if err := s.Server.RegisterService(controller, name); err != nil { + return err } } diff --git a/pkg/api/types.go b/pkg/api/types.go index 3ccbc94..56f038c 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1,8 +1,27 @@ package api +import "errors" + +var ( + errNoIDGiven = errors.New("no ID was given with request") + errExists = errors.New("entity with given ID already exists") + errNotExists = errors.New("entity with given ID does not exist") +) + type storage interface { + Has(key string, kind interface{}) bool Write(key string, value interface{}) error Read(key string, value interface{}) error List(kind interface{}) []string Delete(key string, kind interface{}) error } + +type IDRequest struct { + ID string `json:"id"` +} + +type SuccessResponse struct { + Success bool `json:"success"` +} + +type EmptyRequest struct {} From 991ac8d16a241d8f60ae14bee5d2cefa279b0d6f Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:44:42 +0100 Subject: [PATCH 03/94] Fix code style issues of linting and tests --- cmd/root.go | 2 +- pkg/api/server.go | 8 +++++--- pkg/api/types.go | 7 +++++-- pkg/internal/stringslice/contains.go | 1 + pkg/storage/storage.go | 8 +++++--- pkg/storage/storage_test.go | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 8c8057e..6c57b4a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,7 +11,7 @@ import ( ) var ( - // The Logger used by the whole application + // Logger used by the whole application Logger *logrus.Entry logLevel string ) diff --git a/pkg/api/server.go b/pkg/api/server.go index ab3cafc..7602583 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -9,13 +9,15 @@ import ( "net/http" ) +// Server represents the controllers API server, aware of all the controllers type Server struct { *rpc.Server - logger *logrus.Entry - storage storage + logger *logrus.Entry + storage storage controller map[string]interface{} } +// NewServer returns a new Server instance func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { server := &Server{ Server: rpc.NewServer(), @@ -57,7 +59,7 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { }) httpServer := http.Server{ - Addr: endpoint, + Addr: endpoint, Handler: r, } diff --git a/pkg/api/types.go b/pkg/api/types.go index 56f038c..c5768ea 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -4,7 +4,7 @@ import "errors" var ( errNoIDGiven = errors.New("no ID was given with request") - errExists = errors.New("entity with given ID already exists") + errExists = errors.New("entity with given ID already exists") errNotExists = errors.New("entity with given ID does not exist") ) @@ -16,12 +16,15 @@ type storage interface { Delete(key string, kind interface{}) error } +// IDRequest is a request object only storing an ID type IDRequest struct { ID string `json:"id"` } +// SuccessResponse returns a simple bool to state weather the operation was successful type SuccessResponse struct { Success bool `json:"success"` } -type EmptyRequest struct {} +// EmptyRequest is ... yah, an empty request :shrug: +type EmptyRequest struct{} diff --git a/pkg/internal/stringslice/contains.go b/pkg/internal/stringslice/contains.go index fecc3de..559940a 100644 --- a/pkg/internal/stringslice/contains.go +++ b/pkg/internal/stringslice/contains.go @@ -1,5 +1,6 @@ package stringslice +// Contains returns weather the given slice contains the given element func Contains(elem string, slice []string) bool { for _, s := range slice { if s == elem { diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 50e5b3c..8b15e9e 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -40,6 +40,7 @@ func (s *Storage) buildFileName(key string, value interface{}) string { return fmt.Sprintf("%s_%s.json", s.getType(value), key) } +// Has returns weather the storage has the given entity or not func (s *Storage) Has(key string, kind interface{}) bool { keys := s.listWithPrefix(s.buildFileName(key, kind), kind) return stringslice.Contains(key, keys) @@ -104,9 +105,10 @@ func (s *Storage) Delete(key string, kind interface{}) error { } func (s *Storage) getType(kind interface{}) string { - if t := reflect.TypeOf(kind); t.Kind() == reflect.Ptr { + t := reflect.TypeOf(kind) + if t.Kind() == reflect.Ptr { return t.Elem().Name() - } else { - return t.Name() } + + return t.Name() } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index d6d80c7..ae9211b 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -124,7 +124,7 @@ func TestStorage_Has_NotExisting(t *testing.T) { } expDevice := &cntl.DMXDevice{} - has:= storage.Has(key, expDevice) + has := storage.Has(key, expDevice) if has { t.Errorf("expected storage to NOT have id %q, but does.", key) return From 0c613cbf32ab817ac5590bc07a82e4a437121607 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:46:21 +0100 Subject: [PATCH 04/94] Add DMXDeviceType controller --- pkg/api/dmx_device_type_controller.go | 101 ++++++++++++++++++++++++++ pkg/api/server.go | 1 + 2 files changed, 102 insertions(+) create mode 100644 pkg/api/dmx_device_type_controller.go diff --git a/pkg/api/dmx_device_type_controller.go b/pkg/api/dmx_device_type_controller.go new file mode 100644 index 0000000..05091e6 --- /dev/null +++ b/pkg/api/dmx_device_type_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxDeviceTypeController struct { + logger *logrus.Entry + storage storage +} + +func newDMXDeviceTypeController(logger *logrus.Entry, storage storage) *dmxDeviceTypeController { + return &dmxDeviceTypeController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXDeviceType +func (c *dmxDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXDeviceType +func (c *dmxDeviceTypeController) Update(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXDeviceType +func (c *dmxDeviceTypeController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXDeviceType) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXDeviceType{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXDeviceType +func (c *dmxDeviceTypeController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXDeviceType) error { + for _, id := range c.storage.List(&cntl.DMXDeviceType{}) { + entity := &cntl.DMXDeviceType{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXDeviceType +func (c *dmxDeviceTypeController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXDeviceType{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXDeviceType{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index 7602583..f5e29cf 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -35,6 +35,7 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { func (s *Server) registerControllers() error { s.controller = map[string]interface{}{ "DMXDevice": newDMXDeviceController(s.logger, s.storage), + "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), } for name, controller := range s.controller { From 2a17128cfb1a8c992b5a218c987bcdfc21eb1939 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:46:43 +0100 Subject: [PATCH 05/94] Add DMXDeviceGroup controller --- pkg/api/dmx_device_group_controller.go | 101 +++++++++++++++++++++++++ pkg/api/server.go | 1 + 2 files changed, 102 insertions(+) create mode 100644 pkg/api/dmx_device_group_controller.go diff --git a/pkg/api/dmx_device_group_controller.go b/pkg/api/dmx_device_group_controller.go new file mode 100644 index 0000000..3ad17ab --- /dev/null +++ b/pkg/api/dmx_device_group_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxDeviceGroupController struct { + logger *logrus.Entry + storage storage +} + +func newDMXDeviceGroupController(logger *logrus.Entry, storage storage) *dmxDeviceGroupController { + return &dmxDeviceGroupController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXDeviceGroup +func (c *dmxDeviceGroupController) Create(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXDeviceGroup +func (c *dmxDeviceGroupController) Update(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXDeviceGroup +func (c *dmxDeviceGroupController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXDeviceGroup) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXDeviceGroup{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXDeviceGroup +func (c *dmxDeviceGroupController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXDeviceGroup) error { + for _, id := range c.storage.List(&cntl.DMXDeviceGroup{}) { + entity := &cntl.DMXDeviceGroup{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXDeviceGroup +func (c *dmxDeviceGroupController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXDeviceGroup{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXDeviceGroup{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index f5e29cf..3a3702d 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -35,6 +35,7 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { func (s *Server) registerControllers() error { s.controller = map[string]interface{}{ "DMXDevice": newDMXDeviceController(s.logger, s.storage), + "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), } From 9c2f9cbf16ffde4c05ef79fe7a71be8f75f7b1da Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:48:23 +0100 Subject: [PATCH 06/94] Add Song controller --- pkg/api/server.go | 1 + pkg/api/song_controller.go | 101 +++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 pkg/api/song_controller.go diff --git a/pkg/api/server.go b/pkg/api/server.go index 3a3702d..ece47a3 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -37,6 +37,7 @@ func (s *Server) registerControllers() error { "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), + "Song": newSongController(s.logger, s.storage), } for name, controller := range s.controller { diff --git a/pkg/api/song_controller.go b/pkg/api/song_controller.go new file mode 100644 index 0000000..d1bf3f3 --- /dev/null +++ b/pkg/api/song_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type songController struct { + logger *logrus.Entry + storage storage +} + +func newSongController(logger *logrus.Entry, storage storage) *songController { + return &songController{ + logger: logger, + storage: storage, + } +} + +// Create a new Song +func (c *songController) Create(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new Song +func (c *songController) Update(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a Song +func (c *songController) Get(r *http.Request, idReq *IDRequest, reply *cntl.Song) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.Song{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of Song +func (c *songController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.Song) error { + for _, id := range c.storage.List(&cntl.Song{}) { + entity := &cntl.Song{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a Song +func (c *songController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.Song{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.Song{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} From 1333050b900cf0de9f00955fcbc31a41c5001054 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:49:02 +0100 Subject: [PATCH 07/94] Add SetList controller --- pkg/api/server.go | 1 + pkg/api/set_list_controller.go | 101 +++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 pkg/api/set_list_controller.go diff --git a/pkg/api/server.go b/pkg/api/server.go index ece47a3..1478b1a 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -38,6 +38,7 @@ func (s *Server) registerControllers() error { "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), "Song": newSongController(s.logger, s.storage), + "SetList": newSetListController(s.logger, s.storage), } for name, controller := range s.controller { diff --git a/pkg/api/set_list_controller.go b/pkg/api/set_list_controller.go new file mode 100644 index 0000000..b97721e --- /dev/null +++ b/pkg/api/set_list_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type setListController struct { + logger *logrus.Entry + storage storage +} + +func newSetListController(logger *logrus.Entry, storage storage) *setListController { + return &setListController{ + logger: logger, + storage: storage, + } +} + +// Create a new SetList +func (c *setListController) Create(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new SetList +func (c *setListController) Update(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a SetList +func (c *setListController) Get(r *http.Request, idReq *IDRequest, reply *cntl.SetList) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.SetList{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of SetList +func (c *setListController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.SetList) error { + for _, id := range c.storage.List(&cntl.SetList{}) { + entity := &cntl.SetList{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a SetList +func (c *setListController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.SetList{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.SetList{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} From 787b71dec4defbe5f17221714cc6230807be99fa Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:50:00 +0100 Subject: [PATCH 08/94] Add DMXAnimation controller --- pkg/api/dmx_animation_controller.go | 101 ++++++++++++++++++++++++++++ pkg/api/server.go | 1 + 2 files changed, 102 insertions(+) create mode 100644 pkg/api/dmx_animation_controller.go diff --git a/pkg/api/dmx_animation_controller.go b/pkg/api/dmx_animation_controller.go new file mode 100644 index 0000000..a1bf773 --- /dev/null +++ b/pkg/api/dmx_animation_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxAnimationController struct { + logger *logrus.Entry + storage storage +} + +func newDMXAnimationController(logger *logrus.Entry, storage storage) *dmxAnimationController { + return &dmxAnimationController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXAnimation +func (c *dmxAnimationController) Create(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXAnimation +func (c *dmxAnimationController) Update(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXAnimation +func (c *dmxAnimationController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXAnimation) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXAnimation{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXAnimation +func (c *dmxAnimationController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXAnimation) error { + for _, id := range c.storage.List(&cntl.DMXAnimation{}) { + entity := &cntl.DMXAnimation{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXAnimation +func (c *dmxAnimationController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXAnimation{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXAnimation{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index 1478b1a..c61381b 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -34,6 +34,7 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { func (s *Server) registerControllers() error { s.controller = map[string]interface{}{ + "DMXAnimation": newDMXAnimationController(s.logger, s.storage), "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), From 0f9bb1160b4e3f06f0b2c6e57e3f8d0436c9ae6b Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:50:48 +0100 Subject: [PATCH 09/94] Add DMXTransition controller --- pkg/api/dmx_transition_controller.go | 101 +++++++++++++++++++++++++++ pkg/api/server.go | 1 + 2 files changed, 102 insertions(+) create mode 100644 pkg/api/dmx_transition_controller.go diff --git a/pkg/api/dmx_transition_controller.go b/pkg/api/dmx_transition_controller.go new file mode 100644 index 0000000..51bbf28 --- /dev/null +++ b/pkg/api/dmx_transition_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxTransitionController struct { + logger *logrus.Entry + storage storage +} + +func newDMXTransitionController(logger *logrus.Entry, storage storage) *dmxTransitionController { + return &dmxTransitionController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXTransition +func (c *dmxTransitionController) Create(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXTransition +func (c *dmxTransitionController) Update(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXTransition +func (c *dmxTransitionController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXTransition) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXTransition{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXTransition +func (c *dmxTransitionController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXTransition) error { + for _, id := range c.storage.List(&cntl.DMXTransition{}) { + entity := &cntl.DMXTransition{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXTransition +func (c *dmxTransitionController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXTransition{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXTransition{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index c61381b..4e612fc 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -38,6 +38,7 @@ func (s *Server) registerControllers() error { "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), + "DMXTransition": newDMXTransitionController(s.logger, s.storage), "Song": newSongController(s.logger, s.storage), "SetList": newSetListController(s.logger, s.storage), } From 3b7877c983faac0628b2f586d6e2624d89e4df2b Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:51:48 +0100 Subject: [PATCH 10/94] Add DMXScene controller --- pkg/api/dmx_scene_controller.go | 101 ++++++++++++++++++++++++++++++++ pkg/api/server.go | 1 + 2 files changed, 102 insertions(+) create mode 100644 pkg/api/dmx_scene_controller.go diff --git a/pkg/api/dmx_scene_controller.go b/pkg/api/dmx_scene_controller.go new file mode 100644 index 0000000..74cc71e --- /dev/null +++ b/pkg/api/dmx_scene_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxSceneController struct { + logger *logrus.Entry + storage storage +} + +func newDMXSceneController(logger *logrus.Entry, storage storage) *dmxSceneController { + return &dmxSceneController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXScene +func (c *dmxSceneController) Create(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXScene +func (c *dmxSceneController) Update(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXScene +func (c *dmxSceneController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXScene) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXScene{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXScene +func (c *dmxSceneController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXScene) error { + for _, id := range c.storage.List(&cntl.DMXScene{}) { + entity := &cntl.DMXScene{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXScene +func (c *dmxSceneController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXScene{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXScene{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index 4e612fc..8cd8989 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -38,6 +38,7 @@ func (s *Server) registerControllers() error { "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), + "DMXScene": newDMXSceneController(s.logger, s.storage), "DMXTransition": newDMXTransitionController(s.logger, s.storage), "Song": newSongController(s.logger, s.storage), "SetList": newSetListController(s.logger, s.storage), From dd3a58d3ae35464c27f2e6a9cd9365ee75477bc7 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 9 Feb 2019 23:53:28 +0100 Subject: [PATCH 11/94] Add DMXPreset controller --- pkg/api/dmx_preset_controller.go | 101 +++++++++++++++++++++++++++++++ pkg/api/server.go | 1 + 2 files changed, 102 insertions(+) create mode 100644 pkg/api/dmx_preset_controller.go diff --git a/pkg/api/dmx_preset_controller.go b/pkg/api/dmx_preset_controller.go new file mode 100644 index 0000000..54b139f --- /dev/null +++ b/pkg/api/dmx_preset_controller.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" + "net/http" +) + +type dmxPresetController struct { + logger *logrus.Entry + storage storage +} + +func newDMXPresetController(logger *logrus.Entry, storage storage) *dmxPresetController { + return &dmxPresetController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXPreset +func (c *dmxPresetController) Create(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } else { + if c.storage.Has(entity.ID, entity) { + return errExists + } + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Update a new DMXPreset +func (c *dmxPresetController) Update(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + *reply = *entity + return nil +} + +// Get a DMXPreset +func (c *dmxPresetController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXPreset) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXPreset{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXPreset +func (c *dmxPresetController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXPreset) error { + for _, id := range c.storage.List(&cntl.DMXPreset{}) { + entity := &cntl.DMXPreset{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXPreset +func (c *dmxPresetController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXPreset{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXPreset{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server.go index 8cd8989..f9d90bf 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -38,6 +38,7 @@ func (s *Server) registerControllers() error { "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), + "DMXPreset": newDMXPresetController(s.logger, s.storage), "DMXScene": newDMXSceneController(s.logger, s.storage), "DMXTransition": newDMXTransitionController(s.logger, s.storage), "Song": newSongController(s.logger, s.storage), From d92f019d6b09da017b2680a670f3aad808521dd4 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 10 Feb 2019 00:48:24 +0100 Subject: [PATCH 12/94] Add first controller test --- pkg/api/dmx_device_controller_test.go | 169 ++++++++++++++++++++++++++ pkg/api/server.go | 2 +- pkg/api/types.go | 1 + pkg/internal/testing/cleanup.go | 13 ++ pkg/storage/storage_test.go | 18 ++- 5 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 pkg/api/dmx_device_controller_test.go create mode 100644 pkg/internal/testing/cleanup.go diff --git a/pkg/api/dmx_device_controller_test.go b/pkg/api/dmx_device_controller_test.go new file mode 100644 index 0000000..642f572 --- /dev/null +++ b/pkg/api/dmx_device_controller_test.go @@ -0,0 +1,169 @@ +package api + +import ( + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + disk "github.com/StageAutoControl/controller/pkg/storage" + "github.com/sirupsen/logrus" + "math/rand" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" +) + +var ( + ds = fixtures.DataStore() + key = "35cae00a-0b17-11e7-8bca-bbf30c56f20e" +) + +func TestDmxDeviceController_Create_WithID(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + entity := ds.DMXDevices[key] + reply := &cntl.DMXDevice{} + + if err := controller.Create(req, entity, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID == "" { + t.Errorf("exptected to get reply with an ID set, but isn't") + } +} + +func TestDmxDeviceController_Create_WithoutID(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + entity := ds.DMXDevices[key] + entity.ID = "" + reply := &cntl.DMXDevice{} + + if err := controller.Create(req, entity, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID == "" { + t.Errorf("exptected to get reply with an ID set, but isn't") + } +} + +func TestDmxDeviceController_Get_NotExisting(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + reply := &cntl.DMXDevice{} + + if err := controller.Get(req, &IDRequest{ID: key}, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDmxDeviceController_Get_Existing(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + entity := ds.DMXDevices[key] + + createReply := &cntl.DMXDevice{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + reply := &cntl.DMXDevice{} + if err := controller.Get(req, &IDRequest{ID: key}, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID == "" { + t.Errorf("exptected to get reply with an ID set, but isn't") + } +} + +func TestDmxDeviceController_Update_NotExisting(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + entity := ds.DMXDevices[key] + reply := &cntl.DMXDevice{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDmxDeviceController_Update_Existing(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + entity := ds.DMXDevices[key] + + createReply := &cntl.DMXDevice{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + reply := &cntl.DMXDevice{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } +} +func TestDmxDeviceController_Delete_NotExisting(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + reply := &SuccessResponse{} + + if err := controller.Delete(req, &IDRequest{ID: key}, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDmxDeviceController_Delete_Existing(t *testing.T) { + logger := logrus.New().WithFields(logrus.Fields{}) + path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) + defer internalTesting.Cleanup(t, path) + store := disk.New(path) + controller := newDMXDeviceController(logger, store) + req := httptest.NewRequest(http.MethodPost, rpcPath, nil) + entity := ds.DMXDevices[key] + + createReply := &cntl.DMXDevice{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + reply := &SuccessResponse{} + if err := controller.Delete(req, &IDRequest{ID: key}, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/server.go b/pkg/api/server.go index f9d90bf..4ff7227 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -59,7 +59,7 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { s.Server.RegisterCodec(json.NewCodec(), "application/json") r := http.NewServeMux() - r.Handle("/rpc", s.Server) + r.Handle(rpcPath, s.Server) r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(rw, "OK"); err != nil { s.logger.Errorf("failed to write content to response: %v", err) diff --git a/pkg/api/types.go b/pkg/api/types.go index c5768ea..d5d3056 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -3,6 +3,7 @@ package api import "errors" var ( + rpcPath = "/rpc" errNoIDGiven = errors.New("no ID was given with request") errExists = errors.New("entity with given ID already exists") errNotExists = errors.New("entity with given ID does not exist") diff --git a/pkg/internal/testing/cleanup.go b/pkg/internal/testing/cleanup.go new file mode 100644 index 0000000..fdfc7b7 --- /dev/null +++ b/pkg/internal/testing/cleanup.go @@ -0,0 +1,13 @@ +package testing + +import ( + "os" + "testing" +) + +// Cleanup a storage path after a test run. Method is meant to be run deferred +func Cleanup(t *testing.T, path string) { + if err := os.RemoveAll(path); err != nil { + t.Errorf("failed to remove test storage dir: %v", err) + } +} diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index ae9211b..e3dc82d 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -4,6 +4,7 @@ import ( "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/internal/fixtures" "github.com/StageAutoControl/controller/pkg/internal/stringslice" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "io/ioutil" "os" "path/filepath" @@ -19,11 +20,6 @@ var ( expectedContent = "{\"id\":\"35cae00a-0b17-11e7-8bca-bbf30c56f20e\",\"name\":\"LED-Bar below drums front\",\"typeId\":\"1555d67e-1187-11e7-8135-9b41038b5b75\",\"universe\":1,\"startChannel\":222,\"tags\":[\"bar\",\"drums\"]}" ) -func cleanup(t *testing.T, path string) { - if err := os.RemoveAll(path); err != nil { - t.Errorf("failed to remove test storage dir: %v", err) - } -} func TestStorage_buildFileName(t *testing.T) { storage := New(path) @@ -45,7 +41,7 @@ func TestStorage_getType(t *testing.T) { } func TestStorage_Write(t *testing.T) { - defer cleanup(t, path) + defer internalTesting.Cleanup(t, path) storage := New(path) err := storage.Write(key, device) @@ -71,7 +67,7 @@ func TestStorage_Write(t *testing.T) { } func TestStorage_Read(t *testing.T) { - defer cleanup(t, path) + defer internalTesting.Cleanup(t, path) storage := New(path) if err := os.MkdirAll(filepath.Dir(expectedFileName), 0755); err != nil { @@ -96,7 +92,7 @@ func TestStorage_Read(t *testing.T) { } func TestStorage_Has_Existing(t *testing.T) { - defer cleanup(t, path) + defer internalTesting.Cleanup(t, path) storage := New(path) if err := os.MkdirAll(filepath.Dir(expectedFileName), 0755); err != nil { @@ -116,7 +112,7 @@ func TestStorage_Has_Existing(t *testing.T) { } func TestStorage_Has_NotExisting(t *testing.T) { - defer cleanup(t, path) + defer internalTesting.Cleanup(t, path) storage := New(path) if err := os.MkdirAll(filepath.Dir(expectedFileName), 0755); err != nil { @@ -132,7 +128,7 @@ func TestStorage_Has_NotExisting(t *testing.T) { } func TestStorage_List(t *testing.T) { - defer cleanup(t, path) + defer internalTesting.Cleanup(t, path) storage := New(path) for k, dev := range ds.DMXDevices { @@ -156,7 +152,7 @@ func TestStorage_List(t *testing.T) { } func TestStorage_Delete(t *testing.T) { - defer cleanup(t, path) + defer internalTesting.Cleanup(t, path) storage := New(path) if err := os.MkdirAll(filepath.Dir(expectedFileName), 0755); err != nil { From 1dce1de0dbc0618777d387281cfa5036f983db10 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 10 Feb 2019 11:34:55 +0100 Subject: [PATCH 13/94] Add working DMXDeviceController test --- glide.lock | 11 ++- glide.yaml | 46 ++++----- pkg/api/dmx_device_controller.go | 19 ++-- pkg/api/dmx_device_controller_test.go | 130 ++++++++++++++------------ 4 files changed, 108 insertions(+), 98 deletions(-) diff --git a/glide.lock b/glide.lock index 67f1869..2855c99 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 983effd3e9ab87af045e04b98c0772d1100c2fc7b15f24264dd851be2464082a -updated: 2019-02-07T21:22:24.230622+01:00 +hash: ae7cf22751f01b31993152d74c88291f7ee660e0efb4111d0baf28077466857c +updated: 2019-02-10T11:12:25.802271+01:00 imports: - name: github.com/creasty/go-easing version: 0cfd96d3a544aad2e643739e4a4f6c081b12cda0 @@ -12,10 +12,13 @@ imports: - name: github.com/gorilla/rpc version: 22c016f3df3febe0c1f6727598b6389507e03a18 subpackages: + - json - v2 - v2/json - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/jinzhu/copier + version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet version: f0b9cc962f904f793900f27ba65ffc04b4a942eb subpackages: @@ -39,11 +42,11 @@ imports: - name: github.com/spf13/viper version: 6d33b5a963d922d182c91e8a1c88d81fd150cfd4 - name: golang.org/x/crypto - version: b8fe1690c61389d7d2a8074a507d1d40c5d30448 + version: 193df9c0f06f8bb35fba505183eaf0acc0136505 subpackages: - ssh/terminal - name: golang.org/x/sys - version: 41f3e6584952bb034a481797859f6ab34b6803bd + version: 3b5209105503162ded1863c307ac66fec31120dd subpackages: - unix - windows diff --git a/glide.yaml b/glide.yaml index 3903179..ce71538 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,25 +1,25 @@ package: github.com/StageAutoControl/controller import: - - package: github.com/sirupsen/logrus - - package: github.com/gordonklaus/portaudio - - package: github.com/gorilla/handlers - version: ^1.2.1 - - package: github.com/gorilla/rpc - version: ^1.1.0 - subpackages: - - v2 - - v2/json - - package: github.com/jsimonetti/go-artnet - # repo: https://github.com/StageAutoControl/go-artnet.git - subpackages: - - packet/code - - package: github.com/rakyll/portmidi - version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - - package: github.com/satori/go.uuid - version: ^1.1.0 - - package: github.com/spf13/cobra - - package: github.com/spf13/viper - version: ^1.0.0 - - package: github.com/creasty/go-easing - - package: github.com/peterbourgon/diskv - version: ^2.0.1 +- package: github.com/sirupsen/logrus +- package: github.com/gordonklaus/portaudio +- package: github.com/gorilla/handlers + version: ^1.2.1 +- package: github.com/gorilla/rpc + version: ^1.1.0 + subpackages: + - v2 + - v2/json +- package: github.com/jsimonetti/go-artnet + subpackages: + - packet/code +- package: github.com/rakyll/portmidi + version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 +- package: github.com/satori/go.uuid + version: ^1.1.0 +- package: github.com/spf13/cobra +- package: github.com/spf13/viper + version: ^1.0.0 +- package: github.com/creasty/go-easing +- package: github.com/peterbourgon/diskv + version: ^2.0.1 +- package: github.com/jinzhu/copier diff --git a/pkg/api/dmx_device_controller.go b/pkg/api/dmx_device_controller.go index 634617e..0fd7c15 100644 --- a/pkg/api/dmx_device_controller.go +++ b/pkg/api/dmx_device_controller.go @@ -2,10 +2,12 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxDeviceController struct { @@ -24,18 +26,17 @@ func newDMXDeviceController(logger *logrus.Entry, storage storage) *dmxDeviceCon func (c *dmxDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXDevice @@ -48,8 +49,7 @@ func (c *dmxDeviceController) Update(r *http.Request, entity *cntl.DMXDevice, re return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXDevice @@ -58,6 +58,7 @@ func (c *dmxDeviceController) Get(r *http.Request, idReq *IDRequest, reply *cntl return errNoIDGiven } + fmt.Println(idReq.ID) if !c.storage.Has(idReq.ID, &cntl.DMXDevice{}) { return errNotExists } diff --git a/pkg/api/dmx_device_controller_test.go b/pkg/api/dmx_device_controller_test.go index 642f572..04bfb18 100644 --- a/pkg/api/dmx_device_controller_test.go +++ b/pkg/api/dmx_device_controller_test.go @@ -1,109 +1,115 @@ package api import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/internal/fixtures" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" disk "github.com/StageAutoControl/controller/pkg/storage" + "github.com/jinzhu/copier" "github.com/sirupsen/logrus" - "math/rand" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" ) var ( - ds = fixtures.DataStore() - key = "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + logger *logrus.Entry + path string + store storage + key = "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + ds = fixtures.DataStore() + entity = ds.DMXDevices[key] + req = httptest.NewRequest(http.MethodPost, rpcPath, nil) ) +func init() { + var err error + logger = logrus.New().WithFields(logrus.Fields{}) + path, err = ioutil.TempDir("", "api_test") + if err != nil { + panic(err) + } + + store = disk.New(path) +} + func TestDmxDeviceController_Create_WithID(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - entity := ds.DMXDevices[key] - reply := &cntl.DMXDevice{} - if err := controller.Create(req, entity, reply); err != nil { + createReply := &cntl.DMXDevice{} + if err := controller.Create(req, entity, createReply); err != nil { t.Errorf("failed to call controller: %v", err) } - if reply.ID == "" { - t.Errorf("exptected to get reply with an ID set, but isn't") + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } } func TestDmxDeviceController_Create_WithoutID(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - entity := ds.DMXDevices[key] - entity.ID = "" - reply := &cntl.DMXDevice{} - if err := controller.Create(req, entity, reply); err != nil { + createEntity := &cntl.DMXDevice{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXDevice{} + if err := controller.Create(req, entity, createReply); err != nil { t.Errorf("failed to call controller: %v", err) } - if reply.ID == "" { - t.Errorf("exptected to get reply with an ID set, but isn't") + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } } func TestDmxDeviceController_Get_NotExisting(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) reply := &cntl.DMXDevice{} - if err := controller.Get(req, &IDRequest{ID: key}, reply); err != errNotExists { + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { t.Errorf("expected to get errNotExists, but got %v", err) } } func TestDmxDeviceController_Get_Existing(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { t.Errorf("failed to call controller: %v", err) } + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + reply := &cntl.DMXDevice{} - if err := controller.Get(req, &IDRequest{ID: key}, reply); err != nil { + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call controller: %v", err) } - if reply.ID == "" { - t.Errorf("exptected to get reply with an ID set, but isn't") + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) } } func TestDmxDeviceController_Update_NotExisting(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - entity := ds.DMXDevices[key] + reply := &cntl.DMXDevice{} if err := controller.Update(req, entity, reply); err != errNotExists { @@ -112,54 +118,54 @@ func TestDmxDeviceController_Update_NotExisting(t *testing.T) { } func TestDmxDeviceController_Update_Existing(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { t.Errorf("failed to call controller: %v", err) } + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + reply := &cntl.DMXDevice{} if err := controller.Update(req, entity, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } } func TestDmxDeviceController_Delete_NotExisting(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - reply := &SuccessResponse{} - if err := controller.Delete(req, &IDRequest{ID: key}, reply); err != errNotExists { + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { t.Errorf("expected to get errNotExists, but got %v", err) } } func TestDmxDeviceController_Delete_Existing(t *testing.T) { - logger := logrus.New().WithFields(logrus.Fields{}) - path := filepath.Join(os.TempDir(), "api_test", string(rand.Int63())) defer internalTesting.Cleanup(t, path) - store := disk.New(path) controller := newDMXDeviceController(logger, store) - req := httptest.NewRequest(http.MethodPost, rpcPath, nil) - entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { t.Errorf("failed to call controller: %v", err) } + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + reply := &SuccessResponse{} - if err := controller.Delete(req, &IDRequest{ID: key}, reply); err != nil { + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } From 251360a9118dc6805c1678b3e5586bc7c7c4c8bb Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 10 Feb 2019 11:40:51 +0100 Subject: [PATCH 14/94] Add DMXDeviceTypeController tests --- pkg/api/dmx_animation_controller.go | 3 +- pkg/api/dmx_device_controller_test.go | 58 +++----- pkg/api/dmx_device_group_controller.go | 3 +- pkg/api/dmx_device_type_controller.go | 3 +- pkg/api/dmx_device_type_controller_test.go | 163 +++++++++++++++++++++ pkg/api/dmx_preset_controller.go | 3 +- pkg/api/dmx_scene_controller.go | 3 +- pkg/api/dmx_transition_controller.go | 3 +- pkg/api/server.go | 19 +-- pkg/api/set_list_controller.go | 3 +- pkg/api/song_controller.go | 3 +- pkg/api/types.go | 2 +- pkg/api/types_test.go | 30 ++++ 13 files changed, 243 insertions(+), 53 deletions(-) create mode 100644 pkg/api/dmx_device_type_controller_test.go create mode 100644 pkg/api/types_test.go diff --git a/pkg/api/dmx_animation_controller.go b/pkg/api/dmx_animation_controller.go index a1bf773..55c3c3d 100644 --- a/pkg/api/dmx_animation_controller.go +++ b/pkg/api/dmx_animation_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxAnimationController struct { diff --git a/pkg/api/dmx_device_controller_test.go b/pkg/api/dmx_device_controller_test.go index 04bfb18..4222b5a 100644 --- a/pkg/api/dmx_device_controller_test.go +++ b/pkg/api/dmx_device_controller_test.go @@ -1,43 +1,18 @@ package api import ( - "io/ioutil" - "net/http" - "net/http/httptest" "testing" "github.com/StageAutoControl/controller/pkg/cntl" - "github.com/StageAutoControl/controller/pkg/internal/fixtures" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" - disk "github.com/StageAutoControl/controller/pkg/storage" "github.com/jinzhu/copier" - "github.com/sirupsen/logrus" ) -var ( - logger *logrus.Entry - path string - store storage - key = "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - ds = fixtures.DataStore() - entity = ds.DMXDevices[key] - req = httptest.NewRequest(http.MethodPost, rpcPath, nil) -) - -func init() { - var err error - logger = logrus.New().WithFields(logrus.Fields{}) - path, err = ioutil.TempDir("", "api_test") - if err != nil { - panic(err) - } - - store = disk.New(path) -} - -func TestDmxDeviceController_Create_WithID(t *testing.T) { +func TestDMXDeviceController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { @@ -49,9 +24,11 @@ func TestDmxDeviceController_Create_WithID(t *testing.T) { } } -func TestDmxDeviceController_Create_WithoutID(t *testing.T) { +func TestDMXDeviceController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + entity := ds.DMXDevices[key] createEntity := &cntl.DMXDevice{} if err := copier.Copy(createEntity, entity); err != nil { @@ -70,9 +47,11 @@ func TestDmxDeviceController_Create_WithoutID(t *testing.T) { } } -func TestDmxDeviceController_Get_NotExisting(t *testing.T) { +func TestDMXDeviceController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + reply := &cntl.DMXDevice{} idReq := &IDRequest{ID: key} @@ -81,9 +60,11 @@ func TestDmxDeviceController_Get_NotExisting(t *testing.T) { } } -func TestDmxDeviceController_Get_Existing(t *testing.T) { +func TestDMXDeviceController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { @@ -106,9 +87,11 @@ func TestDmxDeviceController_Get_Existing(t *testing.T) { } } -func TestDmxDeviceController_Update_NotExisting(t *testing.T) { +func TestDMXDeviceController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + entity := ds.DMXDevices[key] reply := &cntl.DMXDevice{} @@ -117,9 +100,11 @@ func TestDmxDeviceController_Update_NotExisting(t *testing.T) { } } -func TestDmxDeviceController_Update_Existing(t *testing.T) { +func TestDMXDeviceController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { @@ -139,9 +124,10 @@ func TestDmxDeviceController_Update_Existing(t *testing.T) { t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) } } -func TestDmxDeviceController_Delete_NotExisting(t *testing.T) { +func TestDMXDeviceController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" reply := &SuccessResponse{} idReq := &IDRequest{ID: key} @@ -150,9 +136,11 @@ func TestDmxDeviceController_Delete_NotExisting(t *testing.T) { } } -func TestDmxDeviceController_Delete_Existing(t *testing.T) { +func TestDMXDeviceController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) + key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + entity := ds.DMXDevices[key] createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { diff --git a/pkg/api/dmx_device_group_controller.go b/pkg/api/dmx_device_group_controller.go index 3ad17ab..2a4ce48 100644 --- a/pkg/api/dmx_device_group_controller.go +++ b/pkg/api/dmx_device_group_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxDeviceGroupController struct { diff --git a/pkg/api/dmx_device_type_controller.go b/pkg/api/dmx_device_type_controller.go index 05091e6..68cadd8 100644 --- a/pkg/api/dmx_device_type_controller.go +++ b/pkg/api/dmx_device_type_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxDeviceTypeController struct { diff --git a/pkg/api/dmx_device_type_controller_test.go b/pkg/api/dmx_device_type_controller_test.go new file mode 100644 index 0000000..e221f4c --- /dev/null +++ b/pkg/api/dmx_device_type_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXDeviceTypeController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + entity := ds.DMXDeviceTypes[key] + + createReply := &cntl.DMXDeviceType{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXDeviceTypeController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + entity := ds.DMXDeviceTypes[key] + + createEntity := &cntl.DMXDeviceType{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXDeviceType{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXDeviceTypeController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + + reply := &cntl.DMXDeviceType{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXDeviceTypeController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + entity := ds.DMXDeviceTypes[key] + + createReply := &cntl.DMXDeviceType{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXDeviceType{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXDeviceTypeController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + entity := ds.DMXDeviceTypes[key] + + reply := &cntl.DMXDeviceType{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXDeviceTypeController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + entity := ds.DMXDeviceTypes[key] + + createReply := &cntl.DMXDeviceType{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXDeviceType{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXDeviceTypeController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXDeviceTypeController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceTypeController(logger, store) + key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" + entity := ds.DMXDeviceTypes[key] + + createReply := &cntl.DMXDeviceType{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/dmx_preset_controller.go b/pkg/api/dmx_preset_controller.go index 54b139f..137a608 100644 --- a/pkg/api/dmx_preset_controller.go +++ b/pkg/api/dmx_preset_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxPresetController struct { diff --git a/pkg/api/dmx_scene_controller.go b/pkg/api/dmx_scene_controller.go index 74cc71e..1ba747b 100644 --- a/pkg/api/dmx_scene_controller.go +++ b/pkg/api/dmx_scene_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxSceneController struct { diff --git a/pkg/api/dmx_transition_controller.go b/pkg/api/dmx_transition_controller.go index 51bbf28..9575336 100644 --- a/pkg/api/dmx_transition_controller.go +++ b/pkg/api/dmx_transition_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type dmxTransitionController struct { diff --git a/pkg/api/server.go b/pkg/api/server.go index 4ff7227..a6b2a74 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -3,10 +3,11 @@ package api import ( "context" "fmt" + "net/http" + "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" "github.com/sirupsen/logrus" - "net/http" ) // Server represents the controllers API server, aware of all the controllers @@ -34,15 +35,15 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { func (s *Server) registerControllers() error { s.controller = map[string]interface{}{ - "DMXAnimation": newDMXAnimationController(s.logger, s.storage), - "DMXDevice": newDMXDeviceController(s.logger, s.storage), + "DMXAnimation": newDMXAnimationController(s.logger, s.storage), + "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), - "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), - "DMXPreset": newDMXPresetController(s.logger, s.storage), - "DMXScene": newDMXSceneController(s.logger, s.storage), - "DMXTransition": newDMXTransitionController(s.logger, s.storage), - "Song": newSongController(s.logger, s.storage), - "SetList": newSetListController(s.logger, s.storage), + "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), + "DMXPreset": newDMXPresetController(s.logger, s.storage), + "DMXScene": newDMXSceneController(s.logger, s.storage), + "DMXTransition": newDMXTransitionController(s.logger, s.storage), + "Song": newSongController(s.logger, s.storage), + "SetList": newSetListController(s.logger, s.storage), } for name, controller := range s.controller { diff --git a/pkg/api/set_list_controller.go b/pkg/api/set_list_controller.go index b97721e..2ded365 100644 --- a/pkg/api/set_list_controller.go +++ b/pkg/api/set_list_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type setListController struct { diff --git a/pkg/api/song_controller.go b/pkg/api/song_controller.go index d1bf3f3..12c1929 100644 --- a/pkg/api/song_controller.go +++ b/pkg/api/song_controller.go @@ -2,10 +2,11 @@ package api import ( "fmt" + "net/http" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" - "net/http" ) type songController struct { diff --git a/pkg/api/types.go b/pkg/api/types.go index d5d3056..18ba550 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -3,7 +3,7 @@ package api import "errors" var ( - rpcPath = "/rpc" + rpcPath = "/rpc" errNoIDGiven = errors.New("no ID was given with request") errExists = errors.New("entity with given ID already exists") errNotExists = errors.New("entity with given ID does not exist") diff --git a/pkg/api/types_test.go b/pkg/api/types_test.go new file mode 100644 index 0000000..bf6e88d --- /dev/null +++ b/pkg/api/types_test.go @@ -0,0 +1,30 @@ +package api + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + + "github.com/StageAutoControl/controller/pkg/internal/fixtures" + disk "github.com/StageAutoControl/controller/pkg/storage" + "github.com/sirupsen/logrus" +) + +var ( + logger *logrus.Entry + path string + store storage + ds = fixtures.DataStore() + req = httptest.NewRequest(http.MethodPost, rpcPath, nil) +) + +func init() { + var err error + logger = logrus.New().WithFields(logrus.Fields{}) + path, err = ioutil.TempDir("", "api_test") + if err != nil { + panic(err) + } + + store = disk.New(path) +} From 5bc04c7a58a31b0f7934b8d92e28ec6e61e8b533 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 10 Feb 2019 11:46:01 +0100 Subject: [PATCH 15/94] Add rest of the controller tests --- pkg/api/dmx_animation_controller_test.go | 163 ++++++++++++++++++++ pkg/api/dmx_device_group_controller_test.go | 163 ++++++++++++++++++++ pkg/api/dmx_preset_controller_test.go | 163 ++++++++++++++++++++ pkg/api/dmx_scene_controller_test.go | 163 ++++++++++++++++++++ pkg/api/dmx_transition_controller_test.go | 163 ++++++++++++++++++++ pkg/api/set_list_controller_test.go | 163 ++++++++++++++++++++ pkg/api/song_controller_test.go | 163 ++++++++++++++++++++ 7 files changed, 1141 insertions(+) create mode 100644 pkg/api/dmx_animation_controller_test.go create mode 100644 pkg/api/dmx_device_group_controller_test.go create mode 100644 pkg/api/dmx_preset_controller_test.go create mode 100644 pkg/api/dmx_scene_controller_test.go create mode 100644 pkg/api/dmx_transition_controller_test.go create mode 100644 pkg/api/set_list_controller_test.go create mode 100644 pkg/api/song_controller_test.go diff --git a/pkg/api/dmx_animation_controller_test.go b/pkg/api/dmx_animation_controller_test.go new file mode 100644 index 0000000..8e6f789 --- /dev/null +++ b/pkg/api/dmx_animation_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXAnimationController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + entity := ds.DMXAnimations[key] + + createReply := &cntl.DMXAnimation{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXAnimationController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + entity := ds.DMXAnimations[key] + + createEntity := &cntl.DMXAnimation{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXAnimation{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXAnimationController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + + reply := &cntl.DMXAnimation{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXAnimationController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + entity := ds.DMXAnimations[key] + + createReply := &cntl.DMXAnimation{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXAnimation{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXAnimationController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + entity := ds.DMXAnimations[key] + + reply := &cntl.DMXAnimation{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXAnimationController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + entity := ds.DMXAnimations[key] + + createReply := &cntl.DMXAnimation{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXAnimation{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXAnimationController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXAnimationController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXAnimationController(logger, store) + key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + entity := ds.DMXAnimations[key] + + createReply := &cntl.DMXAnimation{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/dmx_device_group_controller_test.go b/pkg/api/dmx_device_group_controller_test.go new file mode 100644 index 0000000..ac9fc05 --- /dev/null +++ b/pkg/api/dmx_device_group_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXDeviceGroupController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + entity := ds.DMXDeviceGroups[key] + + createReply := &cntl.DMXDeviceGroup{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXDeviceGroupController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + entity := ds.DMXDeviceGroups[key] + + createEntity := &cntl.DMXDeviceGroup{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXDeviceGroup{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXDeviceGroupController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + + reply := &cntl.DMXDeviceGroup{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXDeviceGroupController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + entity := ds.DMXDeviceGroups[key] + + createReply := &cntl.DMXDeviceGroup{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXDeviceGroup{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXDeviceGroupController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + entity := ds.DMXDeviceGroups[key] + + reply := &cntl.DMXDeviceGroup{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXDeviceGroupController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + entity := ds.DMXDeviceGroups[key] + + createReply := &cntl.DMXDeviceGroup{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXDeviceGroup{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXDeviceGroupController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXDeviceGroupController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXDeviceGroupController(logger, store) + key := "475b71a0-0b16-11e7-9406-e3f678e8b788" + entity := ds.DMXDeviceGroups[key] + + createReply := &cntl.DMXDeviceGroup{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/dmx_preset_controller_test.go b/pkg/api/dmx_preset_controller_test.go new file mode 100644 index 0000000..4525271 --- /dev/null +++ b/pkg/api/dmx_preset_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXPresetController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + entity := ds.DMXPresets[key] + + createReply := &cntl.DMXPreset{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXPresetController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + entity := ds.DMXPresets[key] + + createEntity := &cntl.DMXPreset{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXPreset{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXPresetController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + + reply := &cntl.DMXPreset{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXPresetController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + entity := ds.DMXPresets[key] + + createReply := &cntl.DMXPreset{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXPreset{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXPresetController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + entity := ds.DMXPresets[key] + + reply := &cntl.DMXPreset{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXPresetController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + entity := ds.DMXPresets[key] + + createReply := &cntl.DMXPreset{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXPreset{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXPresetController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXPresetController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXPresetController(logger, store) + key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + entity := ds.DMXPresets[key] + + createReply := &cntl.DMXPreset{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/dmx_scene_controller_test.go b/pkg/api/dmx_scene_controller_test.go new file mode 100644 index 0000000..9ff1b5f --- /dev/null +++ b/pkg/api/dmx_scene_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXSceneController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + entity := ds.DMXScenes[key] + + createReply := &cntl.DMXScene{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXSceneController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + entity := ds.DMXScenes[key] + + createEntity := &cntl.DMXScene{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXScene{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXSceneController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + + reply := &cntl.DMXScene{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXSceneController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + entity := ds.DMXScenes[key] + + createReply := &cntl.DMXScene{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXScene{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXSceneController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + entity := ds.DMXScenes[key] + + reply := &cntl.DMXScene{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXSceneController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + entity := ds.DMXScenes[key] + + createReply := &cntl.DMXScene{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXScene{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXSceneController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXSceneController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXSceneController(logger, store) + key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" + entity := ds.DMXScenes[key] + + createReply := &cntl.DMXScene{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/dmx_transition_controller_test.go b/pkg/api/dmx_transition_controller_test.go new file mode 100644 index 0000000..9643b0a --- /dev/null +++ b/pkg/api/dmx_transition_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXTransitionController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + entity := ds.DMXTransitions[key] + + createReply := &cntl.DMXTransition{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXTransitionController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + entity := ds.DMXTransitions[key] + + createEntity := &cntl.DMXTransition{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXTransition{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXTransitionController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + + reply := &cntl.DMXTransition{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXTransitionController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + entity := ds.DMXTransitions[key] + + createReply := &cntl.DMXTransition{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXTransition{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXTransitionController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + entity := ds.DMXTransitions[key] + + reply := &cntl.DMXTransition{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXTransitionController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + entity := ds.DMXTransitions[key] + + createReply := &cntl.DMXTransition{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXTransition{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXTransitionController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXTransitionController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXTransitionController(logger, store) + key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" + entity := ds.DMXTransitions[key] + + createReply := &cntl.DMXTransition{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/set_list_controller_test.go b/pkg/api/set_list_controller_test.go new file mode 100644 index 0000000..0b2c4c3 --- /dev/null +++ b/pkg/api/set_list_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestSetListController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + entity := ds.SetLists[key] + + createReply := &cntl.SetList{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestSetListController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + entity := ds.SetLists[key] + + createEntity := &cntl.SetList{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.SetList{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestSetListController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + + reply := &cntl.SetList{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestSetListController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + entity := ds.SetLists[key] + + createReply := &cntl.SetList{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.SetList{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestSetListController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + entity := ds.SetLists[key] + + reply := &cntl.SetList{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestSetListController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + entity := ds.SetLists[key] + + createReply := &cntl.SetList{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.SetList{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestSetListController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestSetListController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSetListController(logger, store) + key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" + entity := ds.SetLists[key] + + createReply := &cntl.SetList{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/song_controller_test.go b/pkg/api/song_controller_test.go new file mode 100644 index 0000000..49f5ad9 --- /dev/null +++ b/pkg/api/song_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestSongController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + entity := ds.Songs[key] + + createReply := &cntl.Song{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestSongController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + entity := ds.Songs[key] + + createEntity := &cntl.Song{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.Song{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestSongController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + + reply := &cntl.Song{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestSongController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + entity := ds.Songs[key] + + createReply := &cntl.Song{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.Song{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestSongController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + entity := ds.Songs[key] + + reply := &cntl.Song{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestSongController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + entity := ds.Songs[key] + + createReply := &cntl.Song{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.Song{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestSongController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestSongController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newSongController(logger, store) + key := "3c1065c8-0b14-11e7-96eb-5b134621c411" + entity := ds.Songs[key] + + createReply := &cntl.Song{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} From d1579108fdd686a3c92328a57bcc9b38315d1133 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 10 Feb 2019 11:47:56 +0100 Subject: [PATCH 16/94] sync changes on DMXDeviceController to rest of the controllers --- pkg/api/dmx_animation_controller.go | 15 +++++++-------- pkg/api/dmx_device_group_controller.go | 15 +++++++-------- pkg/api/dmx_device_type_controller.go | 15 +++++++-------- pkg/api/dmx_preset_controller.go | 15 +++++++-------- pkg/api/dmx_scene_controller.go | 15 +++++++-------- pkg/api/dmx_transition_controller.go | 15 +++++++-------- pkg/api/set_list_controller.go | 15 +++++++-------- pkg/api/song_controller.go | 15 +++++++-------- 8 files changed, 56 insertions(+), 64 deletions(-) diff --git a/pkg/api/dmx_animation_controller.go b/pkg/api/dmx_animation_controller.go index 55c3c3d..c49ced4 100644 --- a/pkg/api/dmx_animation_controller.go +++ b/pkg/api/dmx_animation_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newDMXAnimationController(logger *logrus.Entry, storage storage) *dmxAnimat func (c *dmxAnimationController) Create(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXAnimation @@ -49,8 +49,7 @@ func (c *dmxAnimationController) Update(r *http.Request, entity *cntl.DMXAnimati return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXAnimation diff --git a/pkg/api/dmx_device_group_controller.go b/pkg/api/dmx_device_group_controller.go index 2a4ce48..4420157 100644 --- a/pkg/api/dmx_device_group_controller.go +++ b/pkg/api/dmx_device_group_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newDMXDeviceGroupController(logger *logrus.Entry, storage storage) *dmxDevi func (c *dmxDeviceGroupController) Create(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXDeviceGroup @@ -49,8 +49,7 @@ func (c *dmxDeviceGroupController) Update(r *http.Request, entity *cntl.DMXDevic return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXDeviceGroup diff --git a/pkg/api/dmx_device_type_controller.go b/pkg/api/dmx_device_type_controller.go index 68cadd8..21f853d 100644 --- a/pkg/api/dmx_device_type_controller.go +++ b/pkg/api/dmx_device_type_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newDMXDeviceTypeController(logger *logrus.Entry, storage storage) *dmxDevic func (c *dmxDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXDeviceType @@ -49,8 +49,7 @@ func (c *dmxDeviceTypeController) Update(r *http.Request, entity *cntl.DMXDevice return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXDeviceType diff --git a/pkg/api/dmx_preset_controller.go b/pkg/api/dmx_preset_controller.go index 137a608..4653750 100644 --- a/pkg/api/dmx_preset_controller.go +++ b/pkg/api/dmx_preset_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newDMXPresetController(logger *logrus.Entry, storage storage) *dmxPresetCon func (c *dmxPresetController) Create(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXPreset @@ -49,8 +49,7 @@ func (c *dmxPresetController) Update(r *http.Request, entity *cntl.DMXPreset, re return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXPreset diff --git a/pkg/api/dmx_scene_controller.go b/pkg/api/dmx_scene_controller.go index 1ba747b..642a0fe 100644 --- a/pkg/api/dmx_scene_controller.go +++ b/pkg/api/dmx_scene_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newDMXSceneController(logger *logrus.Entry, storage storage) *dmxSceneContr func (c *dmxSceneController) Create(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXScene @@ -49,8 +49,7 @@ func (c *dmxSceneController) Update(r *http.Request, entity *cntl.DMXScene, repl return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXScene diff --git a/pkg/api/dmx_transition_controller.go b/pkg/api/dmx_transition_controller.go index 9575336..c078893 100644 --- a/pkg/api/dmx_transition_controller.go +++ b/pkg/api/dmx_transition_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newDMXTransitionController(logger *logrus.Entry, storage storage) *dmxTrans func (c *dmxTransitionController) Create(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new DMXTransition @@ -49,8 +49,7 @@ func (c *dmxTransitionController) Update(r *http.Request, entity *cntl.DMXTransi return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a DMXTransition diff --git a/pkg/api/set_list_controller.go b/pkg/api/set_list_controller.go index 2ded365..78be5d9 100644 --- a/pkg/api/set_list_controller.go +++ b/pkg/api/set_list_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newSetListController(logger *logrus.Entry, storage storage) *setListControl func (c *setListController) Create(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new SetList @@ -49,8 +49,7 @@ func (c *setListController) Update(r *http.Request, entity *cntl.SetList, reply return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a SetList diff --git a/pkg/api/song_controller.go b/pkg/api/song_controller.go index 12c1929..41aac6f 100644 --- a/pkg/api/song_controller.go +++ b/pkg/api/song_controller.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -25,18 +26,17 @@ func newSongController(logger *logrus.Entry, storage storage) *songController { func (c *songController) Create(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() - } else { - if c.storage.Has(entity.ID, entity) { - return errExists - } + } + + if c.storage.Has(entity.ID, entity) { + return errExists } if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Update a new Song @@ -49,8 +49,7 @@ func (c *songController) Update(r *http.Request, entity *cntl.Song, reply *cntl. return fmt.Errorf("failed to update to disk: %v", err) } - *reply = *entity - return nil + return copier.Copy(reply, entity) } // Get a Song From 3fb39f5524047061ba814ec6263633b5081effb3 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 14 Feb 2019 18:28:11 +0100 Subject: [PATCH 17/94] add API listening log info --- cmd/api.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/api.go b/cmd/api.go index 0570da9..be69487 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/StageAutoControl/controller/cmd/internal" "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/storage" @@ -32,14 +33,17 @@ var apiCmd = &cobra.Command{ logger.Fatal(err) } + endpoint := fmt.Sprintf("0.0.0.0:%d", port) ctx := internal.NewExitHandlerContext(logger.Logger) - if err := server.Run(ctx, fmt.Sprintf("0.0.0.0:%d", port)); err != nil { + + logger.Infof("listening on %s", endpoint) + + if err := server.Run(ctx, endpoint); err != nil { logger.Fatal(err) } }, } - func init() { RootCmd.AddCommand(apiCmd) From bc45433db209b58422754f5621df6cdce90b4477 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 14 Feb 2019 22:37:40 +0100 Subject: [PATCH 18/94] Add CORS handlers to API --- .gitignore | 1 + pkg/api/server.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 134ad23..254e7ab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ bin .envrc /controller tmp +/storage .glide diff --git a/pkg/api/server.go b/pkg/api/server.go index a6b2a74..0ef87c2 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/gorilla/handlers" "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" "github.com/sirupsen/logrus" @@ -67,9 +68,18 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { } }) + h := handlers.LoggingHandler(s.logger.Writer(), r) + h = handlers.RecoveryHandler()(h) + h = handlers.CORS( + handlers.AllowCredentials(), + handlers.AllowedOrigins([]string{"*"}), + handlers.AllowedMethods([]string{"POST", "GET"}), + handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}), + )(h) + httpServer := http.Server{ Addr: endpoint, - Handler: r, + Handler: h, } go func() { From 4d908d0482fc2a3d19094e46288def4f42a72f93 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 3 Mar 2019 14:05:31 +0100 Subject: [PATCH 19/94] Add validation to DMXDeviceController --- Makefile | 4 ++-- pkg/api/dmx_device_controller.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a939016..2c142b3 100644 --- a/Makefile +++ b/Makefile @@ -41,14 +41,14 @@ start-playback-artnet: build-darwin start-api: build-darwin ./bin/controller_darwin api \ - --data-dir "${SAC_DATA_DIR}" + -s ./storage build-all: build-darwin build-arm build-linux build-docker build-darwin: go build -o bin/controller_darwin . -build-linux: +build-linux: GOOS=linux CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o bin/controller_linux . build-arm: diff --git a/pkg/api/dmx_device_controller.go b/pkg/api/dmx_device_controller.go index 0fd7c15..d8147dd 100644 --- a/pkg/api/dmx_device_controller.go +++ b/pkg/api/dmx_device_controller.go @@ -22,6 +22,14 @@ func newDMXDeviceController(logger *logrus.Entry, storage storage) *dmxDeviceCon } } +func (c *dmxDeviceController) validate(entity *cntl.DMXDevice) error { + if !c.storage.Has(entity.TypeID, &cntl.DMXDeviceType{}) { + return fmt.Errorf("cannot save DMXDevice with non-existing DMXDeviceType %q", entity.TypeID) + } + + return nil +} + // Create a new DMXDevice func (c *dmxDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { if entity.ID == "" { @@ -32,6 +40,10 @@ func (c *dmxDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, re return errExists } + if err := c.validate(entity); err != nil { + return fmt.Errorf("failed to validate entity: %v", err) + } + if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } @@ -45,6 +57,10 @@ func (c *dmxDeviceController) Update(r *http.Request, entity *cntl.DMXDevice, re return errNotExists } + if err := c.validate(entity); err != nil { + return fmt.Errorf("failed to validate entity: %v", err) + } + if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to update to disk: %v", err) } From 07a232483ee96b86bb955189ddb52c94a6a6517c Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 3 Mar 2019 15:11:35 +0100 Subject: [PATCH 20/94] Fix controller tests --- pkg/api/dmx_device_controller_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/api/dmx_device_controller_test.go b/pkg/api/dmx_device_controller_test.go index 4222b5a..d664360 100644 --- a/pkg/api/dmx_device_controller_test.go +++ b/pkg/api/dmx_device_controller_test.go @@ -8,7 +8,15 @@ import ( "github.com/jinzhu/copier" ) +func createDeviceType(t *testing.T) { + err := store.Write("1555d67e-1187-11e7-8135-9b41038b5b75", ds.DMXDeviceTypes["1555d67e-1187-11e7-8135-9b41038b5b75"]) + if err != nil { + t.Fatalf("failed to create DMXDeviceType: %v", err) + } +} + func TestDMXDeviceController_Create_WithID(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -25,6 +33,7 @@ func TestDMXDeviceController_Create_WithID(t *testing.T) { } func TestDMXDeviceController_Create_WithoutID(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -48,6 +57,7 @@ func TestDMXDeviceController_Create_WithoutID(t *testing.T) { } func TestDMXDeviceController_Get_NotExisting(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -61,6 +71,7 @@ func TestDMXDeviceController_Get_NotExisting(t *testing.T) { } func TestDMXDeviceController_Get_Existing(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -88,6 +99,7 @@ func TestDMXDeviceController_Get_Existing(t *testing.T) { } func TestDMXDeviceController_Update_NotExisting(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -101,6 +113,7 @@ func TestDMXDeviceController_Update_NotExisting(t *testing.T) { } func TestDMXDeviceController_Update_Existing(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -125,6 +138,7 @@ func TestDMXDeviceController_Update_Existing(t *testing.T) { } } func TestDMXDeviceController_Delete_NotExisting(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" @@ -137,6 +151,7 @@ func TestDMXDeviceController_Delete_NotExisting(t *testing.T) { } func TestDMXDeviceController_Delete_Existing(t *testing.T) { + createDeviceType(t) defer internalTesting.Cleanup(t, path) controller := newDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" From 2b6bc13ed70e3c8a9d1c98d8517c3000bed71265 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 3 Mar 2019 15:35:43 +0100 Subject: [PATCH 21/94] Add DMXColorVariable implementation --- pkg/cntl/dmx/errors.go | 13 +++++----- pkg/cntl/dmx/params.go | 37 ++++++++++++++++++++++++++ pkg/cntl/dmx/params_test.go | 17 ++++++++++-- pkg/cntl/loader.go | 40 ++++++++++++++-------------- pkg/cntl/types.go | 31 ++++++++++++++-------- pkg/cntl/types_equals.go | 1 - pkg/internal/fixtures/fixtures.go | 43 ++++++++++++++++++++++--------- 7 files changed, 131 insertions(+), 51 deletions(-) diff --git a/pkg/cntl/dmx/errors.go b/pkg/cntl/dmx/errors.go index 8a0f862..1831427 100644 --- a/pkg/cntl/dmx/errors.go +++ b/pkg/cntl/dmx/errors.go @@ -9,10 +9,11 @@ var ( ErrDeviceHasDisabledDimmerChannel = errors.New("device has disabled Dimmer channel") ErrDeviceIsNotMoving = errors.New("device is not moving, cannot use tilt and pan") - ErrDeviceParamsDevicesInvalid = errors.New("DMXDeviceParams must have either a group or a device") - ErrDeviceParamsValuesInvalid = errors.New("DMXDeviceParams must not have more the one of [Animation, Transition, Params]") - ErrDeviceParamsNoDevices = errors.New("DMXDeviceParams matches no device") - ErrTransitionDeviceParamsMustMatchLED = errors.New("DMXTransition contains a param set where the LED is not the same") - ErrDeviceSelectorMustHaveTagsOrID = errors.New("DMXDeviceSelector must have either tags or an ID") - ErrDeviceSelectorCannotHaveTagsAndID = errors.New("DMXDeviceSelector cannot have tags and an ID") + ErrDeviceParamsDevicesInvalid = errors.New("DMXDeviceParams must have either a group or a device") + ErrDeviceParamsValuesInvalid = errors.New("DMXDeviceParams must not have more the one of [Animation, Transition, Params]") + ErrDeviceParamsNoDevices = errors.New("DMXDeviceParams matches no device") + ErrDeviceParamsColorVarMustBeExclusive = errors.New("DMXDeviceParams cannot have a $color var and one of [red, green, blue, white]") + ErrTransitionDeviceParamsMustMatchLED = errors.New("DMXTransition contains a param set where the LED is not the same") + ErrDeviceSelectorMustHaveTagsOrID = errors.New("DMXDeviceSelector must have either tags or an ID") + ErrDeviceSelectorCannotHaveTagsAndID = errors.New("DMXDeviceSelector cannot have tags and an ID") ) diff --git a/pkg/cntl/dmx/params.go b/pkg/cntl/dmx/params.go index 884fbaa..64b4f36 100644 --- a/pkg/cntl/dmx/params.go +++ b/pkg/cntl/dmx/params.go @@ -3,6 +3,7 @@ package dmx import ( "errors" "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" ) @@ -113,6 +114,10 @@ func RenderDeviceParams(ds *cntl.DataStore, dp *cntl.DMXDeviceParams) ([]cntl.DM func RenderParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, p cntl.DMXParams) (cmds cntl.DMXCommands, err error) { var channels cntl.DMXCommands + if err := resolveColorVar(ds, &p); err != nil { + return cntl.DMXCommands{}, err + } + if p.Red != nil { channels = append(channels, cntl.DMXCommand{ Channel: ChannelRed, @@ -184,3 +189,35 @@ func RenderParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, p cntl.DMXParams) (c return } + +func resolveColorVar(ds *cntl.DataStore, p *cntl.DMXParams) error { + if p.ColorVar == nil || *p.ColorVar == "" { + return nil + } + + if p.Red != nil || p.Green != nil || p.Blue != nil || p.White != nil { + return ErrDeviceParamsColorVarMustBeExclusive + } + + colorVar := getColorVar(ds, *p.ColorVar) + if colorVar == nil { + return fmt.Errorf("failed to find color variable with the name %q", *p.ColorVar) + } + + p.Red = colorVar.Red + p.Green = colorVar.Green + p.Blue = colorVar.Blue + p.White = colorVar.White + + return nil +} + +func getColorVar(ds *cntl.DataStore, name string) *cntl.DMXColorVariable { + for _, c := range ds.DmxColorVariables { + if c.Name == name { + return c + } + } + + return nil +} diff --git a/pkg/cntl/dmx/params_test.go b/pkg/cntl/dmx/params_test.go index c433529..49fefae 100644 --- a/pkg/cntl/dmx/params_test.go +++ b/pkg/cntl/dmx/params_test.go @@ -2,9 +2,10 @@ package dmx import ( "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) @@ -54,7 +55,7 @@ func TestCheckDeviceParams(t *testing.T) { Device: &cntl.DMXDeviceSelector{ID: "asdf"}, Animation: &cntl.AnimationSelector{ID: "anim1"}, Params: []cntl.DMXParams{ - {Blue: fixtures.Value255},}, + {Blue: fixtures.Value255}}, }, expectedErr: ErrDeviceParamsValuesInvalid, }, @@ -247,6 +248,18 @@ func TestRenderParams(t *testing.T) { {Universe: 2, Channel: 14, Value: cntl.DMXValue{Value: 255}}, }, }, + { + ds: []*cntl.DMXDevice{ + ds.DMXDevices["4a545466-0b17-11e7-9c61-d3c0693099ab"], + }, + p: cntl.DMXParams{ + ColorVar: fixtures.StrPtr("Red255"), + }, + count: 1, + cmds: cntl.DMXCommands{ + {Universe: 2, Channel: 14, Value: cntl.DMXValue{Value: 255}}, + }, + }, { ds: []*cntl.DMXDevice{ ds.DMXDevices["4a545466-0b17-11e7-9c61-d3c0693099ab"], diff --git a/pkg/cntl/loader.go b/pkg/cntl/loader.go index 9c8a269..b4cf0f0 100644 --- a/pkg/cntl/loader.go +++ b/pkg/cntl/loader.go @@ -1,29 +1,31 @@ package cntl -// A DataStore holds the applications data state +// A DataStore holds the controllers data state during playback, or more specifically during the rendering of a song into DMX frames type DataStore struct { - SetLists map[string]*SetList - Songs map[string]*Song - DMXScenes map[string]*DMXScene - DMXPresets map[string]*DMXPreset - DMXAnimations map[string]*DMXAnimation - DMXTransitions map[string]*DMXTransition - DMXDevices map[string]*DMXDevice - DMXDeviceTypes map[string]*DMXDeviceType - DMXDeviceGroups map[string]*DMXDeviceGroup + SetLists map[string]*SetList + Songs map[string]*Song + DMXScenes map[string]*DMXScene + DMXPresets map[string]*DMXPreset + DMXAnimations map[string]*DMXAnimation + DMXTransitions map[string]*DMXTransition + DMXDevices map[string]*DMXDevice + DMXDeviceTypes map[string]*DMXDeviceType + DMXDeviceGroups map[string]*DMXDeviceGroup + DmxColorVariables map[string]*DMXColorVariable } // NewStore creates a new DataStore instance func NewStore() *DataStore { return &DataStore{ - SetLists: make(map[string]*SetList), - Songs: make(map[string]*Song), - DMXScenes: make(map[string]*DMXScene), - DMXPresets: make(map[string]*DMXPreset), - DMXAnimations: make(map[string]*DMXAnimation), - DMXTransitions: make(map[string]*DMXTransition), - DMXDevices: make(map[string]*DMXDevice), - DMXDeviceTypes: make(map[string]*DMXDeviceType), - DMXDeviceGroups: make(map[string]*DMXDeviceGroup), + SetLists: make(map[string]*SetList), + Songs: make(map[string]*Song), + DMXScenes: make(map[string]*DMXScene), + DMXPresets: make(map[string]*DMXPreset), + DMXAnimations: make(map[string]*DMXAnimation), + DMXTransitions: make(map[string]*DMXTransition), + DMXDevices: make(map[string]*DMXDevice), + DMXDeviceTypes: make(map[string]*DMXDeviceType), + DMXDeviceGroups: make(map[string]*DMXDeviceGroup), + DmxColorVariables: make(map[string]*DMXColorVariable), } } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index a98a4a3..6aebbcc 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -66,7 +66,6 @@ type DMXDevice struct { type DMXDeviceType struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` - Key string `json:"key" yaml:"key"` ChannelCount uint16 `json:"addressCount" yaml:"addressCount"` ChannelsPerLED uint16 `json:"channelsPerLED" yaml:"channelsPerLED"` StrobeEnabled bool `json:"strobeEnabled" yaml:"strobeEnabled"` @@ -153,18 +152,28 @@ type DMXSubScene struct { Preset *PresetSelector `json:"preset" yaml:"preset"` } +type DMXColorVariable struct { + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + Red *DMXValue `json:"red" yaml:"red"` + Green *DMXValue `json:"green" yaml:"green"` + Blue *DMXValue `json:"blue" yaml:"blue"` + White *DMXValue `json:"white" yaml:"white"` +} + // DMXParams is a DMX parameter object type DMXParams struct { - LED uint16 `json:"led" yaml:"led"` - Red *DMXValue `json:"red" yaml:"red"` - Green *DMXValue `json:"green" yaml:"green"` - Blue *DMXValue `json:"blue" yaml:"blue"` - White *DMXValue `json:"white" yaml:"white"` - Pan *DMXValue `json:"pan" yaml:"pan"` - Tilt *DMXValue `json:"tilt" yaml:"tilt"` - Strobe *DMXValue `json:"strobe" yaml:"strobe"` - Mode *DMXValue `json:"preset" yaml:"preset"` - Dimmer *DMXValue `json:"dimmer" yaml:"dimmer"` + LED uint16 `json:"led" yaml:"led"` + ColorVar *string `json:"$color" yaml:"$color"` + Red *DMXValue `json:"red" yaml:"red"` + Green *DMXValue `json:"green" yaml:"green"` + Blue *DMXValue `json:"blue" yaml:"blue"` + White *DMXValue `json:"white" yaml:"white"` + Pan *DMXValue `json:"pan" yaml:"pan"` + Tilt *DMXValue `json:"tilt" yaml:"tilt"` + Strobe *DMXValue `json:"strobe" yaml:"strobe"` + Mode *DMXValue `json:"preset" yaml:"preset"` + Dimmer *DMXValue `json:"dimmer" yaml:"dimmer"` } // DMXAnimation is an animation of dmx params in relation to time diff --git a/pkg/cntl/types_equals.go b/pkg/cntl/types_equals.go index 2f86e0b..9a7ebe2 100644 --- a/pkg/cntl/types_equals.go +++ b/pkg/cntl/types_equals.go @@ -55,7 +55,6 @@ func (v1 *DMXDevice) Equals(v2 *DMXDevice) bool { func (v1 *DMXDeviceType) Equals(v2 *DMXDeviceType) bool { return v1.ID == v2.ID && v1.Name == v2.Name && - v1.Key == v2.Key && v1.ChannelCount == v2.ChannelCount && v1.ChannelsPerLED == v2.ChannelsPerLED && v1.StrobeEnabled == v2.StrobeEnabled && diff --git a/pkg/internal/fixtures/fixtures.go b/pkg/internal/fixtures/fixtures.go index 9ae60a6..c274915 100644 --- a/pkg/internal/fixtures/fixtures.go +++ b/pkg/internal/fixtures/fixtures.go @@ -6,14 +6,19 @@ import ( // fixture values var ( - Value0 = &cntl.DMXValue{0} - Value31 = &cntl.DMXValue{31} - Value63 = &cntl.DMXValue{63} - Value127 = &cntl.DMXValue{127} - Value200 = &cntl.DMXValue{200} - Value255 = &cntl.DMXValue{255} + Value0 = &cntl.DMXValue{Value: 0} + Value31 = &cntl.DMXValue{Value: 31} + Value63 = &cntl.DMXValue{Value: 63} + Value127 = &cntl.DMXValue{Value: 127} + Value200 = &cntl.DMXValue{Value: 200} + Value255 = &cntl.DMXValue{Value: 255} ) +// StrPtr returns a pointer to the given string +func StrPtr(str string) *string { + return &str +} + var data = &cntl.DataStore{ SetLists: map[string]*cntl.SetList{ "f5b4be8a-0b18-11e7-b837-4bac99d86956": { @@ -52,7 +57,7 @@ var data = &cntl.DataStore{ ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", }, Params: []cntl.DMXParams{ - {Red: Value255}, + {ColorVar: StrPtr("Red255")}, }, }, }}, @@ -65,7 +70,7 @@ var data = &cntl.DataStore{ ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", }, Params: []cntl.DMXParams{ - {Blue: Value255}, + {ColorVar: StrPtr("Blue255")}, }, }, }}, @@ -78,7 +83,7 @@ var data = &cntl.DataStore{ ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", }, Params: []cntl.DMXParams{ - {Green: Value255}, + {ColorVar: StrPtr("Green255")}, }, }, }}, @@ -228,7 +233,6 @@ var data = &cntl.DataStore{ "1555d67e-1187-11e7-8135-9b41038b5b75": { ID: "1555d67e-1187-11e7-8135-9b41038b5b75", Name: "LED-Bar 67 Channel", - Key: "LEDBar67", ChannelCount: 67, ChannelsPerLED: 4, ModeEnabled: true, @@ -259,7 +263,6 @@ var data = &cntl.DataStore{ "628fc3ea-1188-11e7-8824-5f72d80c17b6": { ID: "628fc3ea-1188-11e7-8824-5f72d80c17b6", Name: "PAR 5 channel", - Key: "PAR5", ChannelCount: 5, ChannelsPerLED: 3, ModeEnabled: false, @@ -275,7 +278,6 @@ var data = &cntl.DataStore{ "5ccc43ee-118c-11e7-8d53-974b41748b71": { ID: "5ccc43ee-118c-11e7-8d53-974b41748b71", Name: "Strobe", - Key: "Strobe", StrobeEnabled: true, StrobeChannel: 0, }, @@ -422,6 +424,23 @@ var data = &cntl.DataStore{ }, }, }, + DmxColorVariables: map[string]*cntl.DMXColorVariable{ + "4b848ea8-5094-4509-a067-09a0e568220d": { + ID: "4b848ea8-5094-4509-a067-09a0e568220d", + Name: "Red255", + Red: Value255, + }, + "dbd2dbca-e680-4a25-a758-0dbf5c847932": { + ID: "dbd2dbca-e680-4a25-a758-0dbf5c847932", + Name: "Blue255", + Blue: Value255, + }, + "10484f74-fc19-47c1-a21c-202d0ffbe66b": { + ID: "10484f74-fc19-47c1-a21c-202d0ffbe66b", + Name: "Green255", + Green: Value255, + }, + }, } // DataStore returns the go object representation of a working set of fixtures From c8ca812be140f6b8a8bcbc16aeafcba65552d858 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 3 Mar 2019 15:39:24 +0100 Subject: [PATCH 22/94] Add DMXColorVariable API controller --- pkg/api/dmx_color_variable_controller.go | 102 +++++++++++ pkg/api/dmx_color_variable_controller_test.go | 163 ++++++++++++++++++ pkg/api/server.go | 19 +- 3 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 pkg/api/dmx_color_variable_controller.go create mode 100644 pkg/api/dmx_color_variable_controller_test.go diff --git a/pkg/api/dmx_color_variable_controller.go b/pkg/api/dmx_color_variable_controller.go new file mode 100644 index 0000000..1ac3f2e --- /dev/null +++ b/pkg/api/dmx_color_variable_controller.go @@ -0,0 +1,102 @@ +package api + +import ( + "fmt" + "net/http" + + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/jinzhu/copier" + "github.com/satori/go.uuid" + "github.com/sirupsen/logrus" +) + +type dmxColorVariableController struct { + logger *logrus.Entry + storage storage +} + +func newDMXColorVariableController(logger *logrus.Entry, storage storage) *dmxColorVariableController { + return &dmxColorVariableController{ + logger: logger, + storage: storage, + } +} + +// Create a new DMXColorVariable +func (c *dmxColorVariableController) Create(r *http.Request, entity *cntl.DMXColorVariable, reply *cntl.DMXColorVariable) error { + if entity.ID == "" { + entity.ID = uuid.NewV4().String() + } + + if c.storage.Has(entity.ID, entity) { + return errExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to write to disk: %v", err) + } + + return copier.Copy(reply, entity) +} + +// Update a new DMXColorVariable +func (c *dmxColorVariableController) Update(r *http.Request, entity *cntl.DMXColorVariable, reply *cntl.DMXColorVariable) error { + if !c.storage.Has(entity.ID, entity) { + return errNotExists + } + + if err := c.storage.Write(entity.ID, entity); err != nil { + return fmt.Errorf("failed to update to disk: %v", err) + } + + return copier.Copy(reply, entity) +} + +// Get a DMXColorVariable +func (c *dmxColorVariableController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXColorVariable) error { + if idReq.ID == "" { + return errNoIDGiven + } + + fmt.Println(idReq.ID) + if !c.storage.Has(idReq.ID, &cntl.DMXColorVariable{}) { + return errNotExists + } + + if err := c.storage.Read(idReq.ID, reply); err != nil { + return fmt.Errorf("failed to read entity: %v", err) + } + + return nil +} + +// GetAll returns all entities of DMXColorVariable +func (c *dmxColorVariableController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXColorVariable) error { + for _, id := range c.storage.List(&cntl.DMXColorVariable{}) { + entity := &cntl.DMXColorVariable{} + if err := c.storage.Read(id, entity); err != nil { + return fmt.Errorf("failed to read entity %s: %v", id, err) + } + *reply = append(*reply, entity) + } + + return nil +} + +// Delete a DMXColorVariable +func (c *dmxColorVariableController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { + if idReq.ID == "" { + return errNoIDGiven + } + + if !c.storage.Has(idReq.ID, &cntl.DMXColorVariable{}) { + return errNotExists + } + + if err := c.storage.Delete(idReq.ID, &cntl.DMXColorVariable{}); err != nil { + return fmt.Errorf("failed to delete entity: %v", err) + } + + reply.Success = true + return nil +} diff --git a/pkg/api/dmx_color_variable_controller_test.go b/pkg/api/dmx_color_variable_controller_test.go new file mode 100644 index 0000000..6c340c1 --- /dev/null +++ b/pkg/api/dmx_color_variable_controller_test.go @@ -0,0 +1,163 @@ +package api + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" + "github.com/jinzhu/copier" +) + +func TestDMXColorVariableController_Create_WithID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + entity := ds.DmxColorVariables[key] + + createReply := &cntl.DMXColorVariable{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXColorVariableController_Create_WithoutID(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + entity := ds.DmxColorVariables[key] + + createEntity := &cntl.DMXColorVariable{} + if err := copier.Copy(createEntity, entity); err != nil { + t.Fatal(err) + } + + createEntity.ID = "" + + createReply := &cntl.DMXColorVariable{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } +} + +func TestDMXColorVariableController_Get_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + + reply := &cntl.DMXColorVariable{} + + idReq := &IDRequest{ID: key} + if err := controller.Get(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXColorVariableController_Get_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + entity := ds.DmxColorVariables[key] + + createReply := &cntl.DMXColorVariable{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXColorVariable{} + idReq := &IDRequest{ID: key} + t.Log("idReq has ID:", idReq.ID) + if err := controller.Get(req, idReq, reply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} + +func TestDMXColorVariableController_Update_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + entity := ds.DmxColorVariables[key] + + reply := &cntl.DMXColorVariable{} + + if err := controller.Update(req, entity, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXColorVariableController_Update_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + entity := ds.DmxColorVariables[key] + + createReply := &cntl.DMXColorVariable{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &cntl.DMXColorVariable{} + if err := controller.Update(req, entity, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if reply.ID != key { + t.Errorf("Expected reply to have id %s, but has %s", key, reply.ID) + } +} +func TestDMXColorVariableController_Delete_NotExisting(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != errNotExists { + t.Errorf("expected to get errNotExists, but got %v", err) + } +} + +func TestDMXColorVariableController_Delete_Existing(t *testing.T) { + defer internalTesting.Cleanup(t, path) + controller := newDMXColorVariableController(logger, store) + key := "4b848ea8-5094-4509-a067-09a0e568220d" + entity := ds.DmxColorVariables[key] + + createReply := &cntl.DMXColorVariable{} + if err := controller.Create(req, entity, createReply); err != nil { + t.Errorf("failed to call controller: %v", err) + } + + if createReply.ID != key { + t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) + } + + reply := &SuccessResponse{} + idReq := &IDRequest{ID: key} + if err := controller.Delete(req, idReq, reply); err != nil { + t.Errorf("expected to get no error, but got %v", err) + } + + if !reply.Success { + t.Error("Expected to get result true, but got false") + } +} diff --git a/pkg/api/server.go b/pkg/api/server.go index 0ef87c2..14e8734 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -36,15 +36,16 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { func (s *Server) registerControllers() error { s.controller = map[string]interface{}{ - "DMXAnimation": newDMXAnimationController(s.logger, s.storage), - "DMXDevice": newDMXDeviceController(s.logger, s.storage), - "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), - "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), - "DMXPreset": newDMXPresetController(s.logger, s.storage), - "DMXScene": newDMXSceneController(s.logger, s.storage), - "DMXTransition": newDMXTransitionController(s.logger, s.storage), - "Song": newSongController(s.logger, s.storage), - "SetList": newSetListController(s.logger, s.storage), + "DMXAnimation": newDMXAnimationController(s.logger, s.storage), + "DMXDevice": newDMXDeviceController(s.logger, s.storage), + "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), + "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), + "DMXPreset": newDMXPresetController(s.logger, s.storage), + "DMXScene": newDMXSceneController(s.logger, s.storage), + "DMXTransition": newDMXTransitionController(s.logger, s.storage), + "DMXColorVariable": newDMXColorVariableController(s.logger, s.storage), + "Song": newSongController(s.logger, s.storage), + "SetList": newSetListController(s.logger, s.storage), } for name, controller := range s.controller { From c49071fd312b5b44c5e95d5115547da1c0a015d0 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 3 Mar 2019 15:47:11 +0100 Subject: [PATCH 23/94] Fix datastore color variables key --- pkg/api/dmx_color_variable_controller_test.go | 12 ++++++------ pkg/cntl/dmx/params.go | 2 +- pkg/cntl/loader.go | 4 ++-- pkg/internal/fixtures/fixtures.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/api/dmx_color_variable_controller_test.go b/pkg/api/dmx_color_variable_controller_test.go index 6c340c1..e808533 100644 --- a/pkg/api/dmx_color_variable_controller_test.go +++ b/pkg/api/dmx_color_variable_controller_test.go @@ -12,7 +12,7 @@ func TestDMXColorVariableController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - entity := ds.DmxColorVariables[key] + entity := ds.DMXColorVariables[key] createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { @@ -28,7 +28,7 @@ func TestDMXColorVariableController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - entity := ds.DmxColorVariables[key] + entity := ds.DMXColorVariables[key] createEntity := &cntl.DMXColorVariable{} if err := copier.Copy(createEntity, entity); err != nil { @@ -64,7 +64,7 @@ func TestDMXColorVariableController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - entity := ds.DmxColorVariables[key] + entity := ds.DMXColorVariables[key] createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { @@ -91,7 +91,7 @@ func TestDMXColorVariableController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - entity := ds.DmxColorVariables[key] + entity := ds.DMXColorVariables[key] reply := &cntl.DMXColorVariable{} @@ -104,7 +104,7 @@ func TestDMXColorVariableController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - entity := ds.DmxColorVariables[key] + entity := ds.DMXColorVariables[key] createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { @@ -140,7 +140,7 @@ func TestDMXColorVariableController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) controller := newDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - entity := ds.DmxColorVariables[key] + entity := ds.DMXColorVariables[key] createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { diff --git a/pkg/cntl/dmx/params.go b/pkg/cntl/dmx/params.go index 64b4f36..5d78bf6 100644 --- a/pkg/cntl/dmx/params.go +++ b/pkg/cntl/dmx/params.go @@ -213,7 +213,7 @@ func resolveColorVar(ds *cntl.DataStore, p *cntl.DMXParams) error { } func getColorVar(ds *cntl.DataStore, name string) *cntl.DMXColorVariable { - for _, c := range ds.DmxColorVariables { + for _, c := range ds.DMXColorVariables { if c.Name == name { return c } diff --git a/pkg/cntl/loader.go b/pkg/cntl/loader.go index b4cf0f0..2be9932 100644 --- a/pkg/cntl/loader.go +++ b/pkg/cntl/loader.go @@ -11,7 +11,7 @@ type DataStore struct { DMXDevices map[string]*DMXDevice DMXDeviceTypes map[string]*DMXDeviceType DMXDeviceGroups map[string]*DMXDeviceGroup - DmxColorVariables map[string]*DMXColorVariable + DMXColorVariables map[string]*DMXColorVariable } // NewStore creates a new DataStore instance @@ -26,6 +26,6 @@ func NewStore() *DataStore { DMXDevices: make(map[string]*DMXDevice), DMXDeviceTypes: make(map[string]*DMXDeviceType), DMXDeviceGroups: make(map[string]*DMXDeviceGroup), - DmxColorVariables: make(map[string]*DMXColorVariable), + DMXColorVariables: make(map[string]*DMXColorVariable), } } diff --git a/pkg/internal/fixtures/fixtures.go b/pkg/internal/fixtures/fixtures.go index c274915..aa9bffe 100644 --- a/pkg/internal/fixtures/fixtures.go +++ b/pkg/internal/fixtures/fixtures.go @@ -424,7 +424,7 @@ var data = &cntl.DataStore{ }, }, }, - DmxColorVariables: map[string]*cntl.DMXColorVariable{ + DMXColorVariables: map[string]*cntl.DMXColorVariable{ "4b848ea8-5094-4509-a067-09a0e568220d": { ID: "4b848ea8-5094-4509-a067-09a0e568220d", Name: "Red255", From cc044d5f0196f9030dfb8416186ec6c5fea60b89 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 4 Mar 2019 02:47:54 +0100 Subject: [PATCH 24/94] Fix storage listing when multiple types are stored --- cmd/api.go | 12 +++++++++++- pkg/storage/storage.go | 18 ++++++++---------- pkg/storage/storage_test.go | 12 ++++++------ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/cmd/api.go b/cmd/api.go index be69487..0dcf896 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -2,6 +2,8 @@ package cmd import ( "fmt" + "os" + "path/filepath" "github.com/StageAutoControl/controller/cmd/internal" "github.com/StageAutoControl/controller/pkg/api" @@ -13,7 +15,6 @@ import ( var apiCmd = &cobra.Command{ Use: "api", Short: "Opens the RPC API to manage the data and control the processes", - Long: ``, Run: func(cmd *cobra.Command, args []string) { logger := Logger.WithField("module", "api") @@ -22,6 +23,15 @@ var apiCmd = &cobra.Command{ logger.Fatal(err) } + cwd, err := os.Getwd() + if err != nil { + logger.Fatal(err) + } + storagePath = filepath.Clean(filepath.Join(cwd, storagePath)) + if err != nil { + logger.Fatal(err) + } + store := storage.New(storagePath) server, err := api.NewServer(logger, store) if err != nil { diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 8b15e9e..df49e21 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -3,11 +3,12 @@ package storage import ( "encoding/json" "fmt" - "github.com/StageAutoControl/controller/pkg/internal/stringslice" "os" "reflect" "strings" + "github.com/StageAutoControl/controller/pkg/internal/stringslice" + "github.com/peterbourgon/diskv" ) @@ -18,7 +19,7 @@ type Storage struct { func transform(s string) []string { parts := strings.Split(s, "_") - if len(parts) == 0 { + if len(parts) == 1 { return parts } @@ -42,8 +43,7 @@ func (s *Storage) buildFileName(key string, value interface{}) string { // Has returns weather the storage has the given entity or not func (s *Storage) Has(key string, kind interface{}) bool { - keys := s.listWithPrefix(s.buildFileName(key, kind), kind) - return stringslice.Contains(key, keys) + return stringslice.Contains(key, s.List(kind)) } // Write a given value with the given fileName to disk @@ -79,15 +79,13 @@ func (s *Storage) Read(key string, value interface{}) error { // List the keys of a given kind func (s *Storage) List(kind interface{}) []string { - return s.listWithPrefix("", kind) -} - -func (s *Storage) listWithPrefix(prefix string, kind interface{}) []string { var keys []string - for key := range s.disk.KeysPrefix(prefix, nil) { + kindType := s.getType(kind) + + for key := range s.disk.KeysPrefix(kindType, nil) { // Remove the custom file schema from the name, which should only return the pure key key = strings.TrimSuffix(key, ".json") - key = strings.TrimPrefix(key, fmt.Sprintf("%s_", s.getType(kind))) + key = strings.TrimPrefix(key, fmt.Sprintf("%s_", kindType)) keys = append(keys, key) } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index e3dc82d..16bf467 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -1,14 +1,15 @@ package storage import ( - "github.com/StageAutoControl/controller/pkg/cntl" - "github.com/StageAutoControl/controller/pkg/internal/fixtures" - "github.com/StageAutoControl/controller/pkg/internal/stringslice" - internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "io/ioutil" "os" "path/filepath" "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" + "github.com/StageAutoControl/controller/pkg/internal/stringslice" + internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" ) var ( @@ -20,7 +21,6 @@ var ( expectedContent = "{\"id\":\"35cae00a-0b17-11e7-8bca-bbf30c56f20e\",\"name\":\"LED-Bar below drums front\",\"typeId\":\"1555d67e-1187-11e7-8135-9b41038b5b75\",\"universe\":1,\"startChannel\":222,\"tags\":[\"bar\",\"drums\"]}" ) - func TestStorage_buildFileName(t *testing.T) { storage := New(path) generated := storage.buildFileName(key, &cntl.DMXDevice{}) @@ -145,7 +145,7 @@ func TestStorage_List(t *testing.T) { for k := range ds.DMXDevices { if !stringslice.Contains(k, keys) { - t.Errorf("Ecpected result list %s to have key %s", keys, k) + t.Errorf("Expected result list %s to have key %s", keys, k) } } From fc8deafa54cefb4cc3164610562495741d12efa8 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 8 Mar 2019 14:54:57 +0100 Subject: [PATCH 25/94] Add validation for DMXDeviceType --- pkg/api/dmx_device_controller.go | 4 ++++ pkg/api/dmx_device_type_controller.go | 16 ++++++++++++++++ pkg/cntl/types.go | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/api/dmx_device_controller.go b/pkg/api/dmx_device_controller.go index d8147dd..c2546f2 100644 --- a/pkg/api/dmx_device_controller.go +++ b/pkg/api/dmx_device_controller.go @@ -23,6 +23,10 @@ func newDMXDeviceController(logger *logrus.Entry, storage storage) *dmxDeviceCon } func (c *dmxDeviceController) validate(entity *cntl.DMXDevice) error { + if entity.Tags == nil { + entity.Tags = make([]cntl.Tag, 0) + } + if !c.storage.Has(entity.TypeID, &cntl.DMXDeviceType{}) { return fmt.Errorf("cannot save DMXDevice with non-existing DMXDeviceType %q", entity.TypeID) } diff --git a/pkg/api/dmx_device_type_controller.go b/pkg/api/dmx_device_type_controller.go index 21f853d..9c90b8b 100644 --- a/pkg/api/dmx_device_type_controller.go +++ b/pkg/api/dmx_device_type_controller.go @@ -22,6 +22,14 @@ func newDMXDeviceTypeController(logger *logrus.Entry, storage storage) *dmxDevic } } +func (c *dmxDeviceTypeController) validate(entity *cntl.DMXDeviceType) error { + if entity.LEDs == nil { + entity.LEDs = make([]cntl.LED, 0) + } + + return nil +} + // Create a new DMXDeviceType func (c *dmxDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { if entity.ID == "" { @@ -32,6 +40,10 @@ func (c *dmxDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDevice return errExists } + if err := c.validate(entity); err != nil { + return fmt.Errorf("failed to validate entity: %v", err) + } + if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } @@ -45,6 +57,10 @@ func (c *dmxDeviceTypeController) Update(r *http.Request, entity *cntl.DMXDevice return errNotExists } + if err := c.validate(entity); err != nil { + return fmt.Errorf("failed to validate entity: %v", err) + } + if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to update to disk: %v", err) } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 6aebbcc..43e3167 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -66,7 +66,7 @@ type DMXDevice struct { type DMXDeviceType struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` - ChannelCount uint16 `json:"addressCount" yaml:"addressCount"` + ChannelCount uint16 `json:"channelCount" yaml:"channelCount"` ChannelsPerLED uint16 `json:"channelsPerLED" yaml:"channelsPerLED"` StrobeEnabled bool `json:"strobeEnabled" yaml:"strobeEnabled"` StrobeChannel DMXChannel `json:"strobeChannel" yaml:"strobeChannel"` From f7a1cb6c681bfd4732022d674a90bbd18c6102eb Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 10 Mar 2019 18:04:10 +0100 Subject: [PATCH 26/94] Refactor package structure --- cmd/README.md | 20 -- cmd/api.go | 62 ----- cmd/artnet/artnet.go | 20 -- cmd/artnet/listen.go | 92 -------- cmd/artnet/node.go | 61 ----- cmd/artnet/send.go | 118 ---------- cmd/artnet/server.go | 75 ------ cmd/audio/audio.go | 20 -- cmd/audio/const.go | 3 - cmd/audio/dump_input.go | 103 --------- cmd/audio/list_devices.go | 68 ------ cmd/audio/sine.go | 69 ------ cmd/init.go | 46 ++++ cmd/internal/exit.go | 25 -- cmd/playback.go | 85 +++---- cmd/root.go | 25 +- cmd/server.go | 42 ++++ glide.lock | 14 +- glide.yaml | 1 + main.go | 2 - pkg/api/dmx_animation_controller.go | 2 +- pkg/api/dmx_animation_controller_test.go | 12 +- pkg/api/dmx_color_variable_controller.go | 2 +- pkg/api/dmx_color_variable_controller_test.go | 12 +- pkg/api/dmx_device_controller.go | 2 +- pkg/api/dmx_device_controller_test.go | 12 +- pkg/api/dmx_device_group_controller.go | 2 +- pkg/api/dmx_device_group_controller_test.go | 12 +- pkg/api/dmx_device_type_controller.go | 2 +- pkg/api/dmx_device_type_controller_test.go | 12 +- pkg/api/dmx_playground_controller.go | 32 +++ pkg/api/dmx_preset_controller.go | 2 +- pkg/api/dmx_preset_controller_test.go | 12 +- pkg/api/dmx_scene_controller.go | 2 +- pkg/api/dmx_scene_controller_test.go | 12 +- pkg/api/dmx_transition_controller.go | 2 +- pkg/api/dmx_transition_controller_test.go | 12 +- pkg/api/server.go | 15 +- pkg/api/set_list_controller.go | 2 +- pkg/api/set_list_controller_test.go | 12 +- pkg/api/song_controller.go | 2 +- pkg/api/song_controller_test.go | 12 +- pkg/api/types.go | 4 +- pkg/api/types_test.go | 2 +- pkg/artnet/controller.go | 103 +++++++++ pkg/{cntl/transport => }/artnet/ip.go | 0 pkg/{cntl/transport => }/artnet/net.go | 0 pkg/{cntl/transport => }/artnet/net_test.go | 0 pkg/{cntl/transport => }/artnet/node.go | 0 pkg/{cntl/transport => }/artnet/state.go | 0 pkg/{cntl/transport => }/artnet/state_test.go | 0 pkg/artnet/types.go | 24 ++ pkg/cntl/dmx/animation.go | 1 + pkg/cntl/dmx/animation_test.go | 3 +- pkg/cntl/dmx/device.go | 1 + pkg/cntl/dmx/device_test.go | 3 +- pkg/cntl/dmx/easing.go | 1 + pkg/cntl/dmx/merge_test.go | 3 +- pkg/cntl/dmx/preset.go | 1 + pkg/cntl/dmx/preset_test.go | 3 +- pkg/cntl/dmx/scene_test.go | 3 +- pkg/cntl/dmx/transition.go | 3 +- pkg/cntl/dmx/transition_test.go | 3 +- pkg/cntl/dmx/utils_test.go | 3 +- pkg/cntl/midi/midi_test.go | 3 +- pkg/cntl/playback/player.go | 3 +- pkg/cntl/playback/player_test.go | 3 +- pkg/cntl/song/helper.go | 3 +- pkg/cntl/song/helper_test.go | 3 +- pkg/cntl/song/song.go | 1 + pkg/cntl/song/song_test.go | 3 +- pkg/cntl/transport/artnet.go | 61 +---- pkg/cntl/transport/bar_logger.go | 1 + pkg/cntl/transport/midi.go | 3 +- pkg/cntl/transport/stream.go | 3 +- pkg/cntl/transport/visualizer.go | 3 +- pkg/cntl/types.go | 10 - pkg/disk/loader.go | 112 +++++++++ pkg/{storage => disk}/storage.go | 2 +- pkg/{storage => disk}/storage_test.go | 2 +- pkg/enhance/enhancer.go | 6 - pkg/enhance/name_to_id.go | 215 ------------------ pkg/internal/logging/logger.go | 30 +++ pkg/loader/files/.gitignore | 1 - pkg/loader/files/db.go | 43 ---- pkg/loader/files/db_test.go | 107 --------- pkg/loader/files/fixtures/dmx_animations.json | 34 --- .../files/fixtures/dmx_device_groups.json | 37 --- .../files/fixtures/dmx_device_types.json | 159 ------------- pkg/loader/files/fixtures/dmx_devices.json | 82 ------- pkg/loader/files/fixtures/dmx_presets.json | 84 ------- pkg/loader/files/fixtures/dmx_scenes.json | 112 --------- .../files/fixtures/dmx_transitions.json | 82 ------- pkg/loader/files/fixtures/set_lists.json | 13 -- pkg/loader/files/fixtures/songs.json | 53 ----- pkg/loader/files/read.go | 98 -------- 96 files changed, 575 insertions(+), 2096 deletions(-) delete mode 100644 cmd/README.md delete mode 100644 cmd/api.go delete mode 100644 cmd/artnet/artnet.go delete mode 100644 cmd/artnet/listen.go delete mode 100644 cmd/artnet/node.go delete mode 100644 cmd/artnet/send.go delete mode 100644 cmd/artnet/server.go delete mode 100644 cmd/audio/audio.go delete mode 100644 cmd/audio/const.go delete mode 100644 cmd/audio/dump_input.go delete mode 100644 cmd/audio/list_devices.go delete mode 100644 cmd/audio/sine.go create mode 100644 cmd/init.go delete mode 100644 cmd/internal/exit.go create mode 100644 cmd/server.go create mode 100644 pkg/api/dmx_playground_controller.go create mode 100644 pkg/artnet/controller.go rename pkg/{cntl/transport => }/artnet/ip.go (100%) rename pkg/{cntl/transport => }/artnet/net.go (100%) rename pkg/{cntl/transport => }/artnet/net_test.go (100%) rename pkg/{cntl/transport => }/artnet/node.go (100%) rename pkg/{cntl/transport => }/artnet/state.go (100%) rename pkg/{cntl/transport => }/artnet/state_test.go (100%) create mode 100644 pkg/artnet/types.go create mode 100644 pkg/disk/loader.go rename pkg/{storage => disk}/storage.go (99%) rename pkg/{storage => disk}/storage_test.go (99%) delete mode 100644 pkg/enhance/enhancer.go delete mode 100644 pkg/enhance/name_to_id.go create mode 100644 pkg/internal/logging/logger.go delete mode 100644 pkg/loader/files/.gitignore delete mode 100644 pkg/loader/files/db.go delete mode 100644 pkg/loader/files/db_test.go delete mode 100755 pkg/loader/files/fixtures/dmx_animations.json delete mode 100755 pkg/loader/files/fixtures/dmx_device_groups.json delete mode 100755 pkg/loader/files/fixtures/dmx_device_types.json delete mode 100755 pkg/loader/files/fixtures/dmx_devices.json delete mode 100755 pkg/loader/files/fixtures/dmx_presets.json delete mode 100755 pkg/loader/files/fixtures/dmx_scenes.json delete mode 100755 pkg/loader/files/fixtures/dmx_transitions.json delete mode 100755 pkg/loader/files/fixtures/set_lists.json delete mode 100755 pkg/loader/files/fixtures/songs.json delete mode 100644 pkg/loader/files/read.go diff --git a/cmd/README.md b/cmd/README.md deleted file mode 100644 index 4a1a2a8..0000000 --- a/cmd/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Command examples - -## Playback - -```bash -./controller playback (song|setlist) song-or-setlist-uuid \ - --data-dir /var/controller/defintions/ \ - --wait-for-trigger audio \ - --trigger-audio-freq 15000 \ - --transport midi \ - --midi-device-id 1 \ - --transport artnet -``` - - -## Art-Net - -## MIDI - -## API diff --git a/cmd/api.go b/cmd/api.go deleted file mode 100644 index 0dcf896..0000000 --- a/cmd/api.go +++ /dev/null @@ -1,62 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/StageAutoControl/controller/cmd/internal" - "github.com/StageAutoControl/controller/pkg/api" - "github.com/StageAutoControl/controller/pkg/storage" - "github.com/spf13/cobra" -) - -// apiCmd represents the api command -var apiCmd = &cobra.Command{ - Use: "api", - Short: "Opens the RPC API to manage the data and control the processes", - Run: func(cmd *cobra.Command, args []string) { - logger := Logger.WithField("module", "api") - - storagePath, err := cmd.Flags().GetString("storage-path") - if err != nil { - logger.Fatal(err) - } - - cwd, err := os.Getwd() - if err != nil { - logger.Fatal(err) - } - storagePath = filepath.Clean(filepath.Join(cwd, storagePath)) - if err != nil { - logger.Fatal(err) - } - - store := storage.New(storagePath) - server, err := api.NewServer(logger, store) - if err != nil { - logger.Fatal(err) - } - - port, err := cmd.Flags().GetUint16("port") - if err != nil { - logger.Fatal(err) - } - - endpoint := fmt.Sprintf("0.0.0.0:%d", port) - ctx := internal.NewExitHandlerContext(logger.Logger) - - logger.Infof("listening on %s", endpoint) - - if err := server.Run(ctx, endpoint); err != nil { - logger.Fatal(err) - } - }, -} - -func init() { - RootCmd.AddCommand(apiCmd) - - apiCmd.Flags().Uint16P("port", "p", 8080, "TCP port the API should listen on") - apiCmd.Flags().StringP("storage-path", "s", "/var/controller/data", "path where the storage should store the data") -} diff --git a/cmd/artnet/artnet.go b/cmd/artnet/artnet.go deleted file mode 100644 index d2dbd0a..0000000 --- a/cmd/artnet/artnet.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package artnet - -import ( - "github.com/StageAutoControl/controller/cmd" - "github.com/spf13/cobra" -) - -// ArtNetCmd represents the ArtNetTest command namespaces -var ArtNetCmd = &cobra.Command{ - Use: "artnet", - Short: "ArtNet commands to work with ArtNet devices", - Long: ``, -} - -func init() { - cmd.RootCmd.AddCommand(ArtNetCmd) -} diff --git a/cmd/artnet/listen.go b/cmd/artnet/listen.go deleted file mode 100644 index 086609c..0000000 --- a/cmd/artnet/listen.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package artnet - -import ( - "net" - "os" - "os/signal" - "runtime" - "strings" - "sync" - "syscall" - "time" - - root "github.com/StageAutoControl/controller/cmd" - artnetTransport "github.com/StageAutoControl/controller/pkg/cntl/transport/artnet" - "github.com/jsimonetti/go-artnet" - "github.com/spf13/cobra" -) - -// Listen represents the ArtNetTest command -var Listen = &cobra.Command{ - Use: "listen", - Short: "ArtNet server to listen for devices and print them", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - var ip net.IP - var err error - - root.Logger.Info("InterfaceName is empty, searching for suitable one ...") - ip, err = artnetTransport.FindArtNetIP() - if err != nil { - root.Logger.Fatal(err) - } - - root.Logger.Infof("Using interface with IP %s", ip.String()) - - if len(ip) == 0 { - root.Logger.Fatal("No IP found") - } - - host, err := os.Hostname() - if err != nil { - panic(err) - } - c := artnet.NewController(host, ip, artnet.NewLogger(root.Logger)) - var wg sync.WaitGroup - - go func() { - wg.Add(1) - if err := c.Start(); err != nil { - root.Logger.Fatal(err) - } - - wg.Done() - }() - - time.Sleep(10 * time.Second) - - cancel := make(chan os.Signal, 2) - signal.Notify(cancel, syscall.SIGTERM, syscall.SIGKILL) - var builder strings.Builder - - LOOP: - for { - select { - case <-cancel: - break LOOP - default: - } - - for _, n := range c.Nodes { - builder.WriteString(artnetTransport.NodeToString(n)) - } - - root.Logger.Infof(builder.String()) - builder.Reset() - - time.Sleep(10 * time.Second) - } - - c.Stop() - wg.Wait() - - root.Logger.Infof("num: %d", runtime.NumGoroutine()) - }, -} - -func init() { - ArtNetCmd.AddCommand(Listen) -} diff --git a/cmd/artnet/node.go b/cmd/artnet/node.go deleted file mode 100644 index 4dd90f2..0000000 --- a/cmd/artnet/node.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package artnet - -import ( - "log" - "net" - "os" - "os/signal" - - root "github.com/StageAutoControl/controller/cmd" - artnetTransport "github.com/StageAutoControl/controller/pkg/cntl/transport/artnet" - "github.com/jsimonetti/go-artnet" - "github.com/jsimonetti/go-artnet/packet/code" - "github.com/spf13/cobra" -) - -// Node represents the ArtNetTest command -var Node = &cobra.Command{ - Use: "node", - Short: "ArtNet node to test network communication", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - var ip net.IP - var err error - - log.Println("InterfaceName is empty, searching for suitable one ...") - ip, err = artnetTransport.FindArtNetIP() - if err != nil { - log.Fatal(err) - } - - log.Printf("Using interface with IP %s", ip.String()) - - if len(ip) == 0 { - log.Fatal("No IP found") - } - - host, err := os.Hostname() - if err != nil { - panic(err) - } - n := artnet.NewNode(host, code.StNode, ip, artnet.NewLogger(root.Logger)) - - if err := n.Start(); err != nil { - log.Fatal(err) - } - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, os.Kill) - <-c - - log.Println("Stopping node ...") - n.Stop() - }, -} - -func init() { - ArtNetCmd.AddCommand(Node) -} diff --git a/cmd/artnet/send.go b/cmd/artnet/send.go deleted file mode 100644 index ad6cde9..0000000 --- a/cmd/artnet/send.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package artnet - -import ( - "bufio" - "fmt" - "net" - "os" - "runtime" - "strconv" - "strings" - "sync" - "time" - - root "github.com/StageAutoControl/controller/cmd" - "github.com/StageAutoControl/controller/pkg/cntl" - "github.com/StageAutoControl/controller/pkg/cntl/transport" - artnetTransport "github.com/StageAutoControl/controller/pkg/cntl/transport/artnet" - "github.com/jsimonetti/go-artnet" - "github.com/spf13/cobra" -) - -const ( - form = " ..." -) - -// Send represents the ArtNetTest command -var Send = &cobra.Command{ - Use: "send", - Short: "Send dmx command to a artnet device (shell mode)", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - - var ip net.IP - var err error - - root.Logger.Info("InterfaceName is empty, searching for suitable one ...") - ip, err = artnetTransport.FindArtNetIP() - if err != nil { - root.Logger.Fatal(err) - } - - root.Logger.Infof("Using interface with IP %s", ip.String()) - - if len(ip) == 0 { - root.Logger.Fatal("No IP found") - } - - host, err := os.Hostname() - if err != nil { - panic(err) - } - c := artnet.NewController(host, ip, artnet.NewLogger(root.Logger)) - var wg sync.WaitGroup - - go func() { - wg.Add(1) - if err := c.Start(); err != nil { - root.Logger.Fatalf("Error during sending: %v", err) - } - - wg.Done() - }() - - root.Logger.Info("Waiting 5sec for nodes to register") - time.Sleep(5 * time.Second) - - root.Logger.Infof("Entering interactive mode. Please enter the lines in the form %s", form) - reader := bufio.NewReader(os.Stdin) - var universe, channel, value uint64 - - state := artnetTransport.NewState() - - for { - fmt.Print("> ") - text, _ := reader.ReadString('\n') - text = strings.Replace(text, "\n", "", -1) - - params := strings.Split(strings.TrimSpace(text), " ") - if len(params) != 3 { - root.Logger.Errorf("Please enter the form %s", form) - continue - } - - if universe, err = strconv.ParseUint(params[0], 10, 16); err != nil { - root.Logger.Errorf("Unable to parse universe: %v", err) - continue - } - if channel, err = strconv.ParseUint(params[1], 10, 16); err != nil { - root.Logger.Errorf("Unable to parse channel: %v", err) - continue - } - if value, err = strconv.ParseUint(params[2], 10, 8); err != nil { - root.Logger.Errorf("Unable to parse value: %v", err) - continue - } - - // root.Logger.Infof("Sending u=%d, c=%d, v=%d", universe, channel, value) - - state.Set(uint16(universe), uint8(channel), uint8(value)) - - for u, dmx := range state { - c.SendDMXToAddress(dmx, transport.UniverseToAddress(cntl.DMXUniverse(u))) - } - } - - c.Stop() - wg.Wait() - - root.Logger.Infof("num: %d", runtime.NumGoroutine()) - }, -} - -func init() { - ArtNetCmd.AddCommand(Send) -} diff --git a/cmd/artnet/server.go b/cmd/artnet/server.go deleted file mode 100644 index 3a68989..0000000 --- a/cmd/artnet/server.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package artnet - -import ( - "net" - "os" - "runtime" - "sync" - "time" - - root "github.com/StageAutoControl/controller/cmd" - artnetTransport "github.com/StageAutoControl/controller/pkg/cntl/transport/artnet" - "github.com/jsimonetti/go-artnet" - "github.com/spf13/cobra" -) - -// Server represents the ArtNetTest command -var Server = &cobra.Command{ - Use: "server", - Short: "ArtNet server to test network communication", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - var ip net.IP - var err error - - root.Logger.Info("InterfaceName is empty, searching for suitable one ...") - ip, err = artnetTransport.FindArtNetIP() - if err != nil { - root.Logger.Fatal(err) - } - - root.Logger.Infof("Using interface with IP %s", ip.String()) - - if len(ip) == 0 { - root.Logger.Fatal("No IP found") - } - - host, err := os.Hostname() - if err != nil { - panic(err) - } - c := artnet.NewController(host, ip, artnet.NewLogger(root.Logger)) - var wg sync.WaitGroup - - go func() { - wg.Add(1) - if err := c.Start(); err != nil { - root.Logger.Fatal(err) - } - - wg.Done() - }() - - time.Sleep(10 * time.Second) - c.SendDMXToAddress([512]byte{0x00, 0xff, 0x00, 0xff, 0x00}, artnet.Address{Net: 0, SubUni: 0}) - time.Sleep(2 * time.Second) - c.SendDMXToAddress([512]byte{0xff, 0x00, 0x00, 0xff, 0x00}, artnet.Address{Net: 0, SubUni: 0}) - time.Sleep(2 * time.Second) - c.SendDMXToAddress([512]byte{0x00, 0x00, 0xff, 0xff, 0x00}, artnet.Address{Net: 0, SubUni: 0}) - time.Sleep(2 * time.Second) - c.SendDMXToAddress([512]byte{}, artnet.Address{Net: 0, SubUni: 0}) - time.Sleep(2 * time.Second) - - c.Stop() - wg.Wait() - - root.Logger.Infof("num: %d", runtime.NumGoroutine()) - }, -} - -func init() { - ArtNetCmd.AddCommand(Server) -} diff --git a/cmd/audio/audio.go b/cmd/audio/audio.go deleted file mode 100644 index 1ae6607..0000000 --- a/cmd/audio/audio.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package audio - -import ( - "github.com/StageAutoControl/controller/cmd" - "github.com/spf13/cobra" -) - -// AudioCmd represents the audio/audio command -var AudioCmd = &cobra.Command{ - Use: "audio", - Short: "A brief description of your command", - Long: ``, -} - -func init() { - cmd.RootCmd.AddCommand(AudioCmd) -} diff --git a/cmd/audio/const.go b/cmd/audio/const.go deleted file mode 100644 index 227d0da..0000000 --- a/cmd/audio/const.go +++ /dev/null @@ -1,3 +0,0 @@ -package audio - -const sampleRate = 44100 diff --git a/cmd/audio/dump_input.go b/cmd/audio/dump_input.go deleted file mode 100644 index f3908a2..0000000 --- a/cmd/audio/dump_input.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package audio - -import ( - "fmt" - "os" - - "os/signal" - - "github.com/gordonklaus/portaudio" - "github.com/spf13/cobra" -) - -var ( - averageSamples int - threshold float32 -) - -// DumpInputCmd represents the DumpInputs command -var DumpInputCmd = &cobra.Command{ - Use: "dump-input", - Short: "Dumps the audio input of a device to console", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - if err := portaudio.Initialize(); err != nil { - panic(err) - } - defer portaudio.Terminate() - - buf := make([]float32, averageSamples) - s, err := portaudio.OpenDefaultStream(1, 0, sampleRate, len(buf), buf) - if err != nil { - panic(err) - } - defer s.Close() - - if err := s.Start(); err != nil { - panic(err) - } - defer s.Stop() - - var frame int64 - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, os.Kill) - - fmt.Println("Started listener, dumping input.") - - for { - if err := s.Read(); err != nil { - panic(err) - } - - go calcAverage(frame, buf) - frame++ - - select { - case <-c: - fmt.Println("cancelled.") - return - default: - } - } - }, -} - -func init() { - AudioCmd.AddCommand(DumpInputCmd) - - DumpInputCmd.PersistentFlags().IntVarP(&averageSamples, "average-samples", "a", 1000, "How many samples to calc the average from") - DumpInputCmd.PersistentFlags().Float32VarP(&threshold, "threshold", "t", 0.8, "Threshold of tick") - -} - -func calcAverage(frame int64, buf []float32) { - var avg, min, max, sum float32 - for _, s := range buf { - sum += s - - if min == 0 || s < min { - min = s - } else if min == 0 || s > max { - max = s - } - } - - tick := "" - if min < (threshold*-1) || max > threshold { - tick = "tick" - } - - avg = sum / float32(len(buf)) - - go fmt.Printf( - "%10d %14s %14s %14s %10s\n", - frame, - fmt.Sprintf("%5.10f", avg), - fmt.Sprintf("%5.10f", min), - fmt.Sprintf("%5.10f", max), - tick, - ) -} diff --git a/cmd/audio/list_devices.go b/cmd/audio/list_devices.go deleted file mode 100644 index 05820e9..0000000 --- a/cmd/audio/list_devices.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package audio - -import ( - "fmt" - "os" - - "github.com/gordonklaus/portaudio" - "github.com/spf13/cobra" -) - -// DeviceCmd represents the Devices command -var DeviceCmd = &cobra.Command{ - Use: "devices", - Short: "Prints info about all devices", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - if err := portaudio.Initialize(); err != nil { - fmt.Println(err) - os.Exit(1) - } - defer portaudio.Terminate() - - devices, err := portaudio.Devices() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - fmt.Printf("Found %d devices. \n", len(devices)) - if len(devices) == 0 { - return - } - - fmt.Println("\n\nID Name Input Output SampleRate") - - for i, device := range devices { - fmt.Printf("%v ", i) - printDevice(device) - } - - fmt.Println("\n\nDefault input device: ") - defaultInputDevice, err := portaudio.DefaultInputDevice() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - printDevice(defaultInputDevice) - - fmt.Println("\n\nDefault output device: ") - defaultOutputDevice, err := portaudio.DefaultOutputDevice() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - printDevice(defaultOutputDevice) - }, -} - -func printDevice(info *portaudio.DeviceInfo) { - fmt.Printf("%v %v %v %v\n", info.Name, info.MaxInputChannels, info.MaxOutputChannels, info.DefaultSampleRate) -} - -func init() { - AudioCmd.AddCommand(DeviceCmd) -} diff --git a/cmd/audio/sine.go b/cmd/audio/sine.go deleted file mode 100644 index f6ae357..0000000 --- a/cmd/audio/sine.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2017 Alexander Pinnecke -// - -package audio - -import ( - "math" - "time" - - "github.com/gordonklaus/portaudio" - "github.com/spf13/cobra" -) - -var ( - frequency int - length int -) - -// SineCmd represents the Sines command -var SineCmd = &cobra.Command{ - Use: "sine", - Short: "Creates a sin curved audio", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - portaudio.Initialize() - defer portaudio.Terminate() - - s := newStereoSine(float64(frequency), sampleRate) - defer s.Close() - - if err := s.Start(); err != nil { - panic(err) - } - defer s.Stop() - - time.Sleep(time.Duration(length) * time.Millisecond) - }, -} - -func init() { - AudioCmd.AddCommand(SineCmd) - - SineCmd.PersistentFlags().IntVarP(&frequency, "frequency", "f", 18000, "Frequency of the sin") - SineCmd.PersistentFlags().IntVarP(&length, "length", "l", 100, "length of the sin in milliseconds") -} - -type stereoSine struct { - *portaudio.Stream - step, phase float64 -} - -func newStereoSine(freq, sampleRate float64) *stereoSine { - s := &stereoSine{nil, freq / sampleRate, 0} - - var err error - s.Stream, err = portaudio.OpenDefaultStream(0, 1, sampleRate, 0, s.processAudio) - if err != nil { - panic(err) - } - - return s -} - -func (g *stereoSine) processAudio(out [][]float32) { - for i := range out[0] { - out[0][i] = float32(math.Sin(2 * math.Pi * g.phase)) - _, g.phase = math.Modf(g.phase + g.step) - } -} diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..56258de --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "os" + "path/filepath" + + "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/disk" + "github.com/sirupsen/logrus" +) + +func createLogger(logLevel string) *logrus.Entry { + logger := logrus.New() + level, err := logrus.ParseLevel(logLevel) + if err != nil { + logger.Panicf("Unable to parse log level %q: %v\n", logLevel, err) + os.Exit(1) + } + + logger.Infof("Using log level %s", logLevel) + + logger.SetLevel(level) + return logger.WithFields(logrus.Fields{}) +} + +func createStorage(logger *logrus.Entry, storagePath string) *disk.Storage { + cwd, err := os.Getwd() + if err != nil { + logger.Fatal(err) + } + storagePath = filepath.Clean(filepath.Join(cwd, storagePath)) + if err != nil { + logger.Fatal(err) + } + + return disk.New(storagePath) +} + +func createController(logger *logrus.Entry) artnet.Controller { + c, err := artnet.NewController(logger) + if err != nil { + logger.Fatal(err) + } + + return c +} diff --git a/cmd/internal/exit.go b/cmd/internal/exit.go deleted file mode 100644 index c6a8faa..0000000 --- a/cmd/internal/exit.go +++ /dev/null @@ -1,25 +0,0 @@ -package internal - -import ( - "context" - "os" - "os/signal" - "syscall" - - "github.com/sirupsen/logrus" -) - -// NewExitHandlerContext creates a trap for termination signals -func NewExitHandlerContext(logger *logrus.Logger) context.Context { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL, os.Interrupt) - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - <-c - defer cancel() - logger.Info("shutting down") - }() - - return ctx -} diff --git a/cmd/playback.go b/cmd/playback.go index 52178d7..7871cb9 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -6,13 +6,13 @@ import ( "fmt" "os" - "github.com/StageAutoControl/controller/cmd/internal" + "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/cntl/transport" "github.com/StageAutoControl/controller/pkg/cntl/waiter" - "github.com/StageAutoControl/controller/pkg/enhance" - "github.com/StageAutoControl/controller/pkg/loader/files" + "github.com/StageAutoControl/controller/pkg/disk" + "github.com/apinnecke/go-exitcontext" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -20,15 +20,9 @@ import ( const ( playbackTypeSong = "song" playbackTypeSetList = "setlist" - - directoryLoader = "directory" - databaseLoader = "database" ) var ( - loaders = []string{directoryLoader, databaseLoader} - loaderType string - transportTypes = []string{ transport.TypeStream, transport.TypeVisualizer, @@ -39,7 +33,6 @@ var ( viualizerEndpoint string midiDeviceID string - dataDir string waiterTypes = []string{ waiter.TypeNone, @@ -58,81 +51,65 @@ var playbackCmd = &cobra.Command{ logrus.SetLevel(logrus.WarnLevel) if len(args) != 2 { - cmd.Usage() + if err := cmd.Usage(); err != nil { + logrus.Fatal(err) + } os.Exit(1) } - var loader cntl.Loader - switch loaderType { - case directoryLoader: - Logger.Infof("Loading data directory %q ...", dataDir) - loader = files.New(dataDir) - - case databaseLoader: - //loader = database.New(), - Logger.Fatal("Database loader is not yet supported.") - - default: - Logger.Fatalf("Loader %q is not supported. Choose one of %s", loader, loaders) - } - + store := disk.New(storagePath) + loader := disk.NewLoader(store) data, err := loader.Load() if err != nil { - Logger.Fatalf("Failed to load data from %q: %v", loaderType, err) + logrus.Fatal(err) } - Logger.Print("enhancing data ...") - for _, e := range enhance.Enhancers { - if es := e.Enhance(data); len(es) > 0 { - for _, e := range es { - Logger.Error(e) - } - Logger.Fatalf("Errors occurred enhancing data store") - } - } - Logger.Print("Done. No errors found.") - var writers []playback.TransportWriter for _, transportType := range usedTransports { switch transportType { case transport.TypeStream: - writers = append(writers, transport.NewStream(Logger.WithField(cntl.LoggerFieldTransport, transport.TypeStream), os.Stdout)) + writers = append(writers, transport.NewStream(logger.WithField(cntl.LoggerFieldTransport, transport.TypeStream), os.Stdout)) break case transport.TypeBarLogger: - writers = append(writers, transport.NewBarLogger(Logger.WithField(cntl.LoggerFieldTransport, transport.TypeBarLogger))) + writers = append(writers, transport.NewBarLogger(logger.WithField(cntl.LoggerFieldTransport, transport.TypeBarLogger))) break case transport.TypeVisualizer: - w, err := transport.NewVisualizer(Logger.WithField(cntl.LoggerFieldTransport, transport.TypeVisualizer), viualizerEndpoint) + w, err := transport.NewVisualizer(logger.WithField(cntl.LoggerFieldTransport, transport.TypeVisualizer), viualizerEndpoint) if err != nil { - Logger.Fatalf("Unable to connect to the visualizer: %v", err) + logger.Fatalf("Unable to connect to the visualizer: %v", err) } writers = append(writers, w) break case transport.TypeArtNet: - w, err := transport.NewArtNet(Logger.WithField(cntl.LoggerFieldTransport, transport.TypeArtNet), "stage-auto-control") + controller, err := artnet.NewController(logger.WithField(cntl.LoggerFieldTransport, transport.TypeArtNet)) + if err != nil { + logger.Fatal(err) + } + + w, err := transport.NewArtNet(controller) if err != nil { - Logger.Fatalf("Unable to connect to the visualizer: %v", err) + logger.Fatalf("Unable to open art net controller: %v", err) } writers = append(writers, w) break case transport.TypeMidi: - w, err := transport.NewMIDI(Logger.WithField(cntl.LoggerFieldTransport, transport.TypeMidi), midiDeviceID) + w, err := transport.NewMIDI(logger.WithField(cntl.LoggerFieldTransport, transport.TypeMidi), midiDeviceID) if err != nil { - Logger.Fatalf("Unable to connect to midi device: %v", err) + logger.Fatalf("Unable to connect to midi device: %v", err) } writers = append(writers, w) break default: - Logger.Fatalf("Transport %q is not supported.", transportType) + logger.Fatalf("Transport %q is not supported", transportType) } } @@ -140,14 +117,14 @@ var playbackCmd = &cobra.Command{ for _, waiterType := range usedWaiters { switch waiterType { case waiter.TypeNone: - waiters = append(waiters, waiter.NewNone(Logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeNone))) + waiters = append(waiters, waiter.NewNone(logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeNone))) break case waiter.TypeAudio: - a, err := waiter.NewAudio(Logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeAudio), audioWaiterThreshold) + a, err := waiter.NewAudio(logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeAudio), audioWaiterThreshold) if err != nil { - Logger.Fatal(err) + logger.Fatal(err) } waiters = append(waiters, a) @@ -156,21 +133,21 @@ var playbackCmd = &cobra.Command{ } } - ctx := internal.NewExitHandlerContext(Logger.Logger) - player := playback.NewPlayer(Logger.Logger.WithField("player", "default"), data, writers, waiters) + ctx := exitcontext.New() + player := playback.NewPlayer(logger.Logger.WithField("player", "default"), data, writers, waiters) switch args[0] { case playbackTypeSong: songID := args[1] if err = player.PlaySong(ctx, songID); err != nil { - Logger.Fatal(err) + logger.Fatal(err) } break case playbackTypeSetList: setListID := args[1] if err = player.PlaySetList(ctx, setListID); err != nil { - Logger.Fatal(err) + logger.Fatal(err) } } }, @@ -184,6 +161,4 @@ func init() { playbackCmd.Flags().StringVarP(&midiDeviceID, "midi-device-id", "m", "", "DeviceID of MIDI output to use (On empty string the default device is used)") playbackCmd.Flags().StringSliceVarP(&usedWaiters, "wait-for", "w", []string{waiter.TypeNone}, fmt.Sprintf("Wait for a specific signal before playing a song (required to be used on stage, otherwise the next song would start immediately), one of %s", waiterTypes)) playbackCmd.Flags().Float32Var(&audioWaiterThreshold, "audio-waiter-threshold", 0.9, "Threshold frequency for audio waiter to trigger a signal") - playbackCmd.Flags().StringVarP(&dataDir, "data-dir", "d", "", "Data directory to load (when loader is set to directory)") - playbackCmd.Flags().StringVar(&loaderType, "loader", directoryLoader, fmt.Sprintf("Which loader to use %s.", loaders)) } diff --git a/cmd/root.go b/cmd/root.go index 6c57b4a..10d7379 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,14 +6,18 @@ import ( "fmt" "os" + "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/disk" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( - // Logger used by the whole application - Logger *logrus.Entry - logLevel string + logLevel string + logger *logrus.Entry + storagePath string + storage *disk.Storage + controller artnet.Controller ) // RootCmd represents the base command when called without any subcommands @@ -22,17 +26,9 @@ var RootCmd = &cobra.Command{ Short: "Stage automatic controlling, triggering state changes.", Long: `Automatic stage controlling, including midi and DMX, by analyzing audio signals and pre defined light scenes`, PersistentPreRun: func(cmd *cobra.Command, args []string) { - logger := logrus.New() - level, err := logrus.ParseLevel(logLevel) - if err != nil { - logger.Panicf("Unable to parse log level %q: %v\n", logLevel, err) - os.Exit(1) - } - - logger.Infof("Using log level %s", logLevel) - - logger.SetLevel(level) - Logger = logger.WithFields(logrus.Fields{}) + logger = createLogger(logLevel) + storage = createStorage(logger, storagePath) + controller = createController(logger) }, } @@ -47,4 +43,5 @@ func Execute() { func init() { RootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Which log level to use") + RootCmd.PersistentFlags().StringVarP(&storagePath, "storage-path", "s", "/var/controller/data", "path where the storage should store the data") } diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..c4d85ca --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "fmt" + + "github.com/StageAutoControl/controller/pkg/api" + "github.com/apinnecke/go-exitcontext" + "github.com/spf13/cobra" +) + +// serverCmd represents the server command +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Opens the RPC API to manage the data and control the processes", + Run: func(cmd *cobra.Command, args []string) { + logger := logger.WithField("module", "server") + server, err := api.NewServer(logger, storage, controller) + if err != nil { + logger.Fatal(err) + } + + port, err := cmd.Flags().GetUint16("port") + if err != nil { + logger.Fatal(err) + } + + endpoint := fmt.Sprintf("0.0.0.0:%d", port) + ctx := exitcontext.New() + + logger.Infof("listening on %s", endpoint) + + if err := server.Run(ctx, endpoint); err != nil { + logger.Fatal(err) + } + }, +} + +func init() { + RootCmd.AddCommand(serverCmd) + + serverCmd.Flags().Uint16P("port", "p", 8080, "TCP port the API should listen on") +} diff --git a/glide.lock b/glide.lock index 2855c99..f8d3f9e 100644 --- a/glide.lock +++ b/glide.lock @@ -1,6 +1,8 @@ -hash: ae7cf22751f01b31993152d74c88291f7ee660e0efb4111d0baf28077466857c -updated: 2019-02-10T11:12:25.802271+01:00 +hash: cf169cddd10a9453d7edb6f4facbf3a022d5581edc3b0f17a20081dd22d2bea0 +updated: 2019-03-09T12:56:06.76403+01:00 imports: +- name: github.com/apinnecke/go-exitcontext + version: 06015046a58d57f896f5e2ea290e6540c3fba863 - name: github.com/creasty/go-easing version: 0cfd96d3a544aad2e643739e4a4f6c081b12cda0 - name: github.com/google/btree @@ -26,7 +28,7 @@ imports: - packet/code - version - name: github.com/konsorten/go-windows-terminal-sequences - version: 5c8c8bd35d3832f5d134ae1e1e375b69a4d25242 + version: f55edac94c9bbba5d6182a4be46d86a2c9b5b50e - name: github.com/peterbourgon/diskv version: 5f041e8faa004a95c88a202771f4cc3e991971e6 - name: github.com/rakyll/portmidi @@ -34,7 +36,7 @@ imports: - name: github.com/satori/go.uuid version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 - name: github.com/sirupsen/logrus - version: 4ea4861398d99a2d05be29675c5b74caf7bea95e + version: d7b6bf5e4d26448fd977d07d745a2a66097ddecb - name: github.com/spf13/cobra version: 7547e83b2d85fd1893c7d76916f67689d761fecb - name: github.com/spf13/pflag @@ -42,11 +44,11 @@ imports: - name: github.com/spf13/viper version: 6d33b5a963d922d182c91e8a1c88d81fd150cfd4 - name: golang.org/x/crypto - version: 193df9c0f06f8bb35fba505183eaf0acc0136505 + version: c2843e01d9a2bc60bb26ad24e09734fdc2d9ec58 subpackages: - ssh/terminal - name: golang.org/x/sys - version: 3b5209105503162ded1863c307ac66fec31120dd + version: 584f3b12f43e1e55248c90e84804777009eed0a4 subpackages: - unix - windows diff --git a/glide.yaml b/glide.yaml index ce71538..b981a8b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -23,3 +23,4 @@ import: - package: github.com/peterbourgon/diskv version: ^2.0.1 - package: github.com/jinzhu/copier +- package: github.com/apinnecke/go-exitcontext diff --git a/main.go b/main.go index c92edf9..b89eafc 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,6 @@ package main import ( "github.com/StageAutoControl/controller/cmd" - _ "github.com/StageAutoControl/controller/cmd/artnet" - _ "github.com/StageAutoControl/controller/cmd/audio" _ "github.com/StageAutoControl/controller/cmd/midi" ) diff --git a/pkg/api/dmx_animation_controller.go b/pkg/api/dmx_animation_controller.go index c49ced4..4de6003 100644 --- a/pkg/api/dmx_animation_controller.go +++ b/pkg/api/dmx_animation_controller.go @@ -70,7 +70,7 @@ func (c *dmxAnimationController) Get(r *http.Request, idReq *IDRequest, reply *c } // GetAll returns all entities of DMXAnimation -func (c *dmxAnimationController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXAnimation) error { +func (c *dmxAnimationController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXAnimation) error { for _, id := range c.storage.List(&cntl.DMXAnimation{}) { entity := &cntl.DMXAnimation{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_animation_controller_test.go b/pkg/api/dmx_animation_controller_test.go index 8e6f789..5777315 100644 --- a/pkg/api/dmx_animation_controller_test.go +++ b/pkg/api/dmx_animation_controller_test.go @@ -16,7 +16,7 @@ func TestDMXAnimationController_Create_WithID(t *testing.T) { createReply := &cntl.DMXAnimation{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXAnimationController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXAnimation{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXAnimationController_Get_Existing(t *testing.T) { createReply := &cntl.DMXAnimation{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXAnimationController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXAnimationController_Update_Existing(t *testing.T) { createReply := &cntl.DMXAnimation{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXAnimationController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXAnimation{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_color_variable_controller.go b/pkg/api/dmx_color_variable_controller.go index 1ac3f2e..3cc03e0 100644 --- a/pkg/api/dmx_color_variable_controller.go +++ b/pkg/api/dmx_color_variable_controller.go @@ -71,7 +71,7 @@ func (c *dmxColorVariableController) Get(r *http.Request, idReq *IDRequest, repl } // GetAll returns all entities of DMXColorVariable -func (c *dmxColorVariableController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXColorVariable) error { +func (c *dmxColorVariableController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXColorVariable) error { for _, id := range c.storage.List(&cntl.DMXColorVariable{}) { entity := &cntl.DMXColorVariable{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_color_variable_controller_test.go b/pkg/api/dmx_color_variable_controller_test.go index e808533..b55f10f 100644 --- a/pkg/api/dmx_color_variable_controller_test.go +++ b/pkg/api/dmx_color_variable_controller_test.go @@ -16,7 +16,7 @@ func TestDMXColorVariableController_Create_WithID(t *testing.T) { createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXColorVariableController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXColorVariableController_Get_Existing(t *testing.T) { createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXColorVariableController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXColorVariableController_Update_Existing(t *testing.T) { createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXColorVariableController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXColorVariable{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_device_controller.go b/pkg/api/dmx_device_controller.go index c2546f2..c60bd86 100644 --- a/pkg/api/dmx_device_controller.go +++ b/pkg/api/dmx_device_controller.go @@ -91,7 +91,7 @@ func (c *dmxDeviceController) Get(r *http.Request, idReq *IDRequest, reply *cntl } // GetAll returns all entities of DMXDevice -func (c *dmxDeviceController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXDevice) error { +func (c *dmxDeviceController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXDevice) error { for _, id := range c.storage.List(&cntl.DMXDevice{}) { entity := &cntl.DMXDevice{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_device_controller_test.go b/pkg/api/dmx_device_controller_test.go index d664360..76ccd50 100644 --- a/pkg/api/dmx_device_controller_test.go +++ b/pkg/api/dmx_device_controller_test.go @@ -24,7 +24,7 @@ func TestDMXDeviceController_Create_WithID(t *testing.T) { createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -48,7 +48,7 @@ func TestDMXDeviceController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXDeviceController_Get_Existing(t *testing.T) { createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -90,7 +90,7 @@ func TestDMXDeviceController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -121,7 +121,7 @@ func TestDMXDeviceController_Update_Existing(t *testing.T) { createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -159,7 +159,7 @@ func TestDMXDeviceController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXDevice{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_device_group_controller.go b/pkg/api/dmx_device_group_controller.go index 4420157..92b945f 100644 --- a/pkg/api/dmx_device_group_controller.go +++ b/pkg/api/dmx_device_group_controller.go @@ -70,7 +70,7 @@ func (c *dmxDeviceGroupController) Get(r *http.Request, idReq *IDRequest, reply } // GetAll returns all entities of DMXDeviceGroup -func (c *dmxDeviceGroupController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXDeviceGroup) error { +func (c *dmxDeviceGroupController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXDeviceGroup) error { for _, id := range c.storage.List(&cntl.DMXDeviceGroup{}) { entity := &cntl.DMXDeviceGroup{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_device_group_controller_test.go b/pkg/api/dmx_device_group_controller_test.go index ac9fc05..368c7ce 100644 --- a/pkg/api/dmx_device_group_controller_test.go +++ b/pkg/api/dmx_device_group_controller_test.go @@ -16,7 +16,7 @@ func TestDMXDeviceGroupController_Create_WithID(t *testing.T) { createReply := &cntl.DMXDeviceGroup{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXDeviceGroupController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXDeviceGroup{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXDeviceGroupController_Get_Existing(t *testing.T) { createReply := &cntl.DMXDeviceGroup{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXDeviceGroupController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXDeviceGroupController_Update_Existing(t *testing.T) { createReply := &cntl.DMXDeviceGroup{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXDeviceGroupController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXDeviceGroup{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_device_type_controller.go b/pkg/api/dmx_device_type_controller.go index 9c90b8b..3b68abf 100644 --- a/pkg/api/dmx_device_type_controller.go +++ b/pkg/api/dmx_device_type_controller.go @@ -86,7 +86,7 @@ func (c *dmxDeviceTypeController) Get(r *http.Request, idReq *IDRequest, reply * } // GetAll returns all entities of DMXDeviceType -func (c *dmxDeviceTypeController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXDeviceType) error { +func (c *dmxDeviceTypeController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXDeviceType) error { for _, id := range c.storage.List(&cntl.DMXDeviceType{}) { entity := &cntl.DMXDeviceType{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_device_type_controller_test.go b/pkg/api/dmx_device_type_controller_test.go index e221f4c..3764d1b 100644 --- a/pkg/api/dmx_device_type_controller_test.go +++ b/pkg/api/dmx_device_type_controller_test.go @@ -16,7 +16,7 @@ func TestDMXDeviceTypeController_Create_WithID(t *testing.T) { createReply := &cntl.DMXDeviceType{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXDeviceTypeController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXDeviceType{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXDeviceTypeController_Get_Existing(t *testing.T) { createReply := &cntl.DMXDeviceType{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXDeviceTypeController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXDeviceTypeController_Update_Existing(t *testing.T) { createReply := &cntl.DMXDeviceType{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXDeviceTypeController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXDeviceType{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_playground_controller.go b/pkg/api/dmx_playground_controller.go new file mode 100644 index 0000000..6db2eca --- /dev/null +++ b/pkg/api/dmx_playground_controller.go @@ -0,0 +1,32 @@ +package api + +import ( + "net/http" + + "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/internal/logging" +) + +type DMXPlaygroundController struct { + logger logging.Logger + controller artnet.Controller +} + +func newDMXPlaygroundController(logger logging.Logger, controller artnet.Controller) *DMXPlaygroundController { + return &DMXPlaygroundController{ + logger: logger, + controller: controller, + } +} + +// SetChannelValue sets a single artnet/dmx value +func (c *DMXPlaygroundController) SetChannelValue(r *http.Request, value *artnet.ChannelValue, request Empty) error { + c.controller.SetDMXChannelValue(*value) + return nil +} + +// SetChannelValues sets multiple artnet/dmx values +func (c *DMXPlaygroundController) SetChannelValues(r *http.Request, values *[]artnet.ChannelValue, request Empty) error { + c.controller.SetDMXChannelValues(*values) + return nil +} diff --git a/pkg/api/dmx_preset_controller.go b/pkg/api/dmx_preset_controller.go index 4653750..a41dda8 100644 --- a/pkg/api/dmx_preset_controller.go +++ b/pkg/api/dmx_preset_controller.go @@ -70,7 +70,7 @@ func (c *dmxPresetController) Get(r *http.Request, idReq *IDRequest, reply *cntl } // GetAll returns all entities of DMXPreset -func (c *dmxPresetController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXPreset) error { +func (c *dmxPresetController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXPreset) error { for _, id := range c.storage.List(&cntl.DMXPreset{}) { entity := &cntl.DMXPreset{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_preset_controller_test.go b/pkg/api/dmx_preset_controller_test.go index 4525271..45bf7e8 100644 --- a/pkg/api/dmx_preset_controller_test.go +++ b/pkg/api/dmx_preset_controller_test.go @@ -16,7 +16,7 @@ func TestDMXPresetController_Create_WithID(t *testing.T) { createReply := &cntl.DMXPreset{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXPresetController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXPreset{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXPresetController_Get_Existing(t *testing.T) { createReply := &cntl.DMXPreset{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXPresetController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXPresetController_Update_Existing(t *testing.T) { createReply := &cntl.DMXPreset{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXPresetController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXPreset{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_scene_controller.go b/pkg/api/dmx_scene_controller.go index 642a0fe..d42e4ff 100644 --- a/pkg/api/dmx_scene_controller.go +++ b/pkg/api/dmx_scene_controller.go @@ -70,7 +70,7 @@ func (c *dmxSceneController) Get(r *http.Request, idReq *IDRequest, reply *cntl. } // GetAll returns all entities of DMXScene -func (c *dmxSceneController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXScene) error { +func (c *dmxSceneController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXScene) error { for _, id := range c.storage.List(&cntl.DMXScene{}) { entity := &cntl.DMXScene{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_scene_controller_test.go b/pkg/api/dmx_scene_controller_test.go index 9ff1b5f..8091da3 100644 --- a/pkg/api/dmx_scene_controller_test.go +++ b/pkg/api/dmx_scene_controller_test.go @@ -16,7 +16,7 @@ func TestDMXSceneController_Create_WithID(t *testing.T) { createReply := &cntl.DMXScene{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXSceneController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXScene{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXSceneController_Get_Existing(t *testing.T) { createReply := &cntl.DMXScene{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXSceneController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXSceneController_Update_Existing(t *testing.T) { createReply := &cntl.DMXScene{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXSceneController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXScene{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/dmx_transition_controller.go b/pkg/api/dmx_transition_controller.go index c078893..5ae7a0d 100644 --- a/pkg/api/dmx_transition_controller.go +++ b/pkg/api/dmx_transition_controller.go @@ -70,7 +70,7 @@ func (c *dmxTransitionController) Get(r *http.Request, idReq *IDRequest, reply * } // GetAll returns all entities of DMXTransition -func (c *dmxTransitionController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.DMXTransition) error { +func (c *dmxTransitionController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXTransition) error { for _, id := range c.storage.List(&cntl.DMXTransition{}) { entity := &cntl.DMXTransition{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/dmx_transition_controller_test.go b/pkg/api/dmx_transition_controller_test.go index 9643b0a..719ec34 100644 --- a/pkg/api/dmx_transition_controller_test.go +++ b/pkg/api/dmx_transition_controller_test.go @@ -16,7 +16,7 @@ func TestDMXTransitionController_Create_WithID(t *testing.T) { createReply := &cntl.DMXTransition{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestDMXTransitionController_Create_WithoutID(t *testing.T) { createReply := &cntl.DMXTransition{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestDMXTransitionController_Get_Existing(t *testing.T) { createReply := &cntl.DMXTransition{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestDMXTransitionController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestDMXTransitionController_Update_Existing(t *testing.T) { createReply := &cntl.DMXTransition{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestDMXTransitionController_Delete_Existing(t *testing.T) { createReply := &cntl.DMXTransition{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/server.go b/pkg/api/server.go index 14e8734..786c022 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/artnet" "github.com/gorilla/handlers" "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" @@ -14,13 +15,14 @@ import ( // Server represents the controllers API server, aware of all the controllers type Server struct { *rpc.Server - logger *logrus.Entry - storage storage - controller map[string]interface{} + logger *logrus.Entry + storage storage + apiController map[string]interface{} + controller artnet.Controller } // NewServer returns a new Server instance -func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { +func NewServer(logger *logrus.Entry, storage storage, controller artnet.Controller) (*Server, error) { server := &Server{ Server: rpc.NewServer(), logger: logger, @@ -35,7 +37,7 @@ func NewServer(logger *logrus.Entry, storage storage) (*Server, error) { } func (s *Server) registerControllers() error { - s.controller = map[string]interface{}{ + s.apiController = map[string]interface{}{ "DMXAnimation": newDMXAnimationController(s.logger, s.storage), "DMXDevice": newDMXDeviceController(s.logger, s.storage), "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), @@ -46,9 +48,10 @@ func (s *Server) registerControllers() error { "DMXColorVariable": newDMXColorVariableController(s.logger, s.storage), "Song": newSongController(s.logger, s.storage), "SetList": newSetListController(s.logger, s.storage), + "DMXPlayground": newDMXPlaygroundController(s.logger, s.controller), } - for name, controller := range s.controller { + for name, controller := range s.apiController { if err := s.Server.RegisterService(controller, name); err != nil { return err } diff --git a/pkg/api/set_list_controller.go b/pkg/api/set_list_controller.go index 78be5d9..d99ff06 100644 --- a/pkg/api/set_list_controller.go +++ b/pkg/api/set_list_controller.go @@ -70,7 +70,7 @@ func (c *setListController) Get(r *http.Request, idReq *IDRequest, reply *cntl.S } // GetAll returns all entities of SetList -func (c *setListController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.SetList) error { +func (c *setListController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.SetList) error { for _, id := range c.storage.List(&cntl.SetList{}) { entity := &cntl.SetList{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/set_list_controller_test.go b/pkg/api/set_list_controller_test.go index 0b2c4c3..f960503 100644 --- a/pkg/api/set_list_controller_test.go +++ b/pkg/api/set_list_controller_test.go @@ -16,7 +16,7 @@ func TestSetListController_Create_WithID(t *testing.T) { createReply := &cntl.SetList{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestSetListController_Create_WithoutID(t *testing.T) { createReply := &cntl.SetList{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestSetListController_Get_Existing(t *testing.T) { createReply := &cntl.SetList{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestSetListController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestSetListController_Update_Existing(t *testing.T) { createReply := &cntl.SetList{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestSetListController_Delete_Existing(t *testing.T) { createReply := &cntl.SetList{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/song_controller.go b/pkg/api/song_controller.go index 41aac6f..5f0cdd9 100644 --- a/pkg/api/song_controller.go +++ b/pkg/api/song_controller.go @@ -70,7 +70,7 @@ func (c *songController) Get(r *http.Request, idReq *IDRequest, reply *cntl.Song } // GetAll returns all entities of Song -func (c *songController) GetAll(r *http.Request, idReq *EmptyRequest, reply *[]*cntl.Song) error { +func (c *songController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.Song) error { for _, id := range c.storage.List(&cntl.Song{}) { entity := &cntl.Song{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/song_controller_test.go b/pkg/api/song_controller_test.go index 49f5ad9..cfdd03c 100644 --- a/pkg/api/song_controller_test.go +++ b/pkg/api/song_controller_test.go @@ -16,7 +16,7 @@ func TestSongController_Create_WithID(t *testing.T) { createReply := &cntl.Song{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -39,7 +39,7 @@ func TestSongController_Create_WithoutID(t *testing.T) { createReply := &cntl.Song{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -68,7 +68,7 @@ func TestSongController_Get_Existing(t *testing.T) { createReply := &cntl.Song{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -79,7 +79,7 @@ func TestSongController_Get_Existing(t *testing.T) { idReq := &IDRequest{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if reply.ID != key { @@ -108,7 +108,7 @@ func TestSongController_Update_Existing(t *testing.T) { createReply := &cntl.Song{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { @@ -144,7 +144,7 @@ func TestSongController_Delete_Existing(t *testing.T) { createReply := &cntl.Song{} if err := controller.Create(req, entity, createReply); err != nil { - t.Errorf("failed to call controller: %v", err) + t.Errorf("failed to call apiController: %v", err) } if createReply.ID != key { diff --git a/pkg/api/types.go b/pkg/api/types.go index 18ba550..0d9d389 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -27,5 +27,5 @@ type SuccessResponse struct { Success bool `json:"success"` } -// EmptyRequest is ... yah, an empty request :shrug: -type EmptyRequest struct{} +// Empty is ... yah, an empty request :shrug: +type Empty struct{} diff --git a/pkg/api/types_test.go b/pkg/api/types_test.go index bf6e88d..d4aadc5 100644 --- a/pkg/api/types_test.go +++ b/pkg/api/types_test.go @@ -5,8 +5,8 @@ import ( "net/http" "net/http/httptest" + "github.com/StageAutoControl/controller/pkg/disk" "github.com/StageAutoControl/controller/pkg/internal/fixtures" - disk "github.com/StageAutoControl/controller/pkg/storage" "github.com/sirupsen/logrus" ) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go new file mode 100644 index 0000000..e3ba0da --- /dev/null +++ b/pkg/artnet/controller.go @@ -0,0 +1,103 @@ +package artnet + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + "time" + + "github.com/StageAutoControl/controller/pkg/internal/logging" + "github.com/jsimonetti/go-artnet" + "github.com/sirupsen/logrus" +) + +// Controller is a transport for the ArtNet protocol (DMX over UDP/IP) +type controller struct { + sender Sender + state State + sendTrigger chan struct{} +} + +// NewController returns a artnet Controller as an anonymous interface +func NewController(logger logging.Logger) (Controller, error) { + ip, err := FindArtNetIP() + if err != nil { + return nil, fmt.Errorf("failed to find the art-net IP: %v", err) + } + + if len(ip) == 0 { + return nil, errors.New("failed to find the art-net IP: No interface found") + } + + host, err := os.Hostname() + if err != nil { + panic(err) + } + + c := artnet.NewController(host, ip, artnet.NewLogger(logger.(*logrus.Entry))) + if err := c.Start(); err != nil { + return nil, fmt.Errorf("failed to start Controller: %v", err) + } + + logger.Info("Waiting 5 seconds for nodes to register") + time.Sleep(5 * time.Second) + + return &controller{ + sender: c, + state: NewState(), + sendTrigger: make(chan struct{}, 1), + }, nil +} + +// Start the controller +func (c *controller) Start() error { + return c.sender.Start() +} + +// Stop the controller +func (c *controller) Stop() { + close(c.sendTrigger) + c.sender.Stop() +} + +func (c *controller) SetDMXChannelValue(value ChannelValue) { + c.state.Set(value.Universe, value.Channel, value.Value) + c.triggerSend() +} + +func (c *controller) SetDMXChannelValues(values []ChannelValue) { + for _, value := range values { + c.state.Set(value.Universe, value.Channel, value.Value) + } + + c.triggerSend() +} + +func (c *controller) triggerSend() { + c.sendTrigger <- struct{}{} +} + +func (c *controller) sendBackground() { + for range c.sendTrigger { + c.send() + } +} + +func (c *controller) send() { + for universe, dmx := range c.state { + c.sender.SendDMXToAddress(dmx, c.universeToAddress(universe)) + } +} + +// universeToAddress converts a dmx universe to a artnet address +// Code stolen from https://play.golang.org/p/pdQPC5u7JX +func (c *controller) universeToAddress(universe uint16) artnet.Address { + v := make([]uint8, 2) + binary.BigEndian.PutUint16(v, universe) + + return artnet.Address{ + Net: v[0], + SubUni: v[1], + } +} diff --git a/pkg/cntl/transport/artnet/ip.go b/pkg/artnet/ip.go similarity index 100% rename from pkg/cntl/transport/artnet/ip.go rename to pkg/artnet/ip.go diff --git a/pkg/cntl/transport/artnet/net.go b/pkg/artnet/net.go similarity index 100% rename from pkg/cntl/transport/artnet/net.go rename to pkg/artnet/net.go diff --git a/pkg/cntl/transport/artnet/net_test.go b/pkg/artnet/net_test.go similarity index 100% rename from pkg/cntl/transport/artnet/net_test.go rename to pkg/artnet/net_test.go diff --git a/pkg/cntl/transport/artnet/node.go b/pkg/artnet/node.go similarity index 100% rename from pkg/cntl/transport/artnet/node.go rename to pkg/artnet/node.go diff --git a/pkg/cntl/transport/artnet/state.go b/pkg/artnet/state.go similarity index 100% rename from pkg/cntl/transport/artnet/state.go rename to pkg/artnet/state.go diff --git a/pkg/cntl/transport/artnet/state_test.go b/pkg/artnet/state_test.go similarity index 100% rename from pkg/cntl/transport/artnet/state_test.go rename to pkg/artnet/state_test.go diff --git a/pkg/artnet/types.go b/pkg/artnet/types.go new file mode 100644 index 0000000..880afab --- /dev/null +++ b/pkg/artnet/types.go @@ -0,0 +1,24 @@ +package artnet + +import "github.com/jsimonetti/go-artnet" + +// Sender is an artnet controller abstraction of the base implementation of jsimonetti +type Sender interface { + SendDMXToAddress(dmx [512]byte, address artnet.Address) + Start() error + Stop() +} + +// ChannelValue defines an ArtNet Universe and the value of the DMX channel +type ChannelValue struct { + Universe uint16 + Channel, Value uint8 +} + +// Controller is a convenience interface to use within this application +type Controller interface { + SetDMXChannelValue(value ChannelValue) + SetDMXChannelValues(values []ChannelValue) + Start() error + Stop() +} diff --git a/pkg/cntl/dmx/animation.go b/pkg/cntl/dmx/animation.go index 3d7d22a..05d27e8 100644 --- a/pkg/cntl/dmx/animation.go +++ b/pkg/cntl/dmx/animation.go @@ -2,6 +2,7 @@ package dmx import ( "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" ) diff --git a/pkg/cntl/dmx/animation_test.go b/pkg/cntl/dmx/animation_test.go index b4bccc4..275e2ee 100644 --- a/pkg/cntl/dmx/animation_test.go +++ b/pkg/cntl/dmx/animation_test.go @@ -1,9 +1,10 @@ package dmx import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/dmx/device.go b/pkg/cntl/dmx/device.go index 26c5b70..be955ae 100644 --- a/pkg/cntl/dmx/device.go +++ b/pkg/cntl/dmx/device.go @@ -2,6 +2,7 @@ package dmx import ( "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" ) diff --git a/pkg/cntl/dmx/device_test.go b/pkg/cntl/dmx/device_test.go index 4592da7..0249915 100644 --- a/pkg/cntl/dmx/device_test.go +++ b/pkg/cntl/dmx/device_test.go @@ -2,9 +2,10 @@ package dmx import ( "errors" - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/dmx/easing.go b/pkg/cntl/dmx/easing.go index 2826d48..219c6ab 100644 --- a/pkg/cntl/dmx/easing.go +++ b/pkg/cntl/dmx/easing.go @@ -2,6 +2,7 @@ package dmx import ( "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/creasty/go-easing" diff --git a/pkg/cntl/dmx/merge_test.go b/pkg/cntl/dmx/merge_test.go index 8cd794c..71dd419 100644 --- a/pkg/cntl/dmx/merge_test.go +++ b/pkg/cntl/dmx/merge_test.go @@ -1,9 +1,10 @@ package dmx import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/dmx/preset.go b/pkg/cntl/dmx/preset.go index 25f28f3..64885f4 100644 --- a/pkg/cntl/dmx/preset.go +++ b/pkg/cntl/dmx/preset.go @@ -2,6 +2,7 @@ package dmx import ( "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" ) diff --git a/pkg/cntl/dmx/preset_test.go b/pkg/cntl/dmx/preset_test.go index 651a10d..d83573a 100644 --- a/pkg/cntl/dmx/preset_test.go +++ b/pkg/cntl/dmx/preset_test.go @@ -1,9 +1,10 @@ package dmx import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/dmx/scene_test.go b/pkg/cntl/dmx/scene_test.go index 0dac6f1..bd376ae 100644 --- a/pkg/cntl/dmx/scene_test.go +++ b/pkg/cntl/dmx/scene_test.go @@ -1,10 +1,11 @@ package dmx import ( - "github.com/StageAutoControl/controller/pkg/cntl" "reflect" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/dmx/transition.go b/pkg/cntl/dmx/transition.go index 9a34751..c601ab9 100644 --- a/pkg/cntl/dmx/transition.go +++ b/pkg/cntl/dmx/transition.go @@ -2,8 +2,9 @@ package dmx import ( "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "math" + + "github.com/StageAutoControl/controller/pkg/cntl" ) // RenderTransition renders the given DMXTransition to an array of DMXCommands to be sent to a DMX device diff --git a/pkg/cntl/dmx/transition_test.go b/pkg/cntl/dmx/transition_test.go index d79f37b..0298122 100644 --- a/pkg/cntl/dmx/transition_test.go +++ b/pkg/cntl/dmx/transition_test.go @@ -1,10 +1,11 @@ package dmx import ( - "github.com/StageAutoControl/controller/pkg/cntl" "log" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/dmx/utils_test.go b/pkg/cntl/dmx/utils_test.go index 2a78b87..3bf44b7 100644 --- a/pkg/cntl/dmx/utils_test.go +++ b/pkg/cntl/dmx/utils_test.go @@ -1,9 +1,10 @@ package dmx import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/midi/midi_test.go b/pkg/cntl/midi/midi_test.go index 1c0664f..d7708b4 100644 --- a/pkg/cntl/midi/midi_test.go +++ b/pkg/cntl/midi/midi_test.go @@ -1,8 +1,9 @@ package midi import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" ) func TestStreamlineMidiCommands(t *testing.T) { diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index 5d65ee5..cb09f5b 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -2,9 +2,10 @@ package playback import ( "context" - "github.com/StageAutoControl/controller/pkg/cntl" "time" + "github.com/StageAutoControl/controller/pkg/cntl" + "fmt" "github.com/StageAutoControl/controller/pkg/cntl/song" diff --git a/pkg/cntl/playback/player_test.go b/pkg/cntl/playback/player_test.go index 1ac96c0..3348288 100644 --- a/pkg/cntl/playback/player_test.go +++ b/pkg/cntl/playback/player_test.go @@ -1,9 +1,10 @@ package playback import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" "time" + + "github.com/StageAutoControl/controller/pkg/cntl" ) func TestCalcRenderSpeed(t *testing.T) { diff --git a/pkg/cntl/song/helper.go b/pkg/cntl/song/helper.go index a54ce62..c83302e 100644 --- a/pkg/cntl/song/helper.go +++ b/pkg/cntl/song/helper.go @@ -2,9 +2,10 @@ package song import ( "errors" - "github.com/StageAutoControl/controller/pkg/cntl" "log" "reflect" + + "github.com/StageAutoControl/controller/pkg/cntl" ) // max returns the bigger of two given uint64 values diff --git a/pkg/cntl/song/helper_test.go b/pkg/cntl/song/helper_test.go index 947d0d4..16ebc9a 100644 --- a/pkg/cntl/song/helper_test.go +++ b/pkg/cntl/song/helper_test.go @@ -1,8 +1,9 @@ package song import ( - "github.com/StageAutoControl/controller/pkg/cntl" "testing" + + "github.com/StageAutoControl/controller/pkg/cntl" ) func TestCalcBarLength(t *testing.T) { diff --git a/pkg/cntl/song/song.go b/pkg/cntl/song/song.go index 2b61839..deeea7c 100644 --- a/pkg/cntl/song/song.go +++ b/pkg/cntl/song/song.go @@ -2,6 +2,7 @@ package song import ( "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/cntl/dmx" diff --git a/pkg/cntl/song/song_test.go b/pkg/cntl/song/song_test.go index 06cce27..bec7c09 100644 --- a/pkg/cntl/song/song_test.go +++ b/pkg/cntl/song/song_test.go @@ -2,10 +2,11 @@ package song import ( "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "reflect" "testing" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/fixtures" ) diff --git a/pkg/cntl/transport/artnet.go b/pkg/cntl/transport/artnet.go index b175819..64a6152 100644 --- a/pkg/cntl/transport/artnet.go +++ b/pkg/cntl/transport/artnet.go @@ -1,71 +1,34 @@ package transport import ( - "encoding/binary" - "errors" - "fmt" + art "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/cntl" - "time" - - artnetTransport "github.com/StageAutoControl/controller/pkg/cntl/transport/artnet" - "github.com/jsimonetti/go-artnet" - "github.com/sirupsen/logrus" ) // ArtNet is a transport for the ArtNet protocol (DMX over UDP/IP) type ArtNet struct { - name string - c *artnet.Controller - state artnetTransport.State + controller art.Controller } // NewArtNet returns a new ArtNet transport instance -func NewArtNet(logger *logrus.Entry, name string) (*ArtNet, error) { - ip, err := artnetTransport.FindArtNetIP() - if err != nil { - return nil, fmt.Errorf("failed to find the art-net IP: %v", err) - } - - if len(ip) == 0 { - return nil, errors.New("failed to find the art-net IP: No interface found") - } - - c := artnet.NewController(name, ip, artnet.NewLogger(logger)) - if err := c.Start(); err != nil { - return nil, fmt.Errorf("failed to start controller: %v", err) - } - - logger.Info("Waiting 5 seconds for nodes to register") - time.Sleep(5 * time.Second) - +func NewArtNet(controller art.Controller) (*ArtNet, error) { return &ArtNet{ - name: name, - c: c, - state: artnetTransport.NewState(), + controller: controller, }, nil } func (a *ArtNet) Write(cmd cntl.Command) error { + values := make([]art.ChannelValue, 0) + for _, c := range cmd.DMXCommands { - a.state.Set(uint16(c.Universe), uint8(c.Channel), c.Value.Uint8()) + values = append(values, art.ChannelValue{ + Universe: uint16(c.Universe), + Channel: uint8(c.Channel), + Value: c.Value.Uint8(), + }) } - for u, dmx := range a.state { - a.c.SendDMXToAddress(dmx, UniverseToAddress(cntl.DMXUniverse(u))) - } + a.controller.SetDMXChannelValues(values) return nil } - -// UniverseToAddress converts a dmx universe to a artnet address -func UniverseToAddress(u cntl.DMXUniverse) artnet.Address { - // https://play.golang.org/p/pdQPC5u7JX - - v := make([]uint8, 2) - binary.BigEndian.PutUint16(v, uint16(u)) - - return artnet.Address{ - Net: v[0], - SubUni: v[1], - } -} diff --git a/pkg/cntl/transport/bar_logger.go b/pkg/cntl/transport/bar_logger.go index c0e7fa0..3e08799 100644 --- a/pkg/cntl/transport/bar_logger.go +++ b/pkg/cntl/transport/bar_logger.go @@ -2,6 +2,7 @@ package transport import ( "fmt" + "github.com/StageAutoControl/controller/pkg/cntl" ) diff --git a/pkg/cntl/transport/midi.go b/pkg/cntl/transport/midi.go index 29bfc53..55e2c53 100644 --- a/pkg/cntl/transport/midi.go +++ b/pkg/cntl/transport/midi.go @@ -2,9 +2,10 @@ package transport import ( "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "strconv" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/rakyll/portmidi" "github.com/sirupsen/logrus" ) diff --git a/pkg/cntl/transport/stream.go b/pkg/cntl/transport/stream.go index 1ec0f84..c5bf177 100644 --- a/pkg/cntl/transport/stream.go +++ b/pkg/cntl/transport/stream.go @@ -2,9 +2,10 @@ package transport import ( "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "io" + "github.com/StageAutoControl/controller/pkg/cntl" + "strings" "github.com/sirupsen/logrus" diff --git a/pkg/cntl/transport/visualizer.go b/pkg/cntl/transport/visualizer.go index 7870e50..3000ef4 100644 --- a/pkg/cntl/transport/visualizer.go +++ b/pkg/cntl/transport/visualizer.go @@ -3,10 +3,11 @@ package transport import ( "encoding/json" "fmt" - "github.com/StageAutoControl/controller/pkg/cntl" "net" "strings" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/sirupsen/logrus" ) diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 43e3167..64bdbff 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -1,15 +1,5 @@ package cntl -// A Loader is responsible for loading the applications data. This could either be a remote or a local store. -type Loader interface { - Load() (*DataStore, error) -} - -// An Enhancer enhances the given datastore -type Enhancer interface { - Enhance(*DataStore) []error -} - // SongSelector is a selector for a song type SongSelector struct { ID string `json:"id" yaml:"id"` diff --git a/pkg/disk/loader.go b/pkg/disk/loader.go new file mode 100644 index 0000000..258b7eb --- /dev/null +++ b/pkg/disk/loader.go @@ -0,0 +1,112 @@ +package disk + +import "github.com/StageAutoControl/controller/pkg/cntl" + +// Loader loads the DataStore from the storage +type Loader struct { + storage *Storage +} + +// NewLoader returns a new Loader instance +func NewLoader(storage *Storage) *Loader { + return &Loader{ + storage: storage, + } +} + +// Load the data from the storage and return a populated data store +func (l *Loader) Load() (*cntl.DataStore, error) { + data := cntl.NewStore() + + setList := &cntl.SetList{} + for _, id := range l.storage.List(setList) { + err := l.storage.Read(id, setList) + if err != nil { + return nil, err + } + + data.SetLists[id] = setList + } + + song := &cntl.Song{} + for _, id := range l.storage.List(song) { + err := l.storage.Read(id, song) + if err != nil { + return nil, err + } + + data.Songs[id] = song + } + + dmxDevice := &cntl.DMXDevice{} + for _, id := range l.storage.List(dmxDevice) { + err := l.storage.Read(id, dmxDevice) + if err != nil { + return nil, err + } + + data.DMXDevices[id] = dmxDevice + } + + dmxDeviceGroup := &cntl.DMXDeviceGroup{} + for _, id := range l.storage.List(dmxDeviceGroup) { + err := l.storage.Read(id, dmxDeviceGroup) + if err != nil { + return nil, err + } + + data.DMXDeviceGroups[id] = dmxDeviceGroup + } + + dmxDeviceType := &cntl.DMXDeviceType{} + for _, id := range l.storage.List(dmxDeviceType) { + err := l.storage.Read(id, dmxDeviceType) + if err != nil { + return nil, err + } + + data.DMXDeviceTypes[id] = dmxDeviceType + } + + dmxPreset := &cntl.DMXPreset{} + for _, id := range l.storage.List(dmxPreset) { + err := l.storage.Read(id, dmxPreset) + if err != nil { + return nil, err + } + + data.DMXPresets[id] = dmxPreset + } + + dmxScene := &cntl.DMXScene{} + for _, id := range l.storage.List(dmxScene) { + err := l.storage.Read(id, dmxScene) + if err != nil { + return nil, err + } + + data.DMXScenes[id] = dmxScene + } + + dmxAnimation := &cntl.DMXAnimation{} + for _, id := range l.storage.List(dmxAnimation) { + err := l.storage.Read(id, dmxAnimation) + if err != nil { + return nil, err + } + + data.DMXAnimations[id] = dmxAnimation + } + + dmxTransition := &cntl.DMXTransition{} + for _, id := range l.storage.List(dmxTransition) { + err := l.storage.Read(id, dmxTransition) + if err != nil { + return nil, err + } + + data.DMXTransitions[id] = dmxTransition + } + + return data, nil +} diff --git a/pkg/storage/storage.go b/pkg/disk/storage.go similarity index 99% rename from pkg/storage/storage.go rename to pkg/disk/storage.go index df49e21..ad82c4d 100644 --- a/pkg/storage/storage.go +++ b/pkg/disk/storage.go @@ -1,4 +1,4 @@ -package storage +package disk import ( "encoding/json" diff --git a/pkg/storage/storage_test.go b/pkg/disk/storage_test.go similarity index 99% rename from pkg/storage/storage_test.go rename to pkg/disk/storage_test.go index 16bf467..d953211 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/disk/storage_test.go @@ -1,4 +1,4 @@ -package storage +package disk import ( "io/ioutil" diff --git a/pkg/enhance/enhancer.go b/pkg/enhance/enhancer.go deleted file mode 100644 index 1dcda94..0000000 --- a/pkg/enhance/enhancer.go +++ /dev/null @@ -1,6 +0,0 @@ -package enhance - -import "github.com/StageAutoControl/controller/pkg/cntl" - -// Enhancers stores the globally registered enhancers -var Enhancers = make([]cntl.Enhancer, 0) diff --git a/pkg/enhance/name_to_id.go b/pkg/enhance/name_to_id.go deleted file mode 100644 index e91447c..0000000 --- a/pkg/enhance/name_to_id.go +++ /dev/null @@ -1,215 +0,0 @@ -package enhance - -import ( - "fmt" - - "github.com/StageAutoControl/controller/pkg/cntl" -) - -func init() { - Enhancers = append(Enhancers, &NameToIDEnhancer{}) -} - -// NameToIDEnhancer enhances the given data by resolving names to IDs -type NameToIDEnhancer struct{} - -// Enhance implements the cntl.Enhancer interface, executes the single entity enhancement methods -func (e *NameToIDEnhancer) Enhance(store *cntl.DataStore) []error { - errs := make([]error, 0) - - errs = append(errs, e.setLists(store)...) - errs = append(errs, e.songs(store)...) - errs = append(errs, e.scenes(store)...) - errs = append(errs, e.presets(store)...) - errs = append(errs, e.deviceGroups(store)...) - - return errs -} - -func (e *NameToIDEnhancer) setLists(store *cntl.DataStore) []error { - errs := make([]error, 0) - - for _, s := range store.SetLists { - for i := range s.Songs { - if s.Songs[i].Name != "" { - s.Songs[i].ID = e.findSong(s.Songs[i].Name, store.Songs) - if s.Songs[i].ID == "" { - errs = append(errs, fmt.Errorf("cannot find Song %q", s.Songs[i].Name)) - } - } - } - } - - return errs -} - -func (e *NameToIDEnhancer) songs(store *cntl.DataStore) []error { - errs := make([]error, 0) - - for _, s := range store.Songs { - for i := range s.DMXScenes { - if s.DMXScenes[i].Name != "" { - s.DMXScenes[i].ID = e.findScene(s.DMXScenes[i].Name, store.DMXScenes) - if s.DMXScenes[i].ID == "" { - errs = append(errs, fmt.Errorf("cannot find scene %q", s.DMXScenes[i].Name)) - } - } - } - } - - return errs -} - -func (e *NameToIDEnhancer) scenes(store *cntl.DataStore) []error { - errs := make([]error, 0) - - for _, s := range store.DMXScenes { - for i := range s.SubScenes { - for p := range s.SubScenes[i].DeviceParams { - errs = append(errs, e.nestedDeviceParams(&s.SubScenes[i].DeviceParams[p], store)...) - } - - if s.SubScenes[i].Preset != nil && s.SubScenes[i].Preset.Name != "" { - s.SubScenes[i].Preset.ID = e.findPreset(s.SubScenes[i].Preset.Name, store.DMXPresets) - if s.SubScenes[i].Preset.ID == "" { - errs = append(errs, fmt.Errorf("cannot find preset %q", s.SubScenes[i].Preset.Name)) - } - } - } - } - - return errs -} - -func (e *NameToIDEnhancer) presets(store *cntl.DataStore) []error { - errs := make([]error, 0) - - for _, s := range store.DMXPresets { - for i := range s.DeviceParams { - errs = append(errs, e.nestedDeviceParams(&s.DeviceParams[i], store)...) - } - } - - return errs -} - -func (e *NameToIDEnhancer) nestedDeviceParams(params *cntl.DMXDeviceParams, store *cntl.DataStore) []error { - errs := make([]error, 0) - - if params.Device != nil && params.Device.Name != "" { - params.Device.ID = e.findDevice(params.Device.Name, store.DMXDevices) - if params.Device.ID == "" { - errs = append(errs, fmt.Errorf("cannot find device %q", params.Device.Name)) - } - } - - if params.Group != nil && params.Group.ID == "" { - params.Group.ID = e.findDeviceGroup(params.Group.Name, store.DMXDeviceGroups) - if params.Group.ID == "" { - errs = append(errs, fmt.Errorf("cannot find device group %q", params.Group.Name)) - } - } - - if params.Animation != nil && params.Animation.ID == "" { - params.Animation.ID = e.findAnimation(params.Animation.Name, store.DMXAnimations) - if params.Animation.ID == "" { - errs = append(errs, fmt.Errorf("cannot find animation %q", params.Animation.Name)) - } - } - - if params.Transition != nil && params.Transition.ID == "" { - params.Transition.ID = e.findTransition(params.Transition.Name, store.DMXTransitions) - if params.Transition.ID == "" { - errs = append(errs, fmt.Errorf("cannot find transition %q", params.Transition.Name)) - } - } - - return errs -} - -func (e *NameToIDEnhancer) deviceGroups(store *cntl.DataStore) []error { - errs := make([]error, 0) - - for _, s := range store.DMXDeviceGroups { - for i := range s.Devices { - if s.Devices[i].Name != "" { - s.Devices[i].ID = e.findDevice(s.Devices[i].Name, store.DMXDevices) - if s.Devices[i].ID == "" { - errs = append(errs, fmt.Errorf("cannot find device %q", s.Devices[i].Name)) - } - } - } - } - - return errs -} - -func (e *NameToIDEnhancer) findSong(name string, values map[string]*cntl.Song) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} - -func (e *NameToIDEnhancer) findScene(name string, values map[string]*cntl.DMXScene) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} - -func (e *NameToIDEnhancer) findPreset(name string, values map[string]*cntl.DMXPreset) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} - -func (e *NameToIDEnhancer) findDevice(name string, values map[string]*cntl.DMXDevice) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} - -func (e *NameToIDEnhancer) findDeviceGroup(name string, values map[string]*cntl.DMXDeviceGroup) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} - -func (e *NameToIDEnhancer) findAnimation(name string, values map[string]*cntl.DMXAnimation) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} - -func (e *NameToIDEnhancer) findTransition(name string, values map[string]*cntl.DMXTransition) string { - for _, v := range values { - if v.Name == name { - return v.ID - } - } - - return "" -} diff --git a/pkg/internal/logging/logger.go b/pkg/internal/logging/logger.go new file mode 100644 index 0000000..8d185f6 --- /dev/null +++ b/pkg/internal/logging/logger.go @@ -0,0 +1,30 @@ +package logging + +type Logger interface { + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Printf(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Warningf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) + Panicf(format string, args ...interface{}) + + Debug(args ...interface{}) + Info(args ...interface{}) + Print(args ...interface{}) + Warn(args ...interface{}) + Warning(args ...interface{}) + Error(args ...interface{}) + Fatal(args ...interface{}) + Panic(args ...interface{}) + + Debugln(args ...interface{}) + Infoln(args ...interface{}) + Println(args ...interface{}) + Warnln(args ...interface{}) + Warningln(args ...interface{}) + Errorln(args ...interface{}) + Fatalln(args ...interface{}) + Panicln(args ...interface{}) +} diff --git a/pkg/loader/files/.gitignore b/pkg/loader/files/.gitignore deleted file mode 100644 index 9daeafb..0000000 --- a/pkg/loader/files/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test diff --git a/pkg/loader/files/db.go b/pkg/loader/files/db.go deleted file mode 100644 index ba0cc7c..0000000 --- a/pkg/loader/files/db.go +++ /dev/null @@ -1,43 +0,0 @@ -package files - -import ( - "github.com/StageAutoControl/controller/pkg/cntl" -) - -type fileData struct { - SetLists []*cntl.SetList `json:"set_lists"` - Songs []*cntl.Song `json:"songs"` - DMXScenes []*cntl.DMXScene `json:"dmx_scenes"` - DMXPresets []*cntl.DMXPreset `json:"dmx_presets"` - DMXAnimations []*cntl.DMXAnimation `json:"dmx_animations"` - DMXTransitions []*cntl.DMXTransition `json:"dmx_transitions"` - DMXDevices []*cntl.DMXDevice `json:"dmx_devices"` - DMXDeviceTypes []*cntl.DMXDeviceType `json:"dmx_device_types"` - DMXDeviceGroups []*cntl.DMXDeviceGroup `json:"dmx_device_groups"` -} - -// Database is a file repository -type Database struct { - dataDir string -} - -// New crates a new file repository and returns it. -func New(dataDir string) *Database { - return &Database{ - dataDir: dataDir, - } -} - -// Load implements cntl.Loader and loads the data from filesystem -func (d *Database) Load() (*cntl.DataStore, error) { - store := cntl.NewStore() - data, err := d.readDir(d.dataDir) - - if err != nil { - return nil, err - } - - expandData(store, data) - - return store, nil -} diff --git a/pkg/loader/files/db_test.go b/pkg/loader/files/db_test.go deleted file mode 100644 index 43a5e75..0000000 --- a/pkg/loader/files/db_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package files - -import ( - "testing" - - "github.com/StageAutoControl/controller/pkg/internal/fixtures" -) - -func TestRepository_Load(t *testing.T) { - dir := "./fixtures" - fix := fixtures.DataStore() - - loader := New(dir) - data, err := loader.Load() - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - for key, sl := range fix.SetLists { - dsl, ok := data.SetLists[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if dsl.ID != sl.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, s := range fix.Songs { - ds, ok := data.Songs[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if ds.ID != s.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, s := range fix.DMXScenes { - ds, ok := data.DMXScenes[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if ds.ID != s.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, p := range fix.DMXPresets { - dp, ok := data.DMXPresets[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if dp.ID != p.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, a := range fix.DMXAnimations { - da, ok := data.DMXAnimations[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if da.ID != a.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, tr := range fix.DMXTransitions { - dt, ok := data.DMXTransitions[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if dt.ID != tr.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, dg := range fix.DMXDeviceGroups { - ddg, ok := data.DMXDeviceGroups[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if ddg.ID != dg.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - - for key, d := range fix.DMXDevices { - dd, ok := data.DMXDevices[key] - if !ok { - t.Fatalf("ID %q not found \n", key) - } - - if dd.ID != d.ID { - t.Errorf("ID %q is not equal \n", key) - } - } - -} diff --git a/pkg/loader/files/fixtures/dmx_animations.json b/pkg/loader/files/fixtures/dmx_animations.json deleted file mode 100755 index 416f527..0000000 --- a/pkg/loader/files/fixtures/dmx_animations.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "dmx_animations": [ - { - "frames": [ - { - "at": 0, - "params": { - "blue": 31 - } - }, - { - "at": 1, - "params": { - "blue": 63 - } - }, - { - "at": 2, - "params": { - "blue": 127 - } - }, - { - "at": 3, - "params": { - "blue": 255 - } - } - ], - "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", - "length": 4 - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_device_groups.json b/pkg/loader/files/fixtures/dmx_device_groups.json deleted file mode 100755 index 841c267..0000000 --- a/pkg/loader/files/fixtures/dmx_device_groups.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "dmx_device_groups": [ - { - "devices": [ - { - "tags": [ - "par", - "left" - ] - } - ], - "id": "475b71a0-0b16-11e7-9406-e3f678e8b788", - "name": "All PARs on the left side" - }, - { - "devices": [ - { - "tags": [ - "par", - "right" - ] - } - ], - "id": "29f7adf8-0b17-11e7-bd45-9f82a70b477b", - "name": "All PARs on the right side" - }, - { - "devices": [ - { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - } - ], - "id": "cb58bc10-0b16-11e7-b45a-7bee591b0adb", - "name": "LED Bar infront the drums" - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_device_types.json b/pkg/loader/files/fixtures/dmx_device_types.json deleted file mode 100755 index eb3a9bb..0000000 --- a/pkg/loader/files/fixtures/dmx_device_types.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "dmx_device_types": [ - { - "channelCount": 67, - "channelsPerLED": 4, - "dimmerChannel": 1, - "dimmerEnabled": true, - "id": "1555d67e-1187-11e7-8135-9b41038b5b75", - "key": "LEDBar67", - "leds": [ - { - "blue": 2, - "green": 1, - "position": 0, - "red": 0, - "white": 3 - }, - { - "blue": 6, - "green": 5, - "position": 1, - "red": 4, - "white": 7 - }, - { - "blue": 10, - "green": 9, - "position": 2, - "red": 8, - "white": 11 - }, - { - "blue": 14, - "green": 13, - "position": 3, - "red": 12, - "white": 15 - }, - { - "blue": 18, - "green": 17, - "position": 4, - "red": 16, - "white": 19 - }, - { - "blue": 22, - "green": 21, - "position": 5, - "red": 20, - "white": 23 - }, - { - "blue": 26, - "green": 25, - "position": 6, - "red": 24, - "white": 27 - }, - { - "blue": 30, - "green": 29, - "position": 7, - "red": 28, - "white": 31 - }, - { - "blue": 34, - "green": 33, - "position": 8, - "red": 32, - "white": 35 - }, - { - "blue": 38, - "green": 37, - "position": 9, - "red": 36, - "white": 39 - }, - { - "blue": 42, - "green": 41, - "position": 10, - "red": 40, - "white": 43 - }, - { - "blue": 46, - "green": 45, - "position": 11, - "red": 44, - "white": 47 - }, - { - "blue": 50, - "green": 49, - "position": 12, - "red": 48, - "white": 51 - }, - { - "blue": 54, - "green": 53, - "position": 13, - "red": 52, - "white": 55 - }, - { - "blue": 58, - "green": 57, - "position": 14, - "red": 56, - "white": 59 - }, - { - "blue": 62, - "green": 61, - "position": 15, - "red": 60, - "white": 63 - } - ], - "modeChannel": 0, - "modeEnabled": true, - "name": "LED-Bar 67 Channel", - "strobeChannel": 2, - "strobeEnabled": true - }, - { - "channelCount": 5, - "channelsPerLED": 3, - "dimmerChannel": 3, - "dimmerEnabled": true, - "id": "628fc3ea-1188-11e7-8824-5f72d80c17b6", - "key": "PAR5", - "leds": [ - { - "blue": 2, - "green": 1, - "position": 0, - "red": 0 - } - ], - "modeChannel": 0, - "modeEnabled": false, - "name": "PAR 5 channel", - "strobeChannel": 4, - "strobeEnabled": true - }, - { - "id": "5ccc43ee-118c-11e7-8d53-974b41748b71", - "key": "Strobe", - "name": "Strobe", - "strobeChannel": 0, - "strobeEnabled": true - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_devices.json b/pkg/loader/files/fixtures/dmx_devices.json deleted file mode 100755 index 78b046a..0000000 --- a/pkg/loader/files/fixtures/dmx_devices.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "dmx_devices": [ - { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e", - "name": "LED-Bar below drums front", - "startChannel": 222, - "tags": [ - "bar", - "drums" - ], - "typeId": "1555d67e-1187-11e7-8135-9b41038b5b75", - "universe": 1 - }, - { - "id": "s429fc37c-0b17-11e7-8b94-c3b6519355d3", - "name": "PAR inner left, stand 1 position 1", - "startChannel": 10, - "tags": [ - "par", - "left", - "inner", - "stand", - "drums-left" - ], - "typeId": "par", - "universe": 2 - }, - { - "id": "4a545466-0b17-11e7-9c61-d3c0693099ab", - "name": "PAR inner left, stand 1 position 2", - "startChannel": 14, - "tags": [ - "par", - "left", - "inner", - "stand", - "drums-left" - ], - "typeId": "par", - "universe": 2 - }, - { - "id": "5e0335e0-0b17-11e7-ad6c-63a7138d926c", - "name": "PAR inner right, stand 2 position 1", - "startChannel": 26, - "tags": [ - "par", - "right", - "inner", - "stand", - "drums-right" - ], - "typeId": "par", - "universe": 2 - }, - { - "id": "620101f4-0b17-11e7-85cc-539952d9aef2", - "name": "PAR inner right, stand 2 position 2", - "startChannel": 30, - "tags": [ - "par", - "right", - "inner", - "stand", - "drums-right" - ], - "typeId": "par", - "universe": 2 - }, - { - "id": "6f7bca8a-0b17-11e7-b604-a356da737e54", - "name": "Strobe Vocs", - "startChannel": 202, - "tags": [ - "strobe-back", - "vocs" - ], - "typeId": "strobe", - "universe": 1 - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_presets.json b/pkg/loader/files/fixtures/dmx_presets.json deleted file mode 100755 index aca4878..0000000 --- a/pkg/loader/files/fixtures/dmx_presets.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "dmx_presets": [ - { - "deviceParams": [ - { - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - }, - "params": [ - { - "red": 255 - } - ] - } - ], - "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc", - "name": "Test-Preset 1" - }, - { - "deviceParams": [ - { - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - }, - "params": [ - { - "blue": 255 - } - ] - } - ], - "id": "11adf93e-0e7b-11e7-998c-5bd2bd0df396", - "name": "Test-Preset 2" - }, - { - "deviceParams": [ - { - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - }, - "params": [ - { - "green": 255 - } - ] - } - ], - "id": "652e716a-0e7b-11e7-b92a-8f2ff28ba235", - "name": "Test-Preset 4" - }, - { - "deviceParams": [ - { - "group": { - "id": "cb58bc10-0b16-11e7-b45a-7bee591b0adb" - }, - "params": [ - { - "strobe": 255 - } - ] - } - ], - "id": "5d3a415a-0b15-11e7-90b9-03c2b960e034", - "name": "Test-Preset 4" - }, - { - "deviceParams": [ - { - "group": { - "id": "475b71a0-0b16-11e7-9406-e3f678e8b788" - }, - "params": [ - { - "red": 200 - } - ] - } - ], - "id": "4e3c2e84-0b15-11e7-a076-4b5bbb4c19bf", - "name": "Test-Preset 5" - } - ] -} diff --git a/pkg/loader/files/fixtures/dmx_scenes.json b/pkg/loader/files/fixtures/dmx_scenes.json deleted file mode 100755 index 89d42d3..0000000 --- a/pkg/loader/files/fixtures/dmx_scenes.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "dmx_scenes": [ - { - "id": "492cef2e-0b14-11e7-be89-c3fa25f9cabb", - "name": "Test-Scene 1", - "noteCount": 4, - "noteValue": 4, - "subScenes": [ - { - "at": [ - 0, - 1, - 2, - 3 - ], - "preset": { - "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" - } - } - ] - }, - { - "id": "a44f8dee-0b14-11e7-b5b9-bf1015384192", - "name": "Test-Scene 2", - "noteCount": 2, - "noteValue": 4, - "subScenes": [ - { - "at": [ - 0, - 1 - ], - "preset": { - "id": "11adf93e-0e7b-11e7-998c-5bd2bd0df396" - } - } - ] - }, - { - "id": "99b86a5e-0e7a-11e7-a01a-5b5fbdeba3d6", - "name": "Test-Scene 3", - "noteCount": 8, - "noteValue": 4, - "subScenes": [ - { - "at": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7 - ], - "deviceParams": [ - { - "animation": { - "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" - }, - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - } - } - ] - } - ] - }, - { - "id": "b82f4750-0e7a-11e7-9522-0f9d6d69958a", - "name": "Test-Scene 4", - "noteCount": 4, - "noteValue": 4, - "subScenes": [ - { - "at": [ - 0, - 1, - 2, - 3 - ], - "preset": { - "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" - } - } - ] - }, - { - "id": "5adec126-6618-42ca-b930-4d18f4524328", - "name": "Test-Scene 5", - "noteCount": 32, - "noteValue": 32, - "subScenes": [ - { - "at": [ - 0 - ], - "deviceParams": [ - { - "transition": { - "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" - }, - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - } - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_transitions.json b/pkg/loader/files/fixtures/dmx_transitions.json deleted file mode 100755 index 026db6e..0000000 --- a/pkg/loader/files/fixtures/dmx_transitions.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "dmx_transitions": [ - { - "id": "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21", - "name": "Blue Bar pulsing on", - "ease": "InOutQuad", - "length": 8, - "params": [ - { - "from": { - "led": 1, - "blue": 0 - }, - "to": { - "led": 1, - "blue": 255 - } - }, - { - "from": { - "led": 2, - "blue": 0 - }, - "to": { - "led": 2, - "blue": 255 - } - } - ] - }, - { - "id": "525eaa7e-fb2d-4608-b413-559d284b3c85", - "name": "Blue Bar pulsing off", - "ease": "InOutQuad", - "length": 8, - "params": [ - { - "from": { - "led": 1, - "blue": 255 - }, - "to": { - "led": 1, - "blue": 0 - } - }, - { - "from": { - "led": 2, - "blue": 255 - }, - "to": { - "led": 2, - "blue": 0 - } - } - ] - }, - { - "id": "e683873b-20da-4fd4-ac62-271925c68047", - "name": "From Red to Green and a little blue", - "ease": "InOutQuad", - "length": 8, - "params": [ - { - "from": { - "led": 1, - "red": 255, - "green": 0, - "blue": 31 - }, - "to": { - "led": 1, - "red": 0, - "green": 255, - "blue": 127 - } - } - ] - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/set_lists.json b/pkg/loader/files/fixtures/set_lists.json deleted file mode 100755 index 71b4409..0000000 --- a/pkg/loader/files/fixtures/set_lists.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "set_lists": [ - { - "id": "f5b4be8a-0b18-11e7-b837-4bac99d86956", - "name": "Regular gig", - "songs": [ - { - "id": "3c1065c8-0b14-11e7-96eb-5b134621c411" - } - ] - } - ] -} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/songs.json b/pkg/loader/files/fixtures/songs.json deleted file mode 100755 index cccd171..0000000 --- a/pkg/loader/files/fixtures/songs.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "songs": [ - { - "barChanges": [ - { - "at": 0, - "noteCount": 4, - "noteValue": 4, - "speed": 160 - }, - { - "at": 512, - "noteCount": 3, - "noteValue": 4 - }, - { - "at": 1184, - "noteCount": 7, - "noteValue": 8 - }, - { - "at": 1632, - "noteCount": 4, - "noteValue": 4 - } - ], - "dmxScenes": [ - { - "at": 0, - "id": "492cef2e-0b14-11e7-be89-c3fa25f9cabb", - "repeat": 4 - }, - { - "at": 512, - "id": "a44f8dee-0b14-11e7-b5b9-bf1015384192", - "repeat": 3 - }, - { - "at": 1408, - "id": "652e716a-0e7b-11e7-b92a-8f2ff28ba235", - "repeat": 2 - }, - { - "at": 1920, - "id": "5d3a415a-0b15-11e7-90b9-03c2b960e034", - "repeat": 0 - } - ], - "id": "3c1065c8-0b14-11e7-96eb-5b134621c411", - "name": "Test song" - } - ] -} diff --git a/pkg/loader/files/read.go b/pkg/loader/files/read.go deleted file mode 100644 index 3304c90..0000000 --- a/pkg/loader/files/read.go +++ /dev/null @@ -1,98 +0,0 @@ -package files - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "path/filepath" - - "github.com/StageAutoControl/controller/pkg/cntl" -) - -func (d *Database) readDir(dir string) (*fileData, error) { - files, err := ioutil.ReadDir(dir) - if err != nil { - return nil, err - } - - data := new(fileData) - for _, file := range files { - file := filepath.Join(dir, file.Name()) - fData := new(fileData) - if err := d.readFile(file, fData); err != nil { - return nil, fmt.Errorf("error reading file %q: %v", file, err) - } - - data = mergeFileData(data, fData) - } - - return data, nil -} - -func (d *Database) readFile(file string, target interface{}) error { - b, err := ioutil.ReadFile(file) - if err != nil { - return fmt.Errorf("error reading file %q: %v", file, err) - } - - err = json.Unmarshal(b, target) - if err != nil { - return fmt.Errorf("unable to parse content of %q: %v", file, err) - } - - return nil -} - -func mergeFileData(data, fd *fileData) *fileData { - newData := new(fileData) - - newData.SetLists = append(data.SetLists, fd.SetLists...) - newData.Songs = append(data.Songs, fd.Songs...) - newData.DMXScenes = append(data.DMXScenes, fd.DMXScenes...) - newData.DMXPresets = append(data.DMXPresets, fd.DMXPresets...) - newData.DMXAnimations = append(data.DMXAnimations, fd.DMXAnimations...) - newData.DMXTransitions = append(data.DMXTransitions, fd.DMXTransitions...) - newData.DMXDevices = append(data.DMXDevices, fd.DMXDevices...) - newData.DMXDeviceTypes = append(data.DMXDeviceTypes, fd.DMXDeviceTypes...) - newData.DMXDeviceGroups = append(data.DMXDeviceGroups, fd.DMXDeviceGroups...) - - return newData -} - -func expandData(data *cntl.DataStore, fileData *fileData) { - for _, sl := range fileData.SetLists { - data.SetLists[sl.ID] = sl - } - - for _, s := range fileData.Songs { - data.Songs[s.ID] = s - } - - for _, d := range fileData.DMXDevices { - data.DMXDevices[d.ID] = d - } - - for _, dg := range fileData.DMXDeviceGroups { - data.DMXDeviceGroups[dg.ID] = dg - } - - for _, dt := range fileData.DMXDeviceTypes { - data.DMXDeviceTypes[dt.ID] = dt - } - - for _, p := range fileData.DMXPresets { - data.DMXPresets[p.ID] = p - } - - for _, sc := range fileData.DMXScenes { - data.DMXScenes[sc.ID] = sc - } - - for _, a := range fileData.DMXAnimations { - data.DMXAnimations[a.ID] = a - } - - for _, t := range fileData.DMXTransitions { - data.DMXTransitions[t.ID] = t - } -} From e7ffc36924a89a083be37e11bcd2046b5dad1a92 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 14 Mar 2019 13:16:12 +0100 Subject: [PATCH 27/94] Refactor process management and API handling --- cmd/init.go | 2 +- cmd/playback.go | 2 +- cmd/server.go | 16 ++- .../dmx_animation_controller.go | 35 ++--- .../dmx_animation_controller_test.go | 43 +++--- .../dmx_color_variable_controller.go | 35 ++--- .../dmx_color_variable_controller_test.go | 43 +++--- .../{ => datastore}/dmx_device_controller.go | 37 +++--- .../dmx_device_controller_test.go | 43 +++--- .../dmx_device_group_controller.go | 35 ++--- .../dmx_device_group_controller_test.go | 43 +++--- .../dmx_device_type_controller.go | 37 +++--- .../dmx_device_type_controller_test.go | 43 +++--- .../dmx_playground_controller.go | 11 +- .../{ => datastore}/dmx_preset_controller.go | 35 ++--- .../dmx_preset_controller_test.go | 41 +++--- .../{ => datastore}/dmx_scene_controller.go | 35 ++--- .../dmx_scene_controller_test.go | 43 +++--- .../dmx_transition_controller.go | 35 ++--- .../dmx_transition_controller_test.go | 43 +++--- .../{ => datastore}/set_list_controller.go | 35 ++--- .../set_list_controller_test.go | 43 +++--- pkg/api/{ => datastore}/song_controller.go | 35 ++--- .../{ => datastore}/song_controller_test.go | 43 +++--- pkg/api/{ => datastore}/types_test.go | 7 +- pkg/api/playback/playback.go | 66 ++++++++++ pkg/api/{ => server}/server.go | 48 ++++--- pkg/api/types.go | 20 ++- pkg/artnet/controller.go | 4 - pkg/cntl/{loader.go => data_store.go} | 0 pkg/cntl/playback/const.go | 6 + pkg/cntl/playback/player.go | 27 +++- pkg/cntl/playback/process.go | 122 ++++++++++++++++++ pkg/cntl/playback/types.go | 46 +++++++ pkg/cntl/transport/midi.go | 9 +- pkg/cntl/transport/stream.go | 7 +- pkg/cntl/transport/visualizer.go | 7 +- pkg/cntl/types.go | 1 + pkg/cntl/waiter/audio.go | 20 ++- pkg/cntl/waiter/none.go | 6 +- pkg/internal/logging/logger.go | 16 +-- pkg/process/buffered_logger.go | 82 ++++++++++++ pkg/process/errors.go | 10 ++ pkg/process/manager.go | 116 +++++++++++++++++ pkg/process/time.go | 34 +++++ pkg/process/time_test.go | 54 ++++++++ pkg/process/types.go | 46 +++++++ 47 files changed, 1108 insertions(+), 459 deletions(-) rename pkg/api/{ => datastore}/dmx_animation_controller.go (62%) rename pkg/api/{ => datastore}/dmx_animation_controller_test.go (77%) rename pkg/api/{ => datastore}/dmx_color_variable_controller.go (62%) rename pkg/api/{ => datastore}/dmx_color_variable_controller_test.go (77%) rename pkg/api/{ => datastore}/dmx_device_controller.go (67%) rename pkg/api/{ => datastore}/dmx_device_controller_test.go (79%) rename pkg/api/{ => datastore}/dmx_device_group_controller.go (62%) rename pkg/api/{ => datastore}/dmx_device_group_controller_test.go (77%) rename pkg/api/{ => datastore}/dmx_device_type_controller.go (65%) rename pkg/api/{ => datastore}/dmx_device_type_controller_test.go (77%) rename pkg/api/{ => datastore}/dmx_playground_controller.go (64%) rename pkg/api/{ => datastore}/dmx_preset_controller.go (63%) rename pkg/api/{ => datastore}/dmx_preset_controller_test.go (79%) rename pkg/api/{ => datastore}/dmx_scene_controller.go (63%) rename pkg/api/{ => datastore}/dmx_scene_controller_test.go (78%) rename pkg/api/{ => datastore}/dmx_transition_controller.go (62%) rename pkg/api/{ => datastore}/dmx_transition_controller_test.go (77%) rename pkg/api/{ => datastore}/set_list_controller.go (63%) rename pkg/api/{ => datastore}/set_list_controller_test.go (78%) rename pkg/api/{ => datastore}/song_controller.go (63%) rename pkg/api/{ => datastore}/song_controller_test.go (78%) rename pkg/api/{ => datastore}/types_test.go (75%) create mode 100644 pkg/api/playback/playback.go rename pkg/api/{ => server}/server.go (52%) rename pkg/cntl/{loader.go => data_store.go} (100%) create mode 100644 pkg/cntl/playback/process.go create mode 100644 pkg/process/buffered_logger.go create mode 100644 pkg/process/errors.go create mode 100644 pkg/process/manager.go create mode 100644 pkg/process/time.go create mode 100644 pkg/process/time_test.go create mode 100644 pkg/process/types.go diff --git a/cmd/init.go b/cmd/init.go index 56258de..d3bad40 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -37,7 +37,7 @@ func createStorage(logger *logrus.Entry, storagePath string) *disk.Storage { } func createController(logger *logrus.Entry) artnet.Controller { - c, err := artnet.NewController(logger) + c, err := artnet.NewController(logger.WithField("module", "controller")) if err != nil { logger.Fatal(err) } diff --git a/cmd/playback.go b/cmd/playback.go index 7871cb9..6bca9f6 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -45,7 +45,7 @@ var ( // playbackCmd represents the playback command var playbackCmd = &cobra.Command{ Use: "playback [song|setlist] song-valid-uuid-1", - Short: "Plays a given Song or SetList by id", + Short: "Plays a given Process or SetList by id", Long: ``, Run: func(cmd *cobra.Command, args []string) { logrus.SetLevel(logrus.WarnLevel) diff --git a/cmd/server.go b/cmd/server.go index c4d85ca..29a8cd1 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -3,7 +3,10 @@ package cmd import ( "fmt" - "github.com/StageAutoControl/controller/pkg/api" + "github.com/StageAutoControl/controller/pkg/api/server" + "github.com/StageAutoControl/controller/pkg/cntl/playback" + "github.com/StageAutoControl/controller/pkg/disk" + "github.com/StageAutoControl/controller/pkg/process" "github.com/apinnecke/go-exitcontext" "github.com/spf13/cobra" ) @@ -13,8 +16,9 @@ var serverCmd = &cobra.Command{ Use: "server", Short: "Opens the RPC API to manage the data and control the processes", Run: func(cmd *cobra.Command, args []string) { - logger := logger.WithField("module", "server") - server, err := api.NewServer(logger, storage, controller) + ctx := exitcontext.New() + pm := process.NewManager(ctx, logger) + server, err := server.New(logger.WithField("module", "api"), storage, controller, pm) if err != nil { logger.Fatal(err) } @@ -25,9 +29,11 @@ var serverCmd = &cobra.Command{ } endpoint := fmt.Sprintf("0.0.0.0:%d", port) - ctx := exitcontext.New() - logger.Infof("listening on %s", endpoint) + loader := disk.NewLoader(storage) + if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller), true); err != nil { + logger.Fatal(err) + } if err := server.Run(ctx, endpoint); err != nil { logger.Fatal(err) diff --git a/pkg/api/dmx_animation_controller.go b/pkg/api/datastore/dmx_animation_controller.go similarity index 62% rename from pkg/api/dmx_animation_controller.go rename to pkg/api/datastore/dmx_animation_controller.go index 4de6003..7af008b 100644 --- a/pkg/api/dmx_animation_controller.go +++ b/pkg/api/datastore/dmx_animation_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxAnimationController struct { +// DMXAnimationController controls the DMXAnimation entity +type DMXAnimationController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXAnimationController(logger *logrus.Entry, storage storage) *dmxAnimationController { - return &dmxAnimationController{ +// NewDMXAnimationController returns a new DMXAnimationController instance +func NewDMXAnimationController(logger *logrus.Entry, storage api.Storage) *DMXAnimationController { + return &DMXAnimationController{ logger: logger, storage: storage, } } // Create a new DMXAnimation -func (c *dmxAnimationController) Create(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { +func (c *DMXAnimationController) Create(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *dmxAnimationController) Create(r *http.Request, entity *cntl.DMXAnimati } // Update a new DMXAnimation -func (c *dmxAnimationController) Update(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { +func (c *DMXAnimationController) Update(r *http.Request, entity *cntl.DMXAnimation, reply *cntl.DMXAnimation) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *dmxAnimationController) Update(r *http.Request, entity *cntl.DMXAnimati } // Get a DMXAnimation -func (c *dmxAnimationController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXAnimation) error { +func (c *DMXAnimationController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXAnimation) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXAnimation{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *dmxAnimationController) Get(r *http.Request, idReq *IDRequest, reply *c } // GetAll returns all entities of DMXAnimation -func (c *dmxAnimationController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXAnimation) error { +func (c *DMXAnimationController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXAnimation) error { for _, id := range c.storage.List(&cntl.DMXAnimation{}) { entity := &cntl.DMXAnimation{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *dmxAnimationController) GetAll(r *http.Request, idReq *Empty, reply *[] } // Delete a DMXAnimation -func (c *dmxAnimationController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXAnimationController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXAnimation{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXAnimation{}); err != nil { diff --git a/pkg/api/dmx_animation_controller_test.go b/pkg/api/datastore/dmx_animation_controller_test.go similarity index 77% rename from pkg/api/dmx_animation_controller_test.go rename to pkg/api/datastore/dmx_animation_controller_test.go index 5777315..b3a228a 100644 --- a/pkg/api/dmx_animation_controller_test.go +++ b/pkg/api/datastore/dmx_animation_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXAnimationController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" entity := ds.DMXAnimations[key] @@ -26,7 +27,7 @@ func TestDMXAnimationController_Create_WithID(t *testing.T) { func TestDMXAnimationController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" entity := ds.DMXAnimations[key] @@ -49,20 +50,20 @@ func TestDMXAnimationController_Create_WithoutID(t *testing.T) { func TestDMXAnimationController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" reply := &cntl.DMXAnimation{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXAnimationController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" entity := ds.DMXAnimations[key] @@ -76,7 +77,7 @@ func TestDMXAnimationController_Get_Existing(t *testing.T) { } reply := &cntl.DMXAnimation{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXAnimationController_Get_Existing(t *testing.T) { func TestDMXAnimationController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" entity := ds.DMXAnimations[key] reply := &cntl.DMXAnimation{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXAnimationController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" entity := ds.DMXAnimations[key] @@ -126,19 +127,19 @@ func TestDMXAnimationController_Update_Existing(t *testing.T) { } func TestDMXAnimationController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXAnimationController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXAnimationController(logger, store) + controller := NewDMXAnimationController(logger, store) key := "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" entity := ds.DMXAnimations[key] @@ -151,8 +152,8 @@ func TestDMXAnimationController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_color_variable_controller.go b/pkg/api/datastore/dmx_color_variable_controller.go similarity index 62% rename from pkg/api/dmx_color_variable_controller.go rename to pkg/api/datastore/dmx_color_variable_controller.go index 3cc03e0..e5e3439 100644 --- a/pkg/api/dmx_color_variable_controller.go +++ b/pkg/api/datastore/dmx_color_variable_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxColorVariableController struct { +// DMXColorVariableController controls the DMXColorVariable entity +type DMXColorVariableController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXColorVariableController(logger *logrus.Entry, storage storage) *dmxColorVariableController { - return &dmxColorVariableController{ +// NewDMXColorVariableController returns a new MXColorVariableController instance +func NewDMXColorVariableController(logger *logrus.Entry, storage api.Storage) *DMXColorVariableController { + return &DMXColorVariableController{ logger: logger, storage: storage, } } // Create a new DMXColorVariable -func (c *dmxColorVariableController) Create(r *http.Request, entity *cntl.DMXColorVariable, reply *cntl.DMXColorVariable) error { +func (c *DMXColorVariableController) Create(r *http.Request, entity *cntl.DMXColorVariable, reply *cntl.DMXColorVariable) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *dmxColorVariableController) Create(r *http.Request, entity *cntl.DMXCol } // Update a new DMXColorVariable -func (c *dmxColorVariableController) Update(r *http.Request, entity *cntl.DMXColorVariable, reply *cntl.DMXColorVariable) error { +func (c *DMXColorVariableController) Update(r *http.Request, entity *cntl.DMXColorVariable, reply *cntl.DMXColorVariable) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,14 +56,14 @@ func (c *dmxColorVariableController) Update(r *http.Request, entity *cntl.DMXCol } // Get a DMXColorVariable -func (c *dmxColorVariableController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXColorVariable) error { +func (c *DMXColorVariableController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXColorVariable) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } fmt.Println(idReq.ID) if !c.storage.Has(idReq.ID, &cntl.DMXColorVariable{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -71,7 +74,7 @@ func (c *dmxColorVariableController) Get(r *http.Request, idReq *IDRequest, repl } // GetAll returns all entities of DMXColorVariable -func (c *dmxColorVariableController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXColorVariable) error { +func (c *DMXColorVariableController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXColorVariable) error { for _, id := range c.storage.List(&cntl.DMXColorVariable{}) { entity := &cntl.DMXColorVariable{} if err := c.storage.Read(id, entity); err != nil { @@ -84,13 +87,13 @@ func (c *dmxColorVariableController) GetAll(r *http.Request, idReq *Empty, reply } // Delete a DMXColorVariable -func (c *dmxColorVariableController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXColorVariableController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXColorVariable{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXColorVariable{}); err != nil { diff --git a/pkg/api/dmx_color_variable_controller_test.go b/pkg/api/datastore/dmx_color_variable_controller_test.go similarity index 77% rename from pkg/api/dmx_color_variable_controller_test.go rename to pkg/api/datastore/dmx_color_variable_controller_test.go index b55f10f..6282b4d 100644 --- a/pkg/api/dmx_color_variable_controller_test.go +++ b/pkg/api/datastore/dmx_color_variable_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXColorVariableController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" entity := ds.DMXColorVariables[key] @@ -26,7 +27,7 @@ func TestDMXColorVariableController_Create_WithID(t *testing.T) { func TestDMXColorVariableController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" entity := ds.DMXColorVariables[key] @@ -49,20 +50,20 @@ func TestDMXColorVariableController_Create_WithoutID(t *testing.T) { func TestDMXColorVariableController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" reply := &cntl.DMXColorVariable{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXColorVariableController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" entity := ds.DMXColorVariables[key] @@ -76,7 +77,7 @@ func TestDMXColorVariableController_Get_Existing(t *testing.T) { } reply := &cntl.DMXColorVariable{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXColorVariableController_Get_Existing(t *testing.T) { func TestDMXColorVariableController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" entity := ds.DMXColorVariables[key] reply := &cntl.DMXColorVariable{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXColorVariableController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" entity := ds.DMXColorVariables[key] @@ -126,19 +127,19 @@ func TestDMXColorVariableController_Update_Existing(t *testing.T) { } func TestDMXColorVariableController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXColorVariableController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXColorVariableController(logger, store) + controller := NewDMXColorVariableController(logger, store) key := "4b848ea8-5094-4509-a067-09a0e568220d" entity := ds.DMXColorVariables[key] @@ -151,8 +152,8 @@ func TestDMXColorVariableController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_device_controller.go b/pkg/api/datastore/dmx_device_controller.go similarity index 67% rename from pkg/api/dmx_device_controller.go rename to pkg/api/datastore/dmx_device_controller.go index c60bd86..0e9909e 100644 --- a/pkg/api/dmx_device_controller.go +++ b/pkg/api/datastore/dmx_device_controller.go @@ -1,28 +1,31 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxDeviceController struct { +// DMXDeviceController controls the DMXDevice entity +type DMXDeviceController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXDeviceController(logger *logrus.Entry, storage storage) *dmxDeviceController { - return &dmxDeviceController{ +// NewDMXDeviceController returns a new DMXDeviceController instance +func NewDMXDeviceController(logger *logrus.Entry, storage api.Storage) *DMXDeviceController { + return &DMXDeviceController{ logger: logger, storage: storage, } } -func (c *dmxDeviceController) validate(entity *cntl.DMXDevice) error { +func (c *DMXDeviceController) validate(entity *cntl.DMXDevice) error { if entity.Tags == nil { entity.Tags = make([]cntl.Tag, 0) } @@ -35,13 +38,13 @@ func (c *dmxDeviceController) validate(entity *cntl.DMXDevice) error { } // Create a new DMXDevice -func (c *dmxDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { +func (c *DMXDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.validate(entity); err != nil { @@ -56,9 +59,9 @@ func (c *dmxDeviceController) Create(r *http.Request, entity *cntl.DMXDevice, re } // Update a new DMXDevice -func (c *dmxDeviceController) Update(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { +func (c *DMXDeviceController) Update(r *http.Request, entity *cntl.DMXDevice, reply *cntl.DMXDevice) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.validate(entity); err != nil { @@ -73,14 +76,14 @@ func (c *dmxDeviceController) Update(r *http.Request, entity *cntl.DMXDevice, re } // Get a DMXDevice -func (c *dmxDeviceController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXDevice) error { +func (c *DMXDeviceController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXDevice) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } fmt.Println(idReq.ID) if !c.storage.Has(idReq.ID, &cntl.DMXDevice{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -91,7 +94,7 @@ func (c *dmxDeviceController) Get(r *http.Request, idReq *IDRequest, reply *cntl } // GetAll returns all entities of DMXDevice -func (c *dmxDeviceController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXDevice) error { +func (c *DMXDeviceController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXDevice) error { for _, id := range c.storage.List(&cntl.DMXDevice{}) { entity := &cntl.DMXDevice{} if err := c.storage.Read(id, entity); err != nil { @@ -104,13 +107,13 @@ func (c *dmxDeviceController) GetAll(r *http.Request, idReq *Empty, reply *[]*cn } // Delete a DMXDevice -func (c *dmxDeviceController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXDeviceController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXDevice{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXDevice{}); err != nil { diff --git a/pkg/api/dmx_device_controller_test.go b/pkg/api/datastore/dmx_device_controller_test.go similarity index 79% rename from pkg/api/dmx_device_controller_test.go rename to pkg/api/datastore/dmx_device_controller_test.go index 76ccd50..b258460 100644 --- a/pkg/api/dmx_device_controller_test.go +++ b/pkg/api/datastore/dmx_device_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -18,7 +19,7 @@ func createDeviceType(t *testing.T) { func TestDMXDeviceController_Create_WithID(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" entity := ds.DMXDevices[key] @@ -35,7 +36,7 @@ func TestDMXDeviceController_Create_WithID(t *testing.T) { func TestDMXDeviceController_Create_WithoutID(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" entity := ds.DMXDevices[key] @@ -59,21 +60,21 @@ func TestDMXDeviceController_Create_WithoutID(t *testing.T) { func TestDMXDeviceController_Get_NotExisting(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" reply := &cntl.DMXDevice{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceController_Get_Existing(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" entity := ds.DMXDevices[key] @@ -87,7 +88,7 @@ func TestDMXDeviceController_Get_Existing(t *testing.T) { } reply := &cntl.DMXDevice{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -101,21 +102,21 @@ func TestDMXDeviceController_Get_Existing(t *testing.T) { func TestDMXDeviceController_Update_NotExisting(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" entity := ds.DMXDevices[key] reply := &cntl.DMXDevice{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceController_Update_Existing(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" entity := ds.DMXDevices[key] @@ -140,20 +141,20 @@ func TestDMXDeviceController_Update_Existing(t *testing.T) { func TestDMXDeviceController_Delete_NotExisting(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceController_Delete_Existing(t *testing.T) { createDeviceType(t) defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceController(logger, store) + controller := NewDMXDeviceController(logger, store) key := "35cae00a-0b17-11e7-8bca-bbf30c56f20e" entity := ds.DMXDevices[key] @@ -166,8 +167,8 @@ func TestDMXDeviceController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_device_group_controller.go b/pkg/api/datastore/dmx_device_group_controller.go similarity index 62% rename from pkg/api/dmx_device_group_controller.go rename to pkg/api/datastore/dmx_device_group_controller.go index 92b945f..a9a8e5e 100644 --- a/pkg/api/dmx_device_group_controller.go +++ b/pkg/api/datastore/dmx_device_group_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxDeviceGroupController struct { +// DMXDeviceGroupController controls the DMXDeviceGroup entity +type DMXDeviceGroupController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXDeviceGroupController(logger *logrus.Entry, storage storage) *dmxDeviceGroupController { - return &dmxDeviceGroupController{ +// NewDMXDeviceGroupController returns a new DMXDeviceGroupController instance +func NewDMXDeviceGroupController(logger *logrus.Entry, storage api.Storage) *DMXDeviceGroupController { + return &DMXDeviceGroupController{ logger: logger, storage: storage, } } // Create a new DMXDeviceGroup -func (c *dmxDeviceGroupController) Create(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { +func (c *DMXDeviceGroupController) Create(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *dmxDeviceGroupController) Create(r *http.Request, entity *cntl.DMXDevic } // Update a new DMXDeviceGroup -func (c *dmxDeviceGroupController) Update(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { +func (c *DMXDeviceGroupController) Update(r *http.Request, entity *cntl.DMXDeviceGroup, reply *cntl.DMXDeviceGroup) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *dmxDeviceGroupController) Update(r *http.Request, entity *cntl.DMXDevic } // Get a DMXDeviceGroup -func (c *dmxDeviceGroupController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXDeviceGroup) error { +func (c *DMXDeviceGroupController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXDeviceGroup) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXDeviceGroup{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *dmxDeviceGroupController) Get(r *http.Request, idReq *IDRequest, reply } // GetAll returns all entities of DMXDeviceGroup -func (c *dmxDeviceGroupController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXDeviceGroup) error { +func (c *DMXDeviceGroupController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXDeviceGroup) error { for _, id := range c.storage.List(&cntl.DMXDeviceGroup{}) { entity := &cntl.DMXDeviceGroup{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *dmxDeviceGroupController) GetAll(r *http.Request, idReq *Empty, reply * } // Delete a DMXDeviceGroup -func (c *dmxDeviceGroupController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXDeviceGroupController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXDeviceGroup{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXDeviceGroup{}); err != nil { diff --git a/pkg/api/dmx_device_group_controller_test.go b/pkg/api/datastore/dmx_device_group_controller_test.go similarity index 77% rename from pkg/api/dmx_device_group_controller_test.go rename to pkg/api/datastore/dmx_device_group_controller_test.go index 368c7ce..86ebe66 100644 --- a/pkg/api/dmx_device_group_controller_test.go +++ b/pkg/api/datastore/dmx_device_group_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXDeviceGroupController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" entity := ds.DMXDeviceGroups[key] @@ -26,7 +27,7 @@ func TestDMXDeviceGroupController_Create_WithID(t *testing.T) { func TestDMXDeviceGroupController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" entity := ds.DMXDeviceGroups[key] @@ -49,20 +50,20 @@ func TestDMXDeviceGroupController_Create_WithoutID(t *testing.T) { func TestDMXDeviceGroupController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" reply := &cntl.DMXDeviceGroup{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceGroupController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" entity := ds.DMXDeviceGroups[key] @@ -76,7 +77,7 @@ func TestDMXDeviceGroupController_Get_Existing(t *testing.T) { } reply := &cntl.DMXDeviceGroup{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXDeviceGroupController_Get_Existing(t *testing.T) { func TestDMXDeviceGroupController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" entity := ds.DMXDeviceGroups[key] reply := &cntl.DMXDeviceGroup{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceGroupController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" entity := ds.DMXDeviceGroups[key] @@ -126,19 +127,19 @@ func TestDMXDeviceGroupController_Update_Existing(t *testing.T) { } func TestDMXDeviceGroupController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceGroupController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceGroupController(logger, store) + controller := NewDMXDeviceGroupController(logger, store) key := "475b71a0-0b16-11e7-9406-e3f678e8b788" entity := ds.DMXDeviceGroups[key] @@ -151,8 +152,8 @@ func TestDMXDeviceGroupController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_device_type_controller.go b/pkg/api/datastore/dmx_device_type_controller.go similarity index 65% rename from pkg/api/dmx_device_type_controller.go rename to pkg/api/datastore/dmx_device_type_controller.go index 3b68abf..23ce824 100644 --- a/pkg/api/dmx_device_type_controller.go +++ b/pkg/api/datastore/dmx_device_type_controller.go @@ -1,28 +1,31 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxDeviceTypeController struct { +// DMXDeviceTypeController controls the DMXDeviceType entity +type DMXDeviceTypeController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXDeviceTypeController(logger *logrus.Entry, storage storage) *dmxDeviceTypeController { - return &dmxDeviceTypeController{ +// NewDMXDeviceTypeController returns a new DMXDeviceTypeController instance +func NewDMXDeviceTypeController(logger *logrus.Entry, storage api.Storage) *DMXDeviceTypeController { + return &DMXDeviceTypeController{ logger: logger, storage: storage, } } -func (c *dmxDeviceTypeController) validate(entity *cntl.DMXDeviceType) error { +func (c *DMXDeviceTypeController) validate(entity *cntl.DMXDeviceType) error { if entity.LEDs == nil { entity.LEDs = make([]cntl.LED, 0) } @@ -31,13 +34,13 @@ func (c *dmxDeviceTypeController) validate(entity *cntl.DMXDeviceType) error { } // Create a new DMXDeviceType -func (c *dmxDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { +func (c *DMXDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.validate(entity); err != nil { @@ -52,9 +55,9 @@ func (c *dmxDeviceTypeController) Create(r *http.Request, entity *cntl.DMXDevice } // Update a new DMXDeviceType -func (c *dmxDeviceTypeController) Update(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { +func (c *DMXDeviceTypeController) Update(r *http.Request, entity *cntl.DMXDeviceType, reply *cntl.DMXDeviceType) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.validate(entity); err != nil { @@ -69,13 +72,13 @@ func (c *dmxDeviceTypeController) Update(r *http.Request, entity *cntl.DMXDevice } // Get a DMXDeviceType -func (c *dmxDeviceTypeController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXDeviceType) error { +func (c *DMXDeviceTypeController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXDeviceType) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXDeviceType{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -86,7 +89,7 @@ func (c *dmxDeviceTypeController) Get(r *http.Request, idReq *IDRequest, reply * } // GetAll returns all entities of DMXDeviceType -func (c *dmxDeviceTypeController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXDeviceType) error { +func (c *DMXDeviceTypeController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXDeviceType) error { for _, id := range c.storage.List(&cntl.DMXDeviceType{}) { entity := &cntl.DMXDeviceType{} if err := c.storage.Read(id, entity); err != nil { @@ -99,13 +102,13 @@ func (c *dmxDeviceTypeController) GetAll(r *http.Request, idReq *Empty, reply *[ } // Delete a DMXDeviceType -func (c *dmxDeviceTypeController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXDeviceTypeController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXDeviceType{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXDeviceType{}); err != nil { diff --git a/pkg/api/dmx_device_type_controller_test.go b/pkg/api/datastore/dmx_device_type_controller_test.go similarity index 77% rename from pkg/api/dmx_device_type_controller_test.go rename to pkg/api/datastore/dmx_device_type_controller_test.go index 3764d1b..1ee2bac 100644 --- a/pkg/api/dmx_device_type_controller_test.go +++ b/pkg/api/datastore/dmx_device_type_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXDeviceTypeController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" entity := ds.DMXDeviceTypes[key] @@ -26,7 +27,7 @@ func TestDMXDeviceTypeController_Create_WithID(t *testing.T) { func TestDMXDeviceTypeController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" entity := ds.DMXDeviceTypes[key] @@ -49,20 +50,20 @@ func TestDMXDeviceTypeController_Create_WithoutID(t *testing.T) { func TestDMXDeviceTypeController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" reply := &cntl.DMXDeviceType{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceTypeController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" entity := ds.DMXDeviceTypes[key] @@ -76,7 +77,7 @@ func TestDMXDeviceTypeController_Get_Existing(t *testing.T) { } reply := &cntl.DMXDeviceType{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXDeviceTypeController_Get_Existing(t *testing.T) { func TestDMXDeviceTypeController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" entity := ds.DMXDeviceTypes[key] reply := &cntl.DMXDeviceType{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceTypeController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" entity := ds.DMXDeviceTypes[key] @@ -126,19 +127,19 @@ func TestDMXDeviceTypeController_Update_Existing(t *testing.T) { } func TestDMXDeviceTypeController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXDeviceTypeController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXDeviceTypeController(logger, store) + controller := NewDMXDeviceTypeController(logger, store) key := "628fc3ea-1188-11e7-8824-5f72d80c17b6" entity := ds.DMXDeviceTypes[key] @@ -151,8 +152,8 @@ func TestDMXDeviceTypeController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_playground_controller.go b/pkg/api/datastore/dmx_playground_controller.go similarity index 64% rename from pkg/api/dmx_playground_controller.go rename to pkg/api/datastore/dmx_playground_controller.go index 6db2eca..f99d554 100644 --- a/pkg/api/dmx_playground_controller.go +++ b/pkg/api/datastore/dmx_playground_controller.go @@ -1,18 +1,21 @@ -package api +package datastore import ( "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/internal/logging" ) +// DMXPlaygroundController to play around and test DMX settings type DMXPlaygroundController struct { logger logging.Logger controller artnet.Controller } -func newDMXPlaygroundController(logger logging.Logger, controller artnet.Controller) *DMXPlaygroundController { +// NewDMXPlaygroundController returns a new DMXPlaygroundController instance +func NewDMXPlaygroundController(logger logging.Logger, controller artnet.Controller) *DMXPlaygroundController { return &DMXPlaygroundController{ logger: logger, controller: controller, @@ -20,13 +23,13 @@ func newDMXPlaygroundController(logger logging.Logger, controller artnet.Control } // SetChannelValue sets a single artnet/dmx value -func (c *DMXPlaygroundController) SetChannelValue(r *http.Request, value *artnet.ChannelValue, request Empty) error { +func (c *DMXPlaygroundController) SetChannelValue(r *http.Request, value *artnet.ChannelValue, response *api.Empty) error { c.controller.SetDMXChannelValue(*value) return nil } // SetChannelValues sets multiple artnet/dmx values -func (c *DMXPlaygroundController) SetChannelValues(r *http.Request, values *[]artnet.ChannelValue, request Empty) error { +func (c *DMXPlaygroundController) SetChannelValues(r *http.Request, values *[]artnet.ChannelValue, response *api.Empty) error { c.controller.SetDMXChannelValues(*values) return nil } diff --git a/pkg/api/dmx_preset_controller.go b/pkg/api/datastore/dmx_preset_controller.go similarity index 63% rename from pkg/api/dmx_preset_controller.go rename to pkg/api/datastore/dmx_preset_controller.go index a41dda8..531bc99 100644 --- a/pkg/api/dmx_preset_controller.go +++ b/pkg/api/datastore/dmx_preset_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxPresetController struct { +// DMXPresetController controls the DMXPreset entity +type DMXPresetController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXPresetController(logger *logrus.Entry, storage storage) *dmxPresetController { - return &dmxPresetController{ +// NewDMXPresetController returns a new DMXPresetController instance +func NewDMXPresetController(logger *logrus.Entry, storage api.Storage) *DMXPresetController { + return &DMXPresetController{ logger: logger, storage: storage, } } // Create a new DMXPreset -func (c *dmxPresetController) Create(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { +func (c *DMXPresetController) Create(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *dmxPresetController) Create(r *http.Request, entity *cntl.DMXPreset, re } // Update a new DMXPreset -func (c *dmxPresetController) Update(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { +func (c *DMXPresetController) Update(r *http.Request, entity *cntl.DMXPreset, reply *cntl.DMXPreset) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *dmxPresetController) Update(r *http.Request, entity *cntl.DMXPreset, re } // Get a DMXPreset -func (c *dmxPresetController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXPreset) error { +func (c *DMXPresetController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXPreset) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXPreset{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *dmxPresetController) Get(r *http.Request, idReq *IDRequest, reply *cntl } // GetAll returns all entities of DMXPreset -func (c *dmxPresetController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXPreset) error { +func (c *DMXPresetController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXPreset) error { for _, id := range c.storage.List(&cntl.DMXPreset{}) { entity := &cntl.DMXPreset{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *dmxPresetController) GetAll(r *http.Request, idReq *Empty, reply *[]*cn } // Delete a DMXPreset -func (c *dmxPresetController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXPresetController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXPreset{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXPreset{}); err != nil { diff --git a/pkg/api/dmx_preset_controller_test.go b/pkg/api/datastore/dmx_preset_controller_test.go similarity index 79% rename from pkg/api/dmx_preset_controller_test.go rename to pkg/api/datastore/dmx_preset_controller_test.go index 45bf7e8..39075af 100644 --- a/pkg/api/dmx_preset_controller_test.go +++ b/pkg/api/datastore/dmx_preset_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXPresetController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" entity := ds.DMXPresets[key] @@ -26,7 +27,7 @@ func TestDMXPresetController_Create_WithID(t *testing.T) { func TestDMXPresetController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" entity := ds.DMXPresets[key] @@ -49,20 +50,20 @@ func TestDMXPresetController_Create_WithoutID(t *testing.T) { func TestDMXPresetController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" reply := &cntl.DMXPreset{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXPresetController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" entity := ds.DMXPresets[key] @@ -76,7 +77,7 @@ func TestDMXPresetController_Get_Existing(t *testing.T) { } reply := &cntl.DMXPreset{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXPresetController_Get_Existing(t *testing.T) { func TestDMXPresetController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" entity := ds.DMXPresets[key] reply := &cntl.DMXPreset{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXPresetController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" entity := ds.DMXPresets[key] @@ -126,19 +127,19 @@ func TestDMXPresetController_Update_Existing(t *testing.T) { } func TestDMXPresetController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { t.Errorf("expected to get errNotExists, but got %v", err) } } func TestDMXPresetController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXPresetController(logger, store) + controller := NewDMXPresetController(logger, store) key := "0de258e0-0e7b-11e7-afd4-ebf6036983dc" entity := ds.DMXPresets[key] @@ -151,8 +152,8 @@ func TestDMXPresetController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_scene_controller.go b/pkg/api/datastore/dmx_scene_controller.go similarity index 63% rename from pkg/api/dmx_scene_controller.go rename to pkg/api/datastore/dmx_scene_controller.go index d42e4ff..8fa7aae 100644 --- a/pkg/api/dmx_scene_controller.go +++ b/pkg/api/datastore/dmx_scene_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxSceneController struct { +// DMXSceneController controls the DMXScene entity +type DMXSceneController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXSceneController(logger *logrus.Entry, storage storage) *dmxSceneController { - return &dmxSceneController{ +// NewDMXSceneController returns a new DMXSceneController instance +func NewDMXSceneController(logger *logrus.Entry, storage api.Storage) *DMXSceneController { + return &DMXSceneController{ logger: logger, storage: storage, } } // Create a new DMXScene -func (c *dmxSceneController) Create(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { +func (c *DMXSceneController) Create(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *dmxSceneController) Create(r *http.Request, entity *cntl.DMXScene, repl } // Update a new DMXScene -func (c *dmxSceneController) Update(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { +func (c *DMXSceneController) Update(r *http.Request, entity *cntl.DMXScene, reply *cntl.DMXScene) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *dmxSceneController) Update(r *http.Request, entity *cntl.DMXScene, repl } // Get a DMXScene -func (c *dmxSceneController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXScene) error { +func (c *DMXSceneController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXScene) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXScene{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *dmxSceneController) Get(r *http.Request, idReq *IDRequest, reply *cntl. } // GetAll returns all entities of DMXScene -func (c *dmxSceneController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXScene) error { +func (c *DMXSceneController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXScene) error { for _, id := range c.storage.List(&cntl.DMXScene{}) { entity := &cntl.DMXScene{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *dmxSceneController) GetAll(r *http.Request, idReq *Empty, reply *[]*cnt } // Delete a DMXScene -func (c *dmxSceneController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXSceneController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXScene{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXScene{}); err != nil { diff --git a/pkg/api/dmx_scene_controller_test.go b/pkg/api/datastore/dmx_scene_controller_test.go similarity index 78% rename from pkg/api/dmx_scene_controller_test.go rename to pkg/api/datastore/dmx_scene_controller_test.go index 8091da3..29ee69c 100644 --- a/pkg/api/dmx_scene_controller_test.go +++ b/pkg/api/datastore/dmx_scene_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXSceneController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" entity := ds.DMXScenes[key] @@ -26,7 +27,7 @@ func TestDMXSceneController_Create_WithID(t *testing.T) { func TestDMXSceneController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" entity := ds.DMXScenes[key] @@ -49,20 +50,20 @@ func TestDMXSceneController_Create_WithoutID(t *testing.T) { func TestDMXSceneController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" reply := &cntl.DMXScene{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXSceneController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" entity := ds.DMXScenes[key] @@ -76,7 +77,7 @@ func TestDMXSceneController_Get_Existing(t *testing.T) { } reply := &cntl.DMXScene{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXSceneController_Get_Existing(t *testing.T) { func TestDMXSceneController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" entity := ds.DMXScenes[key] reply := &cntl.DMXScene{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXSceneController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" entity := ds.DMXScenes[key] @@ -126,19 +127,19 @@ func TestDMXSceneController_Update_Existing(t *testing.T) { } func TestDMXSceneController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXSceneController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXSceneController(logger, store) + controller := NewDMXSceneController(logger, store) key := "492cef2e-0b14-11e7-be89-c3fa25f9cabb" entity := ds.DMXScenes[key] @@ -151,8 +152,8 @@ func TestDMXSceneController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/dmx_transition_controller.go b/pkg/api/datastore/dmx_transition_controller.go similarity index 62% rename from pkg/api/dmx_transition_controller.go rename to pkg/api/datastore/dmx_transition_controller.go index 5ae7a0d..1b61e74 100644 --- a/pkg/api/dmx_transition_controller.go +++ b/pkg/api/datastore/dmx_transition_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type dmxTransitionController struct { +// DMXTransitionController controls the DMXTransition entity +type DMXTransitionController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newDMXTransitionController(logger *logrus.Entry, storage storage) *dmxTransitionController { - return &dmxTransitionController{ +// NewDMXTransitionController returns a new DMXTransitionController instance +func NewDMXTransitionController(logger *logrus.Entry, storage api.Storage) *DMXTransitionController { + return &DMXTransitionController{ logger: logger, storage: storage, } } // Create a new DMXTransition -func (c *dmxTransitionController) Create(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { +func (c *DMXTransitionController) Create(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *dmxTransitionController) Create(r *http.Request, entity *cntl.DMXTransi } // Update a new DMXTransition -func (c *dmxTransitionController) Update(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { +func (c *DMXTransitionController) Update(r *http.Request, entity *cntl.DMXTransition, reply *cntl.DMXTransition) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *dmxTransitionController) Update(r *http.Request, entity *cntl.DMXTransi } // Get a DMXTransition -func (c *dmxTransitionController) Get(r *http.Request, idReq *IDRequest, reply *cntl.DMXTransition) error { +func (c *DMXTransitionController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.DMXTransition) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXTransition{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *dmxTransitionController) Get(r *http.Request, idReq *IDRequest, reply * } // GetAll returns all entities of DMXTransition -func (c *dmxTransitionController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.DMXTransition) error { +func (c *DMXTransitionController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXTransition) error { for _, id := range c.storage.List(&cntl.DMXTransition{}) { entity := &cntl.DMXTransition{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *dmxTransitionController) GetAll(r *http.Request, idReq *Empty, reply *[ } // Delete a DMXTransition -func (c *dmxTransitionController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *DMXTransitionController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.DMXTransition{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.DMXTransition{}); err != nil { diff --git a/pkg/api/dmx_transition_controller_test.go b/pkg/api/datastore/dmx_transition_controller_test.go similarity index 77% rename from pkg/api/dmx_transition_controller_test.go rename to pkg/api/datastore/dmx_transition_controller_test.go index 719ec34..de2fa08 100644 --- a/pkg/api/dmx_transition_controller_test.go +++ b/pkg/api/datastore/dmx_transition_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestDMXTransitionController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" entity := ds.DMXTransitions[key] @@ -26,7 +27,7 @@ func TestDMXTransitionController_Create_WithID(t *testing.T) { func TestDMXTransitionController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" entity := ds.DMXTransitions[key] @@ -49,20 +50,20 @@ func TestDMXTransitionController_Create_WithoutID(t *testing.T) { func TestDMXTransitionController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" reply := &cntl.DMXTransition{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXTransitionController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" entity := ds.DMXTransitions[key] @@ -76,7 +77,7 @@ func TestDMXTransitionController_Get_Existing(t *testing.T) { } reply := &cntl.DMXTransition{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestDMXTransitionController_Get_Existing(t *testing.T) { func TestDMXTransitionController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" entity := ds.DMXTransitions[key] reply := &cntl.DMXTransition{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXTransitionController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" entity := ds.DMXTransitions[key] @@ -126,19 +127,19 @@ func TestDMXTransitionController_Update_Existing(t *testing.T) { } func TestDMXTransitionController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestDMXTransitionController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newDMXTransitionController(logger, store) + controller := NewDMXTransitionController(logger, store) key := "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21" entity := ds.DMXTransitions[key] @@ -151,8 +152,8 @@ func TestDMXTransitionController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/set_list_controller.go b/pkg/api/datastore/set_list_controller.go similarity index 63% rename from pkg/api/set_list_controller.go rename to pkg/api/datastore/set_list_controller.go index d99ff06..39a653b 100644 --- a/pkg/api/set_list_controller.go +++ b/pkg/api/datastore/set_list_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type setListController struct { +// SetListController controls the SetList entity +type SetListController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newSetListController(logger *logrus.Entry, storage storage) *setListController { - return &setListController{ +// NewSetListController returns a new SetListController instance +func NewSetListController(logger *logrus.Entry, storage api.Storage) *SetListController { + return &SetListController{ logger: logger, storage: storage, } } // Create a new SetList -func (c *setListController) Create(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { +func (c *SetListController) Create(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *setListController) Create(r *http.Request, entity *cntl.SetList, reply } // Update a new SetList -func (c *setListController) Update(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { +func (c *SetListController) Update(r *http.Request, entity *cntl.SetList, reply *cntl.SetList) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *setListController) Update(r *http.Request, entity *cntl.SetList, reply } // Get a SetList -func (c *setListController) Get(r *http.Request, idReq *IDRequest, reply *cntl.SetList) error { +func (c *SetListController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.SetList) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.SetList{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *setListController) Get(r *http.Request, idReq *IDRequest, reply *cntl.S } // GetAll returns all entities of SetList -func (c *setListController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.SetList) error { +func (c *SetListController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.SetList) error { for _, id := range c.storage.List(&cntl.SetList{}) { entity := &cntl.SetList{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *setListController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl } // Delete a SetList -func (c *setListController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *SetListController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.SetList{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.SetList{}); err != nil { diff --git a/pkg/api/set_list_controller_test.go b/pkg/api/datastore/set_list_controller_test.go similarity index 78% rename from pkg/api/set_list_controller_test.go rename to pkg/api/datastore/set_list_controller_test.go index f960503..e4ffdc0 100644 --- a/pkg/api/set_list_controller_test.go +++ b/pkg/api/datastore/set_list_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestSetListController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" entity := ds.SetLists[key] @@ -26,7 +27,7 @@ func TestSetListController_Create_WithID(t *testing.T) { func TestSetListController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" entity := ds.SetLists[key] @@ -49,20 +50,20 @@ func TestSetListController_Create_WithoutID(t *testing.T) { func TestSetListController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" reply := &cntl.SetList{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestSetListController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" entity := ds.SetLists[key] @@ -76,7 +77,7 @@ func TestSetListController_Get_Existing(t *testing.T) { } reply := &cntl.SetList{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestSetListController_Get_Existing(t *testing.T) { func TestSetListController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" entity := ds.SetLists[key] reply := &cntl.SetList{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestSetListController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" entity := ds.SetLists[key] @@ -126,19 +127,19 @@ func TestSetListController_Update_Existing(t *testing.T) { } func TestSetListController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestSetListController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSetListController(logger, store) + controller := NewSetListController(logger, store) key := "f5b4be8a-0b18-11e7-b837-4bac99d86956" entity := ds.SetLists[key] @@ -151,8 +152,8 @@ func TestSetListController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/song_controller.go b/pkg/api/datastore/song_controller.go similarity index 63% rename from pkg/api/song_controller.go rename to pkg/api/datastore/song_controller.go index 5f0cdd9..5df3219 100644 --- a/pkg/api/song_controller.go +++ b/pkg/api/datastore/song_controller.go @@ -1,35 +1,38 @@ -package api +package datastore import ( "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) -type songController struct { +// SongController controls the Song entity +type SongController struct { logger *logrus.Entry - storage storage + storage api.Storage } -func newSongController(logger *logrus.Entry, storage storage) *songController { - return &songController{ +// NewSongController returns a new SongController instance +func NewSongController(logger *logrus.Entry, storage api.Storage) *SongController { + return &SongController{ logger: logger, storage: storage, } } // Create a new Song -func (c *songController) Create(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { +func (c *SongController) Create(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { if entity.ID == "" { entity.ID = uuid.NewV4().String() } if c.storage.Has(entity.ID, entity) { - return errExists + return api.ErrExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -40,9 +43,9 @@ func (c *songController) Create(r *http.Request, entity *cntl.Song, reply *cntl. } // Update a new Song -func (c *songController) Update(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { +func (c *SongController) Update(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { if !c.storage.Has(entity.ID, entity) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Write(entity.ID, entity); err != nil { @@ -53,13 +56,13 @@ func (c *songController) Update(r *http.Request, entity *cntl.Song, reply *cntl. } // Get a Song -func (c *songController) Get(r *http.Request, idReq *IDRequest, reply *cntl.Song) error { +func (c *SongController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.Song) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.Song{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Read(idReq.ID, reply); err != nil { @@ -70,7 +73,7 @@ func (c *songController) Get(r *http.Request, idReq *IDRequest, reply *cntl.Song } // GetAll returns all entities of Song -func (c *songController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.Song) error { +func (c *SongController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.Song) error { for _, id := range c.storage.List(&cntl.Song{}) { entity := &cntl.Song{} if err := c.storage.Read(id, entity); err != nil { @@ -83,13 +86,13 @@ func (c *songController) GetAll(r *http.Request, idReq *Empty, reply *[]*cntl.So } // Delete a Song -func (c *songController) Delete(r *http.Request, idReq *IDRequest, reply *SuccessResponse) error { +func (c *SongController) Delete(r *http.Request, idReq *api.IDBody, reply *api.SuccessResponse) error { if idReq.ID == "" { - return errNoIDGiven + return api.ErrNoIDGiven } if !c.storage.Has(idReq.ID, &cntl.Song{}) { - return errNotExists + return api.ErrNotExists } if err := c.storage.Delete(idReq.ID, &cntl.Song{}); err != nil { diff --git a/pkg/api/song_controller_test.go b/pkg/api/datastore/song_controller_test.go similarity index 78% rename from pkg/api/song_controller_test.go rename to pkg/api/datastore/song_controller_test.go index cfdd03c..d354ebb 100644 --- a/pkg/api/song_controller_test.go +++ b/pkg/api/datastore/song_controller_test.go @@ -1,8 +1,9 @@ -package api +package datastore import ( "testing" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" internalTesting "github.com/StageAutoControl/controller/pkg/internal/testing" "github.com/jinzhu/copier" @@ -10,7 +11,7 @@ import ( func TestSongController_Create_WithID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" entity := ds.Songs[key] @@ -26,7 +27,7 @@ func TestSongController_Create_WithID(t *testing.T) { func TestSongController_Create_WithoutID(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" entity := ds.Songs[key] @@ -49,20 +50,20 @@ func TestSongController_Create_WithoutID(t *testing.T) { func TestSongController_Get_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" reply := &cntl.Song{} - idReq := &IDRequest{ID: key} - if err := controller.Get(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + idReq := &api.IDBody{ID: key} + if err := controller.Get(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestSongController_Get_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" entity := ds.Songs[key] @@ -76,7 +77,7 @@ func TestSongController_Get_Existing(t *testing.T) { } reply := &cntl.Song{} - idReq := &IDRequest{ID: key} + idReq := &api.IDBody{ID: key} t.Log("idReq has ID:", idReq.ID) if err := controller.Get(req, idReq, reply); err != nil { t.Errorf("failed to call apiController: %v", err) @@ -89,20 +90,20 @@ func TestSongController_Get_Existing(t *testing.T) { func TestSongController_Update_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" entity := ds.Songs[key] reply := &cntl.Song{} - if err := controller.Update(req, entity, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + if err := controller.Update(req, entity, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestSongController_Update_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" entity := ds.Songs[key] @@ -126,19 +127,19 @@ func TestSongController_Update_Existing(t *testing.T) { } func TestSongController_Delete_NotExisting(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} - if err := controller.Delete(req, idReq, reply); err != errNotExists { - t.Errorf("expected to get errNotExists, but got %v", err) + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} + if err := controller.Delete(req, idReq, reply); err != api.ErrNotExists { + t.Errorf("expected to get api.ErrNotExists, but got %v", err) } } func TestSongController_Delete_Existing(t *testing.T) { defer internalTesting.Cleanup(t, path) - controller := newSongController(logger, store) + controller := NewSongController(logger, store) key := "3c1065c8-0b14-11e7-96eb-5b134621c411" entity := ds.Songs[key] @@ -151,8 +152,8 @@ func TestSongController_Delete_Existing(t *testing.T) { t.Errorf("Expected createReply to have id %s, but has %s", key, createReply.ID) } - reply := &SuccessResponse{} - idReq := &IDRequest{ID: key} + reply := &api.SuccessResponse{} + idReq := &api.IDBody{ID: key} if err := controller.Delete(req, idReq, reply); err != nil { t.Errorf("expected to get no error, but got %v", err) } diff --git a/pkg/api/types_test.go b/pkg/api/datastore/types_test.go similarity index 75% rename from pkg/api/types_test.go rename to pkg/api/datastore/types_test.go index d4aadc5..197e4b2 100644 --- a/pkg/api/types_test.go +++ b/pkg/api/datastore/types_test.go @@ -1,10 +1,11 @@ -package api +package datastore import ( "io/ioutil" "net/http" "net/http/httptest" + "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/disk" "github.com/StageAutoControl/controller/pkg/internal/fixtures" "github.com/sirupsen/logrus" @@ -13,9 +14,9 @@ import ( var ( logger *logrus.Entry path string - store storage + store api.Storage ds = fixtures.DataStore() - req = httptest.NewRequest(http.MethodPost, rpcPath, nil) + req = httptest.NewRequest(http.MethodPost, api.RPCPath, nil) ) func init() { diff --git a/pkg/api/playback/playback.go b/pkg/api/playback/playback.go new file mode 100644 index 0000000..f4aafcb --- /dev/null +++ b/pkg/api/playback/playback.go @@ -0,0 +1,66 @@ +package playback + +import ( + "errors" + "fmt" + "net/http" + + "github.com/StageAutoControl/controller/pkg/api" + "github.com/StageAutoControl/controller/pkg/cntl/playback" + "github.com/StageAutoControl/controller/pkg/process" + "github.com/jinzhu/copier" +) + +var ( + errSongSetListNeedToBeDistinct = errors.New("either a SetList ID or a Song ID must be given, neither both nor none") +) + +// Controller handles management of playback processes +type Controller struct { + pm process.Manager +} + +// NewController returns a new playback controller instance +func NewController(pm process.Manager) *Controller { + return &Controller{ + pm: pm, + } +} + +// StartRequest starts the Playback of a Song or SetList +type StartRequest struct { + playback.Params +} + +// Start a playback with either a song or a setlist +func (c *Controller) Start(r *http.Request, req *StartRequest, res *process.Status) error { + if (req.Song.ID != "" && req.SetList.ID != "") || (req.Song.ID == "" && req.SetList.ID == "") { + return errSongSetListNeedToBeDistinct + } + + p, _, err := c.pm.GetProcess(playback.ProcessName) + if err != nil { + return fmt.Errorf("failed to fetch playback process: %v", err) + } + p.(*playback.Process).SetParams(req.Params) + + if s, err := c.pm.Start(playback.ProcessName); err != nil { + return fmt.Errorf("failed to start playback: %v", err) + + } else if err := copier.Copy(res, s); err != nil { + return fmt.Errorf("failed to write response body: %v", err) + } + + return nil +} + +// Stop a playback +func (c *Controller) Stop(r *http.Request, req *api.IDBody, res *process.Status) error { + _, err := c.pm.Stop(playback.ProcessName) + return err +} + +// Status returns the current status of a playback +func (c *Controller) Status(r *http.Request, req *api.IDBody, res *process.Status) error { + return nil +} diff --git a/pkg/api/server.go b/pkg/api/server/server.go similarity index 52% rename from pkg/api/server.go rename to pkg/api/server/server.go index 786c022..aca5f07 100644 --- a/pkg/api/server.go +++ b/pkg/api/server/server.go @@ -1,11 +1,15 @@ -package api +package server import ( "context" "fmt" "net/http" + "github.com/StageAutoControl/controller/pkg/api" + "github.com/StageAutoControl/controller/pkg/api/datastore" + "github.com/StageAutoControl/controller/pkg/api/playback" "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/process" "github.com/gorilla/handlers" "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" @@ -16,17 +20,20 @@ import ( type Server struct { *rpc.Server logger *logrus.Entry - storage storage + storage api.Storage apiController map[string]interface{} - controller artnet.Controller + cntl artnet.Controller + pm process.Manager } -// NewServer returns a new Server instance -func NewServer(logger *logrus.Entry, storage storage, controller artnet.Controller) (*Server, error) { +// New returns a new Server instance +func New(logger *logrus.Entry, storage api.Storage, cntl artnet.Controller, pm process.Manager) (*Server, error) { server := &Server{ Server: rpc.NewServer(), logger: logger, storage: storage, + cntl: cntl, + pm: pm, } if err := server.registerControllers(); err != nil { @@ -38,21 +45,22 @@ func NewServer(logger *logrus.Entry, storage storage, controller artnet.Controll func (s *Server) registerControllers() error { s.apiController = map[string]interface{}{ - "DMXAnimation": newDMXAnimationController(s.logger, s.storage), - "DMXDevice": newDMXDeviceController(s.logger, s.storage), - "DMXDeviceGroup": newDMXDeviceGroupController(s.logger, s.storage), - "DMXDeviceType": newDMXDeviceTypeController(s.logger, s.storage), - "DMXPreset": newDMXPresetController(s.logger, s.storage), - "DMXScene": newDMXSceneController(s.logger, s.storage), - "DMXTransition": newDMXTransitionController(s.logger, s.storage), - "DMXColorVariable": newDMXColorVariableController(s.logger, s.storage), - "Song": newSongController(s.logger, s.storage), - "SetList": newSetListController(s.logger, s.storage), - "DMXPlayground": newDMXPlaygroundController(s.logger, s.controller), + "DMXAnimation": datastore.NewDMXAnimationController(s.logger, s.storage), + "DMXDevice": datastore.NewDMXDeviceController(s.logger, s.storage), + "DMXDeviceGroup": datastore.NewDMXDeviceGroupController(s.logger, s.storage), + "DMXDeviceType": datastore.NewDMXDeviceTypeController(s.logger, s.storage), + "DMXPreset": datastore.NewDMXPresetController(s.logger, s.storage), + "DMXScene": datastore.NewDMXSceneController(s.logger, s.storage), + "DMXTransition": datastore.NewDMXTransitionController(s.logger, s.storage), + "DMXColorVariable": datastore.NewDMXColorVariableController(s.logger, s.storage), + "Song": datastore.NewSongController(s.logger, s.storage), + "SetList": datastore.NewSetListController(s.logger, s.storage), + "DMXPlayground": datastore.NewDMXPlaygroundController(s.logger, s.cntl), + "Playback": playback.NewController(s.pm), } - for name, controller := range s.apiController { - if err := s.Server.RegisterService(controller, name); err != nil { + for name, cntl := range s.apiController { + if err := s.Server.RegisterService(cntl, name); err != nil { return err } } @@ -65,7 +73,7 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { s.Server.RegisterCodec(json.NewCodec(), "application/json") r := http.NewServeMux() - r.Handle(rpcPath, s.Server) + r.Handle(api.RPCPath, s.Server) r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(rw, "OK"); err != nil { s.logger.Errorf("failed to write content to response: %v", err) @@ -93,6 +101,8 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { } }() + s.logger.Infof("listening on %s", endpoint) + err := httpServer.ListenAndServe() if err != nil && err != http.ErrServerClosed { return err diff --git a/pkg/api/types.go b/pkg/api/types.go index 0d9d389..2a202c0 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -3,13 +3,19 @@ package api import "errors" var ( - rpcPath = "/rpc" - errNoIDGiven = errors.New("no ID was given with request") - errExists = errors.New("entity with given ID already exists") - errNotExists = errors.New("entity with given ID does not exist") + // RPCPath to where the RPC server should listen on + RPCPath = "/rpc" + + // ErrNoIDGiven is returned when the request did not contain a valid ID + ErrNoIDGiven = errors.New("no ID was given with request") + // ErrExists is returned when the entity which is tried to create already exists + ErrExists = errors.New("entity with given ID already exists") + // ErrNotExists is returned when the entity tried to manage dies not exist + ErrNotExists = errors.New("entity with given ID does not exist") ) -type storage interface { +// Storage interface for abstraction in api usage +type Storage interface { Has(key string, kind interface{}) bool Write(key string, value interface{}) error Read(key string, value interface{}) error @@ -17,8 +23,8 @@ type storage interface { Delete(key string, kind interface{}) error } -// IDRequest is a request object only storing an ID -type IDRequest struct { +// IDBody is a request object only storing an ID +type IDBody struct { ID string `json:"id"` } diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index e3ba0da..4b7d65c 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "os" - "time" "github.com/StageAutoControl/controller/pkg/internal/logging" "github.com/jsimonetti/go-artnet" @@ -40,9 +39,6 @@ func NewController(logger logging.Logger) (Controller, error) { return nil, fmt.Errorf("failed to start Controller: %v", err) } - logger.Info("Waiting 5 seconds for nodes to register") - time.Sleep(5 * time.Second) - return &controller{ sender: c, state: NewState(), diff --git a/pkg/cntl/loader.go b/pkg/cntl/data_store.go similarity index 100% rename from pkg/cntl/loader.go rename to pkg/cntl/data_store.go diff --git a/pkg/cntl/playback/const.go b/pkg/cntl/playback/const.go index 4c5a966..019e5a3 100644 --- a/pkg/cntl/playback/const.go +++ b/pkg/cntl/playback/const.go @@ -6,3 +6,9 @@ import "errors" var ( ErrCancelled = errors.New("playback cancelled") ) + +const ( + // ProcessName defines the name of the playback process + ProcessName = "playback" + paramsStorageKey = "playback_process" +) diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index cb09f5b..eab384c 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -5,30 +5,35 @@ import ( "time" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/logging" "fmt" "github.com/StageAutoControl/controller/pkg/cntl/song" - "github.com/sirupsen/logrus" ) // Player plays various things from a given data store, for example songs or a whole SetList. type Player struct { - logger *logrus.Entry + logger logging.Logger dataStore *cntl.DataStore writers []TransportWriter waiters []Waiter } // NewPlayer returns a new Player instance -func NewPlayer(logger *logrus.Entry, ds *cntl.DataStore, writers []TransportWriter, waiters []Waiter) *Player { - return &Player{logger, ds, writers, waiters} +func NewPlayer(logger logging.Logger, ds *cntl.DataStore, writers []TransportWriter, waiters []Waiter) *Player { + return &Player{ + logger: logger, + dataStore: ds, + writers: writers, + waiters: waiters, + } } func (p *Player) checkSetList(setList *cntl.SetList) error { for _, songSel := range setList.Songs { if _, ok := p.dataStore.Songs[songSel.ID]; !ok { - return fmt.Errorf("cannot find Song %q", songSel.ID) + return fmt.Errorf("cannot find Process %q", songSel.ID) } } @@ -75,7 +80,11 @@ func (p *Player) wait(ctx context.Context) error { }() for _, w := range p.waiters { - go w.Wait(done, cancel, err) + go func() { + if err := w.Wait(done, cancel, err); err != nil { + p.logger.Error(err) + } + }() } select { @@ -125,7 +134,11 @@ func (p *Player) PlaySong(ctx context.Context, songID string) error { } for _, w := range p.writers { - go w.Write(cmd) + go func() { + if err := w.Write(cmd); err != nil { + p.logger.Error(err) + } + }() } i++ diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go new file mode 100644 index 0000000..a4bf0f4 --- /dev/null +++ b/pkg/cntl/playback/process.go @@ -0,0 +1,122 @@ +package playback + +import ( + "context" + "fmt" + + "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/cntl/transport" + "github.com/StageAutoControl/controller/pkg/cntl/waiter" + "github.com/StageAutoControl/controller/pkg/internal/logging" +) + +// Process handles the playback of a single song +type Process struct { + logger logging.Logger + loader loader + storage storage + params Params + controller artnet.Controller + player *Player + ctx context.Context + cancel context.CancelFunc +} + +// NewProcess returns a new playback process instance +func NewProcess(loader loader, storage storage, controller artnet.Controller) *Process { + return &Process{ + loader: loader, + storage: storage, + controller: controller, + } +} + +// SetParams tells the playback process whether to playback a song or set list and the corresponding ID +func (p *Process) SetParams(params Params) { + p.params = params +} + +// SetLogger sets the logger for the process +func (p *Process) SetLogger(logger logging.Logger) { + p.logger = logger +} + +// Start the process, i.e. start the player with all the collected information +func (p *Process) Start(ctx context.Context) error { + ds, err := p.loader.Load() + if err != nil { + return fmt.Errorf("failed to load data from disk: %v", err) + } + + config := &Config{} + if err := p.storage.Read(paramsStorageKey, config); err != nil { + return fmt.Errorf("failed to find playback config: %v", err) + } + + cfg, err := p.parseConfig(config) + p.player = NewPlayer(p.logger, ds, cfg.writers, cfg.waiters) + p.ctx, p.cancel = context.WithCancel(ctx) + + if p.params.SetList.ID != "" { + if err := p.player.PlaySetList(ctx, p.params.SetList.ID); err != nil { + return fmt.Errorf("failed to start setlist playbaack: %v", err) + } + } else if p.params.Song.ID != "" { + if err := p.player.PlaySong(ctx, p.params.Song.ID); err != nil { + return fmt.Errorf("failed to start song playback: %v", err) + } + } + + return nil +} + +func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { + cfg := &parsedConfig{ + waiters: []Waiter{}, + writers: []TransportWriter{}, + } + + writers := 0 + if config.TransportWriters.ArtNet.Enabled { + aw, err := transport.NewArtNet(p.controller) + if err != nil { + return nil, fmt.Errorf("failed to create artnet transport writer: %v", err) + } + + writers++ + cfg.writers = append(cfg.writers, aw) + } + + if config.TransportWriters.MIDI.Enabled { + writers++ + + mw, err := transport.NewMIDI(p.logger, config.TransportWriters.MIDI.OutputDeviceID) + if err != nil { + return nil, fmt.Errorf("failed to create midi transport writer: %v", err) + } + + writers++ + cfg.writers = append(cfg.writers, mw) + } + + if config.Waiters.Audio.Enabled { + audio, err := waiter.NewAudio(p.logger, config.Waiters.Audio.Threshold) + if err != nil { + return nil, fmt.Errorf("failed to create audio waiter: %v", err) + } + cfg.waiters = append(cfg.waiters, audio) + } else { + cfg.waiters = append(cfg.waiters, waiter.NewNone(p.logger)) + } + + return cfg, nil +} + +// Stop the process, i.e. cancel the playback context +func (p *Process) Stop() error { + p.cancel() + p.player = nil + p.ctx = nil + + return nil +} diff --git a/pkg/cntl/playback/types.go b/pkg/cntl/playback/types.go index 6ead4d4..1b108c9 100644 --- a/pkg/cntl/playback/types.go +++ b/pkg/cntl/playback/types.go @@ -11,3 +11,49 @@ type TransportWriter interface { type Waiter interface { Wait(done chan struct{}, cancel chan struct{}, err chan error) error } + +type storage interface { + Has(key string, kind interface{}) bool + Write(key string, value interface{}) error + Read(key string, value interface{}) error + List(kind interface{}) []string + Delete(key string, kind interface{}) error +} + +type loader interface { + Load() (*cntl.DataStore, error) +} + +// Params specifies how to run a playback +type Params struct { + Song struct { + ID string + } + SetList struct { + ID string + } +} + +type parsedConfig struct { + waiters []Waiter + writers []TransportWriter +} + +// Config stores the information on which waiters and/or transport writers are enabled and what their config is +type Config struct { + Waiters struct { + Audio struct { + Enabled bool + Threshold float32 + } + } + TransportWriters struct { + ArtNet struct { + Enabled bool + } + MIDI struct { + Enabled bool + OutputDeviceID string + } + } +} diff --git a/pkg/cntl/transport/midi.go b/pkg/cntl/transport/midi.go index 55e2c53..a6a8274 100644 --- a/pkg/cntl/transport/midi.go +++ b/pkg/cntl/transport/midi.go @@ -1,25 +1,26 @@ package transport import ( + "errors" "fmt" "strconv" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/logging" "github.com/rakyll/portmidi" - "github.com/sirupsen/logrus" ) // MIDI is a transport that sends MIDI signals using portmidi. type MIDI struct { - logger *logrus.Entry + logger logging.Logger deviceInfo *portmidi.DeviceInfo deviceID portmidi.DeviceID out *portmidi.Stream } // NewMIDI creates a new MIDI transport -func NewMIDI(logger *logrus.Entry, deviceID string) (*MIDI, error) { +func NewMIDI(logger logging.Logger, deviceID string) (*MIDI, error) { if err := portmidi.Initialize(); err != nil { return nil, err } @@ -37,7 +38,7 @@ func NewMIDI(logger *logrus.Entry, deviceID string) (*MIDI, error) { info := portmidi.Info(d) if info == nil { - logger.Fatal("Unable to read default output device") + return nil, errors.New("unable to read default output device") } out, err := portmidi.NewOutputStream(d, 10, 0) diff --git a/pkg/cntl/transport/stream.go b/pkg/cntl/transport/stream.go index c5bf177..4473d21 100644 --- a/pkg/cntl/transport/stream.go +++ b/pkg/cntl/transport/stream.go @@ -5,21 +5,20 @@ import ( "io" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/logging" "strings" - - "github.com/sirupsen/logrus" ) // Stream is an output channel that can render to a buffer type Stream struct { - logger *logrus.Entry + logger logging.Logger w io.Writer i uint64 } // NewStream returns a new Stream instance -func NewStream(logger *logrus.Entry, w io.Writer) *Stream { +func NewStream(logger logging.Logger, w io.Writer) *Stream { fmt.Fprint(w, " Position [ BarChange ] [ Midi ] [ DMX ] \n") return &Stream{logger, w, 0} diff --git a/pkg/cntl/transport/visualizer.go b/pkg/cntl/transport/visualizer.go index 3000ef4..e3dd9b6 100644 --- a/pkg/cntl/transport/visualizer.go +++ b/pkg/cntl/transport/visualizer.go @@ -7,19 +7,18 @@ import ( "strings" "github.com/StageAutoControl/controller/pkg/cntl" - - "github.com/sirupsen/logrus" + "github.com/StageAutoControl/controller/pkg/internal/logging" ) // Visualizer is a writer to the visualizer tool type Visualizer struct { - logger *logrus.Entry + logger logging.Logger endpoint string socket net.Conn } // NewVisualizer creates a new Visualizer -func NewVisualizer(logger *logrus.Entry, endpoint string) (*Visualizer, error) { +func NewVisualizer(logger logging.Logger, endpoint string) (*Visualizer, error) { socket, err := net.Dial("tcp", endpoint) if err != nil { return nil, err diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 64bdbff..c4d52e4 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -142,6 +142,7 @@ type DMXSubScene struct { Preset *PresetSelector `json:"preset" yaml:"preset"` } +// DMXColorVariable is a global variable for a DMX color type DMXColorVariable struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` diff --git a/pkg/cntl/waiter/audio.go b/pkg/cntl/waiter/audio.go index 6d233e8..0e9d69e 100644 --- a/pkg/cntl/waiter/audio.go +++ b/pkg/cntl/waiter/audio.go @@ -1,13 +1,15 @@ package waiter import ( + "fmt" + + "github.com/StageAutoControl/controller/pkg/internal/logging" "github.com/gordonklaus/portaudio" - "github.com/sirupsen/logrus" ) // Audio is a waiter that does nothing type Audio struct { - logger *logrus.Entry + logger logging.Logger threshold float32 fanOut []chan struct{} buf []float32 @@ -17,8 +19,10 @@ type Audio struct { } // NewAudio creates a new Audio waiter -func NewAudio(logger *logrus.Entry, threshold float32) (*Audio, error) { - portaudio.Initialize() +func NewAudio(logger logging.Logger, threshold float32) (*Audio, error) { + if err := portaudio.Initialize(); err != nil { + return nil, fmt.Errorf("failed to initialize portaudio: %v", err) + } buf := make([]float32, 64) stream, err := portaudio.OpenDefaultStream(1, 0, sampleRate, len(buf), buf) @@ -107,8 +111,6 @@ func (a *Audio) Wait(done chan struct{}, cancel chan struct{}, err chan error) e case err := <-a.err: return err } - - return nil } } @@ -116,7 +118,11 @@ func (a *Audio) Wait(done chan struct{}, cancel chan struct{}, err chan error) e func (a *Audio) Stop() (err error) { a.stop <- struct{}{} - defer portaudio.Terminate() + defer func() { + if err := portaudio.Terminate(); err != nil { + a.logger.Errorf("failed to terminate portaudio: %v", err) + } + }() return a.stream.Close() } diff --git a/pkg/cntl/waiter/none.go b/pkg/cntl/waiter/none.go index f098fbd..d7b7252 100644 --- a/pkg/cntl/waiter/none.go +++ b/pkg/cntl/waiter/none.go @@ -1,14 +1,14 @@ package waiter -import "github.com/sirupsen/logrus" +import "github.com/StageAutoControl/controller/pkg/internal/logging" // None is a waiter that does nothing type None struct { - logger *logrus.Entry + logger logging.Logger } // NewNone creates a new None waiter -func NewNone(logger *logrus.Entry) *None { +func NewNone(logger logging.Logger) *None { return &None{logger} } diff --git a/pkg/internal/logging/logger.go b/pkg/internal/logging/logger.go index 8d185f6..077be83 100644 --- a/pkg/internal/logging/logger.go +++ b/pkg/internal/logging/logger.go @@ -1,30 +1,16 @@ package logging +// Logger is a logging interface used for convenience across the application type Logger interface { Debugf(format string, args ...interface{}) Infof(format string, args ...interface{}) - Printf(format string, args ...interface{}) Warnf(format string, args ...interface{}) Warningf(format string, args ...interface{}) Errorf(format string, args ...interface{}) - Fatalf(format string, args ...interface{}) - Panicf(format string, args ...interface{}) Debug(args ...interface{}) Info(args ...interface{}) - Print(args ...interface{}) Warn(args ...interface{}) Warning(args ...interface{}) Error(args ...interface{}) - Fatal(args ...interface{}) - Panic(args ...interface{}) - - Debugln(args ...interface{}) - Infoln(args ...interface{}) - Println(args ...interface{}) - Warnln(args ...interface{}) - Warningln(args ...interface{}) - Errorln(args ...interface{}) - Fatalln(args ...interface{}) - Panicln(args ...interface{}) } diff --git a/pkg/process/buffered_logger.go b/pkg/process/buffered_logger.go new file mode 100644 index 0000000..50e9622 --- /dev/null +++ b/pkg/process/buffered_logger.go @@ -0,0 +1,82 @@ +package process + +import ( + "fmt" + "time" +) + +// BufferedLogger appends the logs to a given slice of Log entries which is passed by reference +type BufferedLogger struct { + logs *[]Log + verbose bool +} + +// NewBufferedLogger returns a new BufferedLogger instance +func NewBufferedLogger(logs *[]Log, verbose bool) *BufferedLogger { + return &BufferedLogger{ + logs: logs, + verbose: verbose, + } +} + +func (l *BufferedLogger) log(level, msg string) { + if !l.verbose && level == "debug" { + return + } + + *l.logs = append(*l.logs, Log{ + Time: JSONTime{Time: time.Now()}, + Level: level, + Message: msg, + }) +} + +// Debugf log method +func (l *BufferedLogger) Debugf(format string, args ...interface{}) { + l.log("debug", fmt.Sprintf(format, args...)) +} + +// Infof log method +func (l *BufferedLogger) Infof(format string, args ...interface{}) { + l.log("info", fmt.Sprintf(format, args...)) +} + +// Warnf log method +func (l *BufferedLogger) Warnf(format string, args ...interface{}) { + l.log("warn", fmt.Sprintf(format, args...)) +} + +// Warningf log method +func (l *BufferedLogger) Warningf(format string, args ...interface{}) { + l.log("warning", fmt.Sprintf(format, args...)) +} + +// Errorf log method +func (l *BufferedLogger) Errorf(format string, args ...interface{}) { + l.log("error", fmt.Sprintf(format, args...)) +} + +// Debug log method +func (l *BufferedLogger) Debug(args ...interface{}) { + l.log("debug", fmt.Sprint(args...)) +} + +// Info log method +func (l *BufferedLogger) Info(args ...interface{}) { + l.log("info", fmt.Sprint(args...)) +} + +// Warn log method +func (l *BufferedLogger) Warn(args ...interface{}) { + l.log("warn", fmt.Sprint(args...)) +} + +// Warning log method +func (l *BufferedLogger) Warning(args ...interface{}) { + l.log("warning", fmt.Sprint(args...)) +} + +// Error log method +func (l *BufferedLogger) Error(args ...interface{}) { + l.log("error", fmt.Sprint(args...)) +} diff --git a/pkg/process/errors.go b/pkg/process/errors.go new file mode 100644 index 0000000..e3afec0 --- /dev/null +++ b/pkg/process/errors.go @@ -0,0 +1,10 @@ +package process + +import "errors" + +var ( + errProcessNotFound = errors.New("the process with given name was not found or isn't running") + errProcessAlreadyExists = errors.New("the process with the given name already exists") + errProcessAlreadyRunning = errors.New("the process with the given name is already running") + errProcessNotRunning = errors.New("the process with the given name is not running") +) diff --git a/pkg/process/manager.go b/pkg/process/manager.go new file mode 100644 index 0000000..701c5bc --- /dev/null +++ b/pkg/process/manager.go @@ -0,0 +1,116 @@ +package process + +import ( + "context" + "fmt" + "time" + + "github.com/StageAutoControl/controller/pkg/internal/logging" +) + +type processInfo struct { + process Process + status Status +} + +type manager struct { + ctx context.Context + logger logging.Logger + processes map[string]processInfo +} + +// NewManager returns a new process manager instance +func NewManager(ctx context.Context, logger logging.Logger) Manager { + m := &manager{ + ctx: ctx, + logger: logger, + processes: make(map[string]processInfo), + } + + go m.listenExit() + + return m +} + +func (m *manager) listenExit() { + <-m.ctx.Done() + for name := range m.processes { + if _, err := m.Stop(name); err != nil { + m.logger.Errorf("failed to stop process %q: %v", name, err) + } + } +} + +func (m *manager) AddProcess(name string, process Process, verbose bool) error { + if _, ok := m.processes[name]; ok { + return errProcessAlreadyExists + } + + m.processes[name] = processInfo{ + process: process, + status: Status{ + Name: name, + Running: false, + Logs: make([]Log, 0), + Verbose: verbose, + }, + } + return nil +} + +func (m *manager) GetProcess(name string) (Process, *Status, error) { + info, ok := m.processes[name] + if !ok { + return nil, nil, errProcessNotFound + } + + return info.process, &info.status, nil +} + +func (m *manager) Start(name string) (*Status, error) { + info, ok := m.processes[name] + if !ok { + return nil, errProcessNotFound + } + + if info.status.Running { + return nil, errProcessAlreadyRunning + } + + info.status.Running = true + info.status.Error = nil + info.status.StartedAt = JSONTime{Time: time.Now()} + info.status.StoppedAt = JSONTime{} + info.status.Logs = make([]Log, 0) + + logger := NewBufferedLogger(&info.status.Logs, info.status.Verbose) + info.process.SetLogger(logger) + + if err := info.process.Start(m.ctx); err != nil { + info.status.Error = err + return m.Stop(name) + } + + return &info.status, nil +} + +// Stop a given process ID +func (m *manager) Stop(name string) (*Status, error) { + p, ok := m.processes[name] + if !ok { + return nil, errProcessNotFound + } + + if !p.status.Running { + return nil, errProcessNotRunning + } + + if err := p.process.Stop(); err != nil { + return nil, fmt.Errorf("failed to stop process %q: %v", name, err) + } + + p.status.Running = false + p.status.StoppedAt = JSONTime{Time: time.Now()} + + return &p.status, nil +} diff --git a/pkg/process/time.go b/pkg/process/time.go new file mode 100644 index 0000000..6c40e99 --- /dev/null +++ b/pkg/process/time.go @@ -0,0 +1,34 @@ +package process + +import ( + "fmt" + "time" +) + +// JSONTime handles parsing and formatting timestamps according the ISO8601 standard +type JSONTime struct { + time.Time +} + +// String returns a string representation of the time. +func (t JSONTime) String() string { + return t.Format(time.RFC3339) +} + +// MarshalJSON formats the timestamp as JSON +func (t JSONTime) MarshalJSON() ([]byte, error) { + date := fmt.Sprintf("%q", t.String()) + return []byte(date), nil +} + +func datesAreEqual(t1 *JSONTime, t2 *JSONTime) bool { + if (t1 == nil && t2 != nil) || (t1 != nil && t2 == nil) { + return false + } + + if t1 == nil && t2 == nil { + return true + } + + return (*t1).Equal(t2.Time) +} diff --git a/pkg/process/time_test.go b/pkg/process/time_test.go new file mode 100644 index 0000000..2cde8e1 --- /dev/null +++ b/pkg/process/time_test.go @@ -0,0 +1,54 @@ +package process + +import ( + "bytes" + "encoding/json" + "testing" + "time" +) + +type testJSON struct { + Test JSONTime `json:"test"` +} + +var ( + jsonDate = []byte("{\"test\":\"2018-08-18T10:31:17+02:00\"}") + rawDate = "2018-08-18T10:31:17+02:00" +) + +func getTestTime(t *testing.T) JSONTime { + date, err := time.Parse(time.RFC3339, rawDate) + if err != nil { + t.Fatal(err) + } + + return JSONTime{Time: date} +} + +func TestJSONTime_MarshalJSON(t *testing.T) { + test := &testJSON{ + Test: JSONTime(getTestTime(t)), + } + + b, err := json.Marshal(test) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(b, jsonDate) { + t.Fatalf("Expected to get %q, got %q", string(jsonDate), string(b)) + } +} + +func TestJSONTime_UnmarshalJSON(t *testing.T) { + test := &testJSON{} + testTime := getTestTime(t) + + if err := json.Unmarshal(jsonDate, test); err != nil { + t.Fatal(err) + } + + if !test.Test.Equal(testTime.Time) { + t.Fatalf("Dates are not equal: %v, %v", test.Test, testTime) + } +} diff --git a/pkg/process/types.go b/pkg/process/types.go new file mode 100644 index 0000000..18ce49f --- /dev/null +++ b/pkg/process/types.go @@ -0,0 +1,46 @@ +package process + +import ( + "context" + + "github.com/StageAutoControl/controller/pkg/internal/logging" +) + +// Status of a process as handled by the manager +type Status struct { + Name string `json:"name"` + Running bool `json:"running"` + StartedAt JSONTime `json:"started_at"` + StoppedAt JSONTime `json:"stopped_at"` + Error error `json:"error"` + Logs []Log `json:"logs"` + Verbose bool `json:"verbose"` +} + +// Log represents a log line printed by the process +type Log struct { + Time JSONTime `json:"time"` + Level string `json:"level"` + Message string `json:"message"` +} + +// Process carries the information how and what to manage as a process, it implements the custom logic +type Process interface { + // SetLogger sets the logger of the process, which in fact is a buffering logger + SetLogger(logger logging.Logger) + + // Start should care about starting the process, including handling errors if the process does not come up. + // When Start executed without an error the process manager assumes that the process is up and running. + Start(ctx context.Context) error + + // Stop should fully stop the process and also clean up any leftovers (state, files, go routines, ...) + Stop() error +} + +// Manager to handle a set of processes as pets +type Manager interface { + AddProcess(name string, process Process, verbose bool) error + GetProcess(name string) (Process, *Status, error) + Start(name string) (*Status, error) + Stop(name string) (*Status, error) +} From d40ac0f3a6d51d4bc7aaf721136481ba53857f33 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 14 Mar 2019 18:38:37 +0100 Subject: [PATCH 28/94] Move DMXPlaygroundController to api/playground package --- cmd/init.go | 7 ++++++- cmd/root.go | 14 ++++++++------ cmd/server.go | 10 ++++++---- .../dmx_playground_controller.go | 15 ++++++++++++++- pkg/api/server/server.go | 3 ++- 5 files changed, 36 insertions(+), 13 deletions(-) rename pkg/api/{datastore => playground}/dmx_playground_controller.go (79%) diff --git a/cmd/init.go b/cmd/init.go index d3bad40..f8c14c0 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -36,7 +36,12 @@ func createStorage(logger *logrus.Entry, storagePath string) *disk.Storage { return disk.New(storagePath) } -func createController(logger *logrus.Entry) artnet.Controller { +func createController(logger *logrus.Entry, disable bool) artnet.Controller { + if disable { + logger.Warn("ArtNet controller is disabled, so no playback or playground will be possible!") + return nil + } + c, err := artnet.NewController(logger.WithField("module", "controller")) if err != nil { logger.Fatal(err) diff --git a/cmd/root.go b/cmd/root.go index 10d7379..965bbd2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,11 +13,12 @@ import ( ) var ( - logLevel string - logger *logrus.Entry - storagePath string - storage *disk.Storage - controller artnet.Controller + logLevel string + logger *logrus.Entry + storagePath string + storage *disk.Storage + controller artnet.Controller + disableController bool ) // RootCmd represents the base command when called without any subcommands @@ -28,7 +29,7 @@ var RootCmd = &cobra.Command{ PersistentPreRun: func(cmd *cobra.Command, args []string) { logger = createLogger(logLevel) storage = createStorage(logger, storagePath) - controller = createController(logger) + controller = createController(logger, disableController) }, } @@ -43,5 +44,6 @@ func Execute() { func init() { RootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Which log level to use") + RootCmd.PersistentFlags().BoolVar(&disableController, "disable-controller", false, "Disable the controller, e.g. when not on an artnet network") RootCmd.PersistentFlags().StringVarP(&storagePath, "storage-path", "s", "/var/controller/data", "path where the storage should store the data") } diff --git a/cmd/server.go b/cmd/server.go index 29a8cd1..0d9299e 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -29,12 +29,14 @@ var serverCmd = &cobra.Command{ } endpoint := fmt.Sprintf("0.0.0.0:%d", port) - loader := disk.NewLoader(storage) - if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller), true); err != nil { - logger.Fatal(err) - } + if !disableController { + if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller), true); err != nil { + logger.Fatal(err) + } + + } if err := server.Run(ctx, endpoint); err != nil { logger.Fatal(err) } diff --git a/pkg/api/datastore/dmx_playground_controller.go b/pkg/api/playground/dmx_playground_controller.go similarity index 79% rename from pkg/api/datastore/dmx_playground_controller.go rename to pkg/api/playground/dmx_playground_controller.go index f99d554..1e3a328 100644 --- a/pkg/api/datastore/dmx_playground_controller.go +++ b/pkg/api/playground/dmx_playground_controller.go @@ -1,6 +1,7 @@ -package datastore +package playground import ( + "errors" "net/http" "github.com/StageAutoControl/controller/pkg/api" @@ -8,6 +9,10 @@ import ( "github.com/StageAutoControl/controller/pkg/internal/logging" ) +var ( + errControllerDisabled = errors.New("the ArtNet controller is not set, most likely it is disabled in your current instance") +) + // DMXPlaygroundController to play around and test DMX settings type DMXPlaygroundController struct { logger logging.Logger @@ -24,12 +29,20 @@ func NewDMXPlaygroundController(logger logging.Logger, controller artnet.Control // SetChannelValue sets a single artnet/dmx value func (c *DMXPlaygroundController) SetChannelValue(r *http.Request, value *artnet.ChannelValue, response *api.Empty) error { + if c.controller == nil { + return errControllerDisabled + } + c.controller.SetDMXChannelValue(*value) return nil } // SetChannelValues sets multiple artnet/dmx values func (c *DMXPlaygroundController) SetChannelValues(r *http.Request, values *[]artnet.ChannelValue, response *api.Empty) error { + if c.controller == nil { + return errControllerDisabled + } + c.controller.SetDMXChannelValues(*values) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index aca5f07..7dce344 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -8,6 +8,7 @@ import ( "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/api/datastore" "github.com/StageAutoControl/controller/pkg/api/playback" + "github.com/StageAutoControl/controller/pkg/api/playground" "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/process" "github.com/gorilla/handlers" @@ -55,7 +56,7 @@ func (s *Server) registerControllers() error { "DMXColorVariable": datastore.NewDMXColorVariableController(s.logger, s.storage), "Song": datastore.NewSongController(s.logger, s.storage), "SetList": datastore.NewSetListController(s.logger, s.storage), - "DMXPlayground": datastore.NewDMXPlaygroundController(s.logger, s.cntl), + "DMXPlayground": playground.NewDMXPlaygroundController(s.logger, s.cntl), "Playback": playback.NewController(s.pm), } From 09c98af73e02cb2a6d96b2e93594ee2884b1ffe0 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 15 Mar 2019 21:05:56 +0100 Subject: [PATCH 29/94] Add logging to controller --- glide.lock | 12 ++++++------ pkg/artnet/controller.go | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/glide.lock b/glide.lock index f8d3f9e..b4e9a85 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: cf169cddd10a9453d7edb6f4facbf3a022d5581edc3b0f17a20081dd22d2bea0 -updated: 2019-03-09T12:56:06.76403+01:00 +updated: 2019-03-15T21:03:27.602112+01:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -36,19 +36,19 @@ imports: - name: github.com/satori/go.uuid version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 - name: github.com/sirupsen/logrus - version: d7b6bf5e4d26448fd977d07d745a2a66097ddecb + version: dae0fa8d5b0c810a8ab733fbd5510c7cae84eca4 - name: github.com/spf13/cobra - version: 7547e83b2d85fd1893c7d76916f67689d761fecb + version: ba1052d4cbce7aac421a96de820558f75199ccbc - name: github.com/spf13/pflag version: 24fa6976df40757dce6aea913e7b81ade90530e1 - name: github.com/spf13/viper - version: 6d33b5a963d922d182c91e8a1c88d81fd150cfd4 + version: 9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9 - name: golang.org/x/crypto - version: c2843e01d9a2bc60bb26ad24e09734fdc2d9ec58 + version: a1f597ede03a7bef967a422b5b3a5bd08805a01e subpackages: - ssh/terminal - name: golang.org/x/sys - version: 584f3b12f43e1e55248c90e84804777009eed0a4 + version: fead79001313d15903fb4605b4a1b781532cd93e subpackages: - unix - windows diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index 4b7d65c..88d7936 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "os" + "strings" + "time" "github.com/StageAutoControl/controller/pkg/internal/logging" "github.com/jsimonetti/go-artnet" @@ -13,7 +15,8 @@ import ( // Controller is a transport for the ArtNet protocol (DMX over UDP/IP) type controller struct { - sender Sender + logger logging.Logger + sender *artnet.Controller state State sendTrigger chan struct{} } @@ -34,16 +37,26 @@ func NewController(logger logging.Logger) (Controller, error) { panic(err) } - c := artnet.NewController(host, ip, artnet.NewLogger(logger.(*logrus.Entry))) + host = strings.ToLower(strings.Split(host, ".")[0]) + + logger.Infof("Using ArtNet IP %s and hostname %s", ip.String(), host) + + c := artnet.NewController(host, ip, artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet"))) if err := c.Start(); err != nil { return nil, fmt.Errorf("failed to start Controller: %v", err) } - return &controller{ + cntl := &controller{ + logger: logger, sender: c, state: NewState(), sendTrigger: make(chan struct{}, 1), - }, nil + } + + go cntl.sendBackground() + go cntl.debugDevices() + + return cntl, nil } // Start the controller @@ -76,6 +89,7 @@ func (c *controller) triggerSend() { func (c *controller) sendBackground() { for range c.sendTrigger { + c.logger.Debug("Sending DMX Values") c.send() } } @@ -97,3 +111,17 @@ func (c *controller) universeToAddress(universe uint16) artnet.Address { SubUni: v[1], } } + +func (c *controller) debugDevices() { + t := time.NewTicker(30 * time.Second) + for range t.C { + c.logger.Debugf("Currently %d devices are registered: %+s", len(c.sender.Nodes), ips(c.sender.Nodes)) + } +} + +func ips(nodes []*artnet.ControlledNode) (ips []string) { + for _, n := range nodes { + ips = append(ips, NodeToString(n)) + } + return +} From 72b5e88642c9c6ba82b404c1289fa19191e8cf20 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 15 Mar 2019 23:29:43 +0100 Subject: [PATCH 30/94] Clean up makefile --- Makefile | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Makefile b/Makefile index 2c142b3..727fc95 100644 --- a/Makefile +++ b/Makefile @@ -19,30 +19,6 @@ lint: test: go test -v $(PACKAGES) -start-playback-visualizer: build-darwin - ./bin/controller_darwin playback song "${SONG}" \ - --data-dir "${SAC_DATA_DIR}" \ - --transport visualizer \ - --visualizer-endpoint localhost:1337 - -start-playback-stream: build-darwin - ./bin/controller_darwin playback song "${SONG}" \ - --data-dir "${SAC_DATA_DIR}" \ - --transport stream - -start-playback-none: build-darwin - ./bin/controller_darwin playback song "${SONG}" \ - --data-dir "${SAC_DATA_DIR}" - -start-playback-artnet: build-darwin - ./bin/controller_darwin playback song "${SONG}" \ - --data-dir "${SAC_DATA_DIR}" \ - --transport artnet - -start-api: build-darwin - ./bin/controller_darwin api \ - -s ./storage - build-all: build-darwin build-arm build-linux build-docker build-darwin: From f73f29be6965055405b494953f842fd2327b65d5 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 15 Mar 2019 23:43:09 +0100 Subject: [PATCH 31/94] Fix broken artnet package --- glide.lock | 7 ++++--- glide.yaml | 1 + pkg/artnet/controller.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/glide.lock b/glide.lock index b4e9a85..b23f071 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: cf169cddd10a9453d7edb6f4facbf3a022d5581edc3b0f17a20081dd22d2bea0 -updated: 2019-03-15T21:03:27.602112+01:00 +hash: 4a22eeea5709acfd04e88ed3dbdd1980fbae5816b39c4d81487823d65d3cc8f9 +updated: 2019-03-15T23:30:34.346436+01:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,8 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: f0b9cc962f904f793900f27ba65ffc04b4a942eb + version: 824feaef253c302b68768088dcc293a9b71a2b4e + repo: git@github.com:StageAutoControl/go-artnet subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index b981a8b..b841d3a 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,6 +12,7 @@ import: - package: github.com/jsimonetti/go-artnet subpackages: - packet/code + repo: git@github.com:StageAutoControl/go-artnet - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index 88d7936..3f27203 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -96,7 +96,7 @@ func (c *controller) sendBackground() { func (c *controller) send() { for universe, dmx := range c.state { - c.sender.SendDMXToAddress(dmx, c.universeToAddress(universe)) + go c.sender.SendDMXToAddress(dmx, c.universeToAddress(universe)) } } From 4d3df0ddbeae7b29d16e33af8ffa076399947c94 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 18 Mar 2019 12:00:06 +0100 Subject: [PATCH 32/94] Remove unused name fields, fix returned empty slices on API --- pkg/api/datastore/dmx_animation_controller.go | 1 + .../dmx_color_variable_controller.go | 2 +- pkg/api/datastore/dmx_device_controller.go | 2 +- .../datastore/dmx_device_group_controller.go | 1 + .../datastore/dmx_device_type_controller.go | 1 + pkg/api/datastore/dmx_preset_controller.go | 1 + pkg/api/datastore/dmx_scene_controller.go | 1 + .../datastore/dmx_transition_controller.go | 1 + pkg/api/datastore/set_list_controller.go | 1 + pkg/api/datastore/song_controller.go | 1 + pkg/api/server/server.go | 3 +- pkg/cntl/dmx/animation.go | 13 ++++++- pkg/cntl/types.go | 36 ++++++++----------- pkg/cntl/types_equals.go | 3 +- pkg/cntl/types_equals_lists.go | 2 +- pkg/internal/fixtures/fixtures.go | 5 ++- 16 files changed, 41 insertions(+), 33 deletions(-) diff --git a/pkg/api/datastore/dmx_animation_controller.go b/pkg/api/datastore/dmx_animation_controller.go index 7af008b..9799883 100644 --- a/pkg/api/datastore/dmx_animation_controller.go +++ b/pkg/api/datastore/dmx_animation_controller.go @@ -74,6 +74,7 @@ func (c *DMXAnimationController) Get(r *http.Request, idReq *api.IDBody, reply * // GetAll returns all entities of DMXAnimation func (c *DMXAnimationController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXAnimation) error { + *reply = []*cntl.DMXAnimation{} for _, id := range c.storage.List(&cntl.DMXAnimation{}) { entity := &cntl.DMXAnimation{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_color_variable_controller.go b/pkg/api/datastore/dmx_color_variable_controller.go index e5e3439..3b582d4 100644 --- a/pkg/api/datastore/dmx_color_variable_controller.go +++ b/pkg/api/datastore/dmx_color_variable_controller.go @@ -61,7 +61,6 @@ func (c *DMXColorVariableController) Get(r *http.Request, idReq *api.IDBody, rep return api.ErrNoIDGiven } - fmt.Println(idReq.ID) if !c.storage.Has(idReq.ID, &cntl.DMXColorVariable{}) { return api.ErrNotExists } @@ -75,6 +74,7 @@ func (c *DMXColorVariableController) Get(r *http.Request, idReq *api.IDBody, rep // GetAll returns all entities of DMXColorVariable func (c *DMXColorVariableController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXColorVariable) error { + *reply = []*cntl.DMXColorVariable{} for _, id := range c.storage.List(&cntl.DMXColorVariable{}) { entity := &cntl.DMXColorVariable{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_device_controller.go b/pkg/api/datastore/dmx_device_controller.go index 0e9909e..f94b331 100644 --- a/pkg/api/datastore/dmx_device_controller.go +++ b/pkg/api/datastore/dmx_device_controller.go @@ -81,7 +81,6 @@ func (c *DMXDeviceController) Get(r *http.Request, idReq *api.IDBody, reply *cnt return api.ErrNoIDGiven } - fmt.Println(idReq.ID) if !c.storage.Has(idReq.ID, &cntl.DMXDevice{}) { return api.ErrNotExists } @@ -95,6 +94,7 @@ func (c *DMXDeviceController) Get(r *http.Request, idReq *api.IDBody, reply *cnt // GetAll returns all entities of DMXDevice func (c *DMXDeviceController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXDevice) error { + *reply = []*cntl.DMXDevice{} for _, id := range c.storage.List(&cntl.DMXDevice{}) { entity := &cntl.DMXDevice{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_device_group_controller.go b/pkg/api/datastore/dmx_device_group_controller.go index a9a8e5e..8ee55eb 100644 --- a/pkg/api/datastore/dmx_device_group_controller.go +++ b/pkg/api/datastore/dmx_device_group_controller.go @@ -74,6 +74,7 @@ func (c *DMXDeviceGroupController) Get(r *http.Request, idReq *api.IDBody, reply // GetAll returns all entities of DMXDeviceGroup func (c *DMXDeviceGroupController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXDeviceGroup) error { + *reply = []*cntl.DMXDeviceGroup{} for _, id := range c.storage.List(&cntl.DMXDeviceGroup{}) { entity := &cntl.DMXDeviceGroup{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_device_type_controller.go b/pkg/api/datastore/dmx_device_type_controller.go index 23ce824..e673f60 100644 --- a/pkg/api/datastore/dmx_device_type_controller.go +++ b/pkg/api/datastore/dmx_device_type_controller.go @@ -90,6 +90,7 @@ func (c *DMXDeviceTypeController) Get(r *http.Request, idReq *api.IDBody, reply // GetAll returns all entities of DMXDeviceType func (c *DMXDeviceTypeController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXDeviceType) error { + *reply = []*cntl.DMXDeviceType{} for _, id := range c.storage.List(&cntl.DMXDeviceType{}) { entity := &cntl.DMXDeviceType{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_preset_controller.go b/pkg/api/datastore/dmx_preset_controller.go index 531bc99..9f5fb21 100644 --- a/pkg/api/datastore/dmx_preset_controller.go +++ b/pkg/api/datastore/dmx_preset_controller.go @@ -74,6 +74,7 @@ func (c *DMXPresetController) Get(r *http.Request, idReq *api.IDBody, reply *cnt // GetAll returns all entities of DMXPreset func (c *DMXPresetController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXPreset) error { + *reply = []*cntl.DMXPreset{} for _, id := range c.storage.List(&cntl.DMXPreset{}) { entity := &cntl.DMXPreset{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_scene_controller.go b/pkg/api/datastore/dmx_scene_controller.go index 8fa7aae..867ebc1 100644 --- a/pkg/api/datastore/dmx_scene_controller.go +++ b/pkg/api/datastore/dmx_scene_controller.go @@ -74,6 +74,7 @@ func (c *DMXSceneController) Get(r *http.Request, idReq *api.IDBody, reply *cntl // GetAll returns all entities of DMXScene func (c *DMXSceneController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXScene) error { + *reply = []*cntl.DMXScene{} for _, id := range c.storage.List(&cntl.DMXScene{}) { entity := &cntl.DMXScene{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/dmx_transition_controller.go b/pkg/api/datastore/dmx_transition_controller.go index 1b61e74..37db5d5 100644 --- a/pkg/api/datastore/dmx_transition_controller.go +++ b/pkg/api/datastore/dmx_transition_controller.go @@ -74,6 +74,7 @@ func (c *DMXTransitionController) Get(r *http.Request, idReq *api.IDBody, reply // GetAll returns all entities of DMXTransition func (c *DMXTransitionController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.DMXTransition) error { + *reply = []*cntl.DMXTransition{} for _, id := range c.storage.List(&cntl.DMXTransition{}) { entity := &cntl.DMXTransition{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/set_list_controller.go b/pkg/api/datastore/set_list_controller.go index 39a653b..5b655fc 100644 --- a/pkg/api/datastore/set_list_controller.go +++ b/pkg/api/datastore/set_list_controller.go @@ -74,6 +74,7 @@ func (c *SetListController) Get(r *http.Request, idReq *api.IDBody, reply *cntl. // GetAll returns all entities of SetList func (c *SetListController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.SetList) error { + *reply = []*cntl.SetList{} for _, id := range c.storage.List(&cntl.SetList{}) { entity := &cntl.SetList{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/datastore/song_controller.go b/pkg/api/datastore/song_controller.go index 5df3219..8982c34 100644 --- a/pkg/api/datastore/song_controller.go +++ b/pkg/api/datastore/song_controller.go @@ -74,6 +74,7 @@ func (c *SongController) Get(r *http.Request, idReq *api.IDBody, reply *cntl.Son // GetAll returns all entities of Song func (c *SongController) GetAll(r *http.Request, idReq *api.Empty, reply *[]*cntl.Song) error { + *reply = []*cntl.Song{} for _, id := range c.storage.List(&cntl.Song{}) { entity := &cntl.Song{} if err := c.storage.Read(id, entity); err != nil { diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 7dce344..7ec6c74 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -81,8 +81,7 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { } }) - h := handlers.LoggingHandler(s.logger.Writer(), r) - h = handlers.RecoveryHandler()(h) + h := handlers.RecoveryHandler()(r) h = handlers.CORS( handlers.AllowCredentials(), handlers.AllowedOrigins([]string{"*"}), diff --git a/pkg/cntl/dmx/animation.go b/pkg/cntl/dmx/animation.go index 05d27e8..da43194 100644 --- a/pkg/cntl/dmx/animation.go +++ b/pkg/cntl/dmx/animation.go @@ -8,7 +8,7 @@ import ( // RenderAnimation renders the given DMXAnimation to an array of DMXCommands to be sent to a DMX device func RenderAnimation(ds *cntl.DataStore, dd []*cntl.DMXDevice, a *cntl.DMXAnimation) ([]cntl.DMXCommands, error) { - cmds := make([]cntl.DMXCommands, a.Length) + cmds := make([]cntl.DMXCommands, maxFrame(a)) for _, f := range a.Frames { ps, err := RenderParams(ds, dd, f.Params) if err != nil { @@ -20,3 +20,14 @@ func RenderAnimation(ds *cntl.DataStore, dd []*cntl.DMXDevice, a *cntl.DMXAnimat return cmds, nil } + +func maxFrame(a *cntl.DMXAnimation) uint8 { + var max uint8 + for _, f := range a.Frames { + if f.At > max { + max = f.At + } + } + + return max +} diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index c4d52e4..18f514b 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -2,8 +2,7 @@ package cntl // SongSelector is a selector for a song type SongSelector struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` + ID string `json:"id" yaml:"id"` } // SetList is a set of songs in a specific order @@ -21,22 +20,21 @@ type BarChange struct { Speed uint16 `json:"speed" yaml:"speed"` } -// ScenePosition describes the position of a DMX scene within a song -type ScenePosition struct { +// DMXScenePosition describes the position of a DMX scene within a song +type DMXScenePosition struct { ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` At uint64 `json:"at" yaml:"at"` Repeat uint8 `json:"repeat" yaml:"repeat"` } // Song is the whole container for everything that needs to be controlled during a song. type Song struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - BarChanges []BarChange `json:"barChanges" yaml:"barChanges"` - DMXScenes []ScenePosition `json:"dmxScenes" yaml:"dmxScenes"` - DMXDeviceParams []DMXDeviceParams `json:"dmxDeviceParams" yaml:"dmxDeviceParams"` - MIDICommands []MIDICommand `json:"midiCommands" yaml:"midiCommands"` + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + BarChanges []BarChange `json:"barChanges" yaml:"barChanges"` + DMXScenes []DMXScenePosition `json:"dmxScenes" yaml:"dmxScenes"` + DMXDeviceParams []DMXDeviceParams `json:"dmxDeviceParams" yaml:"dmxDeviceParams"` + MIDICommands []MIDICommand `json:"midiCommands" yaml:"midiCommands"` } // Tag is a string literal tagging a DMX device @@ -82,14 +80,12 @@ type LED struct { // DMXDeviceSelector is a selector for DMX devices type DMXDeviceSelector struct { ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` Tags []Tag `json:"tags" yaml:"tags"` } // DMXDeviceGroupSelector is a selector for DMX device groups type DMXDeviceGroupSelector struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` + ID string `json:"id" yaml:"id"` } // DMXDeviceGroup is a DMX device group @@ -101,14 +97,12 @@ type DMXDeviceGroup struct { // AnimationSelector selects an animation type AnimationSelector struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` + ID string `json:"id" yaml:"id"` } // TransitionSelector selects a transition type TransitionSelector struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` + ID string `json:"id" yaml:"id"` } // DMXDeviceParams is an object storing DMX parameters including the selection of either groups or devices @@ -131,8 +125,7 @@ type DMXScene struct { // PresetSelector is a selector for a preset type PresetSelector struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` + ID string `json:"id" yaml:"id"` } // DMXSubScene is a sub scene of a light scene @@ -163,7 +156,7 @@ type DMXParams struct { Pan *DMXValue `json:"pan" yaml:"pan"` Tilt *DMXValue `json:"tilt" yaml:"tilt"` Strobe *DMXValue `json:"strobe" yaml:"strobe"` - Mode *DMXValue `json:"preset" yaml:"preset"` + Mode *DMXValue `json:"mode" yaml:"mode"` Dimmer *DMXValue `json:"dimmer" yaml:"dimmer"` } @@ -171,7 +164,6 @@ type DMXParams struct { type DMXAnimation struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` - Length uint8 `json:"length" yaml:"length"` Frames []DMXAnimationFrame `json:"frames" yaml:"frames"` } diff --git a/pkg/cntl/types_equals.go b/pkg/cntl/types_equals.go index 9a7ebe2..4c3372b 100644 --- a/pkg/cntl/types_equals.go +++ b/pkg/cntl/types_equals.go @@ -21,7 +21,7 @@ func (v1 BarChange) Equals(v2 BarChange) bool { } // Equals returns whether the two given objects are equal -func (v1 ScenePosition) Equals(v2 ScenePosition) bool { +func (v1 DMXScenePosition) Equals(v2 DMXScenePosition) bool { return v1.At == v2.At && v1.ID == v2.ID && v1.Repeat == v2.Repeat @@ -141,7 +141,6 @@ func (v1 DMXParams) Equals(v2 DMXParams) bool { // Equals returns whether the two given objects are equal func (v1 DMXAnimation) Equals(v2 DMXAnimation) bool { return v2.ID == v2.ID && - v1.Length == v2.Length && dmxAnimationFrameList(v1.Frames).Equals(dmxAnimationFrameList(v2.Frames)) } diff --git a/pkg/cntl/types_equals_lists.go b/pkg/cntl/types_equals_lists.go index ed8f0a4..d06121d 100644 --- a/pkg/cntl/types_equals_lists.go +++ b/pkg/cntl/types_equals_lists.go @@ -32,7 +32,7 @@ func (v1 barChangeList) Equals(v2 barChangeList) bool { return true } -type scenePositionList []ScenePosition +type scenePositionList []DMXScenePosition func (v1 scenePositionList) Equals(v2 scenePositionList) bool { if len(v1) != len(v2) { diff --git a/pkg/internal/fixtures/fixtures.go b/pkg/internal/fixtures/fixtures.go index aa9bffe..f26a8eb 100644 --- a/pkg/internal/fixtures/fixtures.go +++ b/pkg/internal/fixtures/fixtures.go @@ -39,7 +39,7 @@ var data = &cntl.DataStore{ {At: 1184, NoteCount: 7, NoteValue: 8}, {At: 1632, NoteCount: 4, NoteValue: 4}, }, - DMXScenes: []cntl.ScenePosition{ + DMXScenes: []cntl.DMXScenePosition{ {At: 0, ID: "492cef2e-0b14-11e7-be89-c3fa25f9cabb", Repeat: 3}, {At: 512, ID: "a44f8dee-0b14-11e7-b5b9-bf1015384192", Repeat: 3}, {At: 1408, ID: "99b86a5e-0e7a-11e7-a01a-5b5fbdeba3d6", Repeat: 2}, @@ -334,8 +334,7 @@ var data = &cntl.DataStore{ }, DMXAnimations: map[string]*cntl.DMXAnimation{ "a51f7b2a-0e7b-11e7-bfc8-57da167865d7": { - ID: "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", - Length: 4, + ID: "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", Frames: []cntl.DMXAnimationFrame{ {At: 0, Params: cntl.DMXParams{LED: 1, Blue: Value31}}, {At: 1, Params: cntl.DMXParams{LED: 1, Blue: Value63}}, From 0537c478763adb6f3388494acef064a1361da4da Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 18 Mar 2019 12:00:35 +0100 Subject: [PATCH 33/94] Re-add loader for backwards compatibility --- pkg/loader/files/.gitignore | 1 + pkg/loader/files/db.go | 44 +++++ pkg/loader/files/db_test.go | 107 ++++++++++++ pkg/loader/files/fixtures/dmx_animations.json | 34 ++++ .../files/fixtures/dmx_device_groups.json | 37 ++++ .../files/fixtures/dmx_device_types.json | 159 ++++++++++++++++++ pkg/loader/files/fixtures/dmx_devices.json | 82 +++++++++ pkg/loader/files/fixtures/dmx_presets.json | 84 +++++++++ pkg/loader/files/fixtures/dmx_scenes.json | 112 ++++++++++++ .../files/fixtures/dmx_transitions.json | 82 +++++++++ pkg/loader/files/fixtures/set_lists.json | 13 ++ pkg/loader/files/fixtures/songs.json | 53 ++++++ pkg/loader/files/read.go | 103 ++++++++++++ 13 files changed, 911 insertions(+) create mode 100755 pkg/loader/files/.gitignore create mode 100755 pkg/loader/files/db.go create mode 100755 pkg/loader/files/db_test.go create mode 100755 pkg/loader/files/fixtures/dmx_animations.json create mode 100755 pkg/loader/files/fixtures/dmx_device_groups.json create mode 100755 pkg/loader/files/fixtures/dmx_device_types.json create mode 100755 pkg/loader/files/fixtures/dmx_devices.json create mode 100755 pkg/loader/files/fixtures/dmx_presets.json create mode 100755 pkg/loader/files/fixtures/dmx_scenes.json create mode 100755 pkg/loader/files/fixtures/dmx_transitions.json create mode 100755 pkg/loader/files/fixtures/set_lists.json create mode 100755 pkg/loader/files/fixtures/songs.json create mode 100755 pkg/loader/files/read.go diff --git a/pkg/loader/files/.gitignore b/pkg/loader/files/.gitignore new file mode 100755 index 0000000..9daeafb --- /dev/null +++ b/pkg/loader/files/.gitignore @@ -0,0 +1 @@ +test diff --git a/pkg/loader/files/db.go b/pkg/loader/files/db.go new file mode 100755 index 0000000..8e6c775 --- /dev/null +++ b/pkg/loader/files/db.go @@ -0,0 +1,44 @@ +package files + +import ( + "github.com/StageAutoControl/controller/pkg/cntl" +) + +type fileData struct { + SetLists []*cntl.SetList `json:"set_lists"` + Songs []*cntl.Song `json:"songs"` + DMXScenes []*cntl.DMXScene `json:"dmx_scenes"` + DMXPresets []*cntl.DMXPreset `json:"dmx_presets"` + DMXAnimations []*cntl.DMXAnimation `json:"dmx_animations"` + DMXTransitions []*cntl.DMXTransition `json:"dmx_transitions"` + DMXDevices []*cntl.DMXDevice `json:"dmx_devices"` + DMXDeviceTypes []*cntl.DMXDeviceType `json:"dmx_device_types"` + DMXDeviceGroups []*cntl.DMXDeviceGroup `json:"dmx_device_groups"` + DMXColorVariables []*cntl.DMXColorVariable `json:"dmx_color_variables"` +} + +// Database is a file repository +type Database struct { + dataDir string +} + +// New crates a new file repository and returns it. +func New(dataDir string) *Database { + return &Database{ + dataDir: dataDir, + } +} + +// Load implements cntl.Loader and loads the data from filesystem +func (d *Database) Load() (*cntl.DataStore, error) { + store := cntl.NewStore() + data, err := d.readDir(d.dataDir) + + if err != nil { + return nil, err + } + + expandData(store, data) + + return store, nil +} diff --git a/pkg/loader/files/db_test.go b/pkg/loader/files/db_test.go new file mode 100755 index 0000000..43a5e75 --- /dev/null +++ b/pkg/loader/files/db_test.go @@ -0,0 +1,107 @@ +package files + +import ( + "testing" + + "github.com/StageAutoControl/controller/pkg/internal/fixtures" +) + +func TestRepository_Load(t *testing.T) { + dir := "./fixtures" + fix := fixtures.DataStore() + + loader := New(dir) + data, err := loader.Load() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + for key, sl := range fix.SetLists { + dsl, ok := data.SetLists[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if dsl.ID != sl.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, s := range fix.Songs { + ds, ok := data.Songs[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if ds.ID != s.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, s := range fix.DMXScenes { + ds, ok := data.DMXScenes[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if ds.ID != s.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, p := range fix.DMXPresets { + dp, ok := data.DMXPresets[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if dp.ID != p.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, a := range fix.DMXAnimations { + da, ok := data.DMXAnimations[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if da.ID != a.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, tr := range fix.DMXTransitions { + dt, ok := data.DMXTransitions[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if dt.ID != tr.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, dg := range fix.DMXDeviceGroups { + ddg, ok := data.DMXDeviceGroups[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if ddg.ID != dg.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + + for key, d := range fix.DMXDevices { + dd, ok := data.DMXDevices[key] + if !ok { + t.Fatalf("ID %q not found \n", key) + } + + if dd.ID != d.ID { + t.Errorf("ID %q is not equal \n", key) + } + } + +} diff --git a/pkg/loader/files/fixtures/dmx_animations.json b/pkg/loader/files/fixtures/dmx_animations.json new file mode 100755 index 0000000..416f527 --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_animations.json @@ -0,0 +1,34 @@ +{ + "dmx_animations": [ + { + "frames": [ + { + "at": 0, + "params": { + "blue": 31 + } + }, + { + "at": 1, + "params": { + "blue": 63 + } + }, + { + "at": 2, + "params": { + "blue": 127 + } + }, + { + "at": 3, + "params": { + "blue": 255 + } + } + ], + "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", + "length": 4 + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_device_groups.json b/pkg/loader/files/fixtures/dmx_device_groups.json new file mode 100755 index 0000000..841c267 --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_device_groups.json @@ -0,0 +1,37 @@ +{ + "dmx_device_groups": [ + { + "devices": [ + { + "tags": [ + "par", + "left" + ] + } + ], + "id": "475b71a0-0b16-11e7-9406-e3f678e8b788", + "name": "All PARs on the left side" + }, + { + "devices": [ + { + "tags": [ + "par", + "right" + ] + } + ], + "id": "29f7adf8-0b17-11e7-bd45-9f82a70b477b", + "name": "All PARs on the right side" + }, + { + "devices": [ + { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + } + ], + "id": "cb58bc10-0b16-11e7-b45a-7bee591b0adb", + "name": "LED Bar infront the drums" + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_device_types.json b/pkg/loader/files/fixtures/dmx_device_types.json new file mode 100755 index 0000000..eb3a9bb --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_device_types.json @@ -0,0 +1,159 @@ +{ + "dmx_device_types": [ + { + "channelCount": 67, + "channelsPerLED": 4, + "dimmerChannel": 1, + "dimmerEnabled": true, + "id": "1555d67e-1187-11e7-8135-9b41038b5b75", + "key": "LEDBar67", + "leds": [ + { + "blue": 2, + "green": 1, + "position": 0, + "red": 0, + "white": 3 + }, + { + "blue": 6, + "green": 5, + "position": 1, + "red": 4, + "white": 7 + }, + { + "blue": 10, + "green": 9, + "position": 2, + "red": 8, + "white": 11 + }, + { + "blue": 14, + "green": 13, + "position": 3, + "red": 12, + "white": 15 + }, + { + "blue": 18, + "green": 17, + "position": 4, + "red": 16, + "white": 19 + }, + { + "blue": 22, + "green": 21, + "position": 5, + "red": 20, + "white": 23 + }, + { + "blue": 26, + "green": 25, + "position": 6, + "red": 24, + "white": 27 + }, + { + "blue": 30, + "green": 29, + "position": 7, + "red": 28, + "white": 31 + }, + { + "blue": 34, + "green": 33, + "position": 8, + "red": 32, + "white": 35 + }, + { + "blue": 38, + "green": 37, + "position": 9, + "red": 36, + "white": 39 + }, + { + "blue": 42, + "green": 41, + "position": 10, + "red": 40, + "white": 43 + }, + { + "blue": 46, + "green": 45, + "position": 11, + "red": 44, + "white": 47 + }, + { + "blue": 50, + "green": 49, + "position": 12, + "red": 48, + "white": 51 + }, + { + "blue": 54, + "green": 53, + "position": 13, + "red": 52, + "white": 55 + }, + { + "blue": 58, + "green": 57, + "position": 14, + "red": 56, + "white": 59 + }, + { + "blue": 62, + "green": 61, + "position": 15, + "red": 60, + "white": 63 + } + ], + "modeChannel": 0, + "modeEnabled": true, + "name": "LED-Bar 67 Channel", + "strobeChannel": 2, + "strobeEnabled": true + }, + { + "channelCount": 5, + "channelsPerLED": 3, + "dimmerChannel": 3, + "dimmerEnabled": true, + "id": "628fc3ea-1188-11e7-8824-5f72d80c17b6", + "key": "PAR5", + "leds": [ + { + "blue": 2, + "green": 1, + "position": 0, + "red": 0 + } + ], + "modeChannel": 0, + "modeEnabled": false, + "name": "PAR 5 channel", + "strobeChannel": 4, + "strobeEnabled": true + }, + { + "id": "5ccc43ee-118c-11e7-8d53-974b41748b71", + "key": "Strobe", + "name": "Strobe", + "strobeChannel": 0, + "strobeEnabled": true + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_devices.json b/pkg/loader/files/fixtures/dmx_devices.json new file mode 100755 index 0000000..78b046a --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_devices.json @@ -0,0 +1,82 @@ +{ + "dmx_devices": [ + { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e", + "name": "LED-Bar below drums front", + "startChannel": 222, + "tags": [ + "bar", + "drums" + ], + "typeId": "1555d67e-1187-11e7-8135-9b41038b5b75", + "universe": 1 + }, + { + "id": "s429fc37c-0b17-11e7-8b94-c3b6519355d3", + "name": "PAR inner left, stand 1 position 1", + "startChannel": 10, + "tags": [ + "par", + "left", + "inner", + "stand", + "drums-left" + ], + "typeId": "par", + "universe": 2 + }, + { + "id": "4a545466-0b17-11e7-9c61-d3c0693099ab", + "name": "PAR inner left, stand 1 position 2", + "startChannel": 14, + "tags": [ + "par", + "left", + "inner", + "stand", + "drums-left" + ], + "typeId": "par", + "universe": 2 + }, + { + "id": "5e0335e0-0b17-11e7-ad6c-63a7138d926c", + "name": "PAR inner right, stand 2 position 1", + "startChannel": 26, + "tags": [ + "par", + "right", + "inner", + "stand", + "drums-right" + ], + "typeId": "par", + "universe": 2 + }, + { + "id": "620101f4-0b17-11e7-85cc-539952d9aef2", + "name": "PAR inner right, stand 2 position 2", + "startChannel": 30, + "tags": [ + "par", + "right", + "inner", + "stand", + "drums-right" + ], + "typeId": "par", + "universe": 2 + }, + { + "id": "6f7bca8a-0b17-11e7-b604-a356da737e54", + "name": "Strobe Vocs", + "startChannel": 202, + "tags": [ + "strobe-back", + "vocs" + ], + "typeId": "strobe", + "universe": 1 + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_presets.json b/pkg/loader/files/fixtures/dmx_presets.json new file mode 100755 index 0000000..aca4878 --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_presets.json @@ -0,0 +1,84 @@ +{ + "dmx_presets": [ + { + "deviceParams": [ + { + "device": { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + }, + "params": [ + { + "red": 255 + } + ] + } + ], + "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc", + "name": "Test-Preset 1" + }, + { + "deviceParams": [ + { + "device": { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + }, + "params": [ + { + "blue": 255 + } + ] + } + ], + "id": "11adf93e-0e7b-11e7-998c-5bd2bd0df396", + "name": "Test-Preset 2" + }, + { + "deviceParams": [ + { + "device": { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + }, + "params": [ + { + "green": 255 + } + ] + } + ], + "id": "652e716a-0e7b-11e7-b92a-8f2ff28ba235", + "name": "Test-Preset 4" + }, + { + "deviceParams": [ + { + "group": { + "id": "cb58bc10-0b16-11e7-b45a-7bee591b0adb" + }, + "params": [ + { + "strobe": 255 + } + ] + } + ], + "id": "5d3a415a-0b15-11e7-90b9-03c2b960e034", + "name": "Test-Preset 4" + }, + { + "deviceParams": [ + { + "group": { + "id": "475b71a0-0b16-11e7-9406-e3f678e8b788" + }, + "params": [ + { + "red": 200 + } + ] + } + ], + "id": "4e3c2e84-0b15-11e7-a076-4b5bbb4c19bf", + "name": "Test-Preset 5" + } + ] +} diff --git a/pkg/loader/files/fixtures/dmx_scenes.json b/pkg/loader/files/fixtures/dmx_scenes.json new file mode 100755 index 0000000..89d42d3 --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_scenes.json @@ -0,0 +1,112 @@ +{ + "dmx_scenes": [ + { + "id": "492cef2e-0b14-11e7-be89-c3fa25f9cabb", + "name": "Test-Scene 1", + "noteCount": 4, + "noteValue": 4, + "subScenes": [ + { + "at": [ + 0, + 1, + 2, + 3 + ], + "preset": { + "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + } + } + ] + }, + { + "id": "a44f8dee-0b14-11e7-b5b9-bf1015384192", + "name": "Test-Scene 2", + "noteCount": 2, + "noteValue": 4, + "subScenes": [ + { + "at": [ + 0, + 1 + ], + "preset": { + "id": "11adf93e-0e7b-11e7-998c-5bd2bd0df396" + } + } + ] + }, + { + "id": "99b86a5e-0e7a-11e7-a01a-5b5fbdeba3d6", + "name": "Test-Scene 3", + "noteCount": 8, + "noteValue": 4, + "subScenes": [ + { + "at": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "deviceParams": [ + { + "animation": { + "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + }, + "device": { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + } + } + ] + } + ] + }, + { + "id": "b82f4750-0e7a-11e7-9522-0f9d6d69958a", + "name": "Test-Scene 4", + "noteCount": 4, + "noteValue": 4, + "subScenes": [ + { + "at": [ + 0, + 1, + 2, + 3 + ], + "preset": { + "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" + } + } + ] + }, + { + "id": "5adec126-6618-42ca-b930-4d18f4524328", + "name": "Test-Scene 5", + "noteCount": 32, + "noteValue": 32, + "subScenes": [ + { + "at": [ + 0 + ], + "deviceParams": [ + { + "transition": { + "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" + }, + "device": { + "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/dmx_transitions.json b/pkg/loader/files/fixtures/dmx_transitions.json new file mode 100755 index 0000000..026db6e --- /dev/null +++ b/pkg/loader/files/fixtures/dmx_transitions.json @@ -0,0 +1,82 @@ +{ + "dmx_transitions": [ + { + "id": "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21", + "name": "Blue Bar pulsing on", + "ease": "InOutQuad", + "length": 8, + "params": [ + { + "from": { + "led": 1, + "blue": 0 + }, + "to": { + "led": 1, + "blue": 255 + } + }, + { + "from": { + "led": 2, + "blue": 0 + }, + "to": { + "led": 2, + "blue": 255 + } + } + ] + }, + { + "id": "525eaa7e-fb2d-4608-b413-559d284b3c85", + "name": "Blue Bar pulsing off", + "ease": "InOutQuad", + "length": 8, + "params": [ + { + "from": { + "led": 1, + "blue": 255 + }, + "to": { + "led": 1, + "blue": 0 + } + }, + { + "from": { + "led": 2, + "blue": 255 + }, + "to": { + "led": 2, + "blue": 0 + } + } + ] + }, + { + "id": "e683873b-20da-4fd4-ac62-271925c68047", + "name": "From Red to Green and a little blue", + "ease": "InOutQuad", + "length": 8, + "params": [ + { + "from": { + "led": 1, + "red": 255, + "green": 0, + "blue": 31 + }, + "to": { + "led": 1, + "red": 0, + "green": 255, + "blue": 127 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/set_lists.json b/pkg/loader/files/fixtures/set_lists.json new file mode 100755 index 0000000..71b4409 --- /dev/null +++ b/pkg/loader/files/fixtures/set_lists.json @@ -0,0 +1,13 @@ +{ + "set_lists": [ + { + "id": "f5b4be8a-0b18-11e7-b837-4bac99d86956", + "name": "Regular gig", + "songs": [ + { + "id": "3c1065c8-0b14-11e7-96eb-5b134621c411" + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/loader/files/fixtures/songs.json b/pkg/loader/files/fixtures/songs.json new file mode 100755 index 0000000..cccd171 --- /dev/null +++ b/pkg/loader/files/fixtures/songs.json @@ -0,0 +1,53 @@ +{ + "songs": [ + { + "barChanges": [ + { + "at": 0, + "noteCount": 4, + "noteValue": 4, + "speed": 160 + }, + { + "at": 512, + "noteCount": 3, + "noteValue": 4 + }, + { + "at": 1184, + "noteCount": 7, + "noteValue": 8 + }, + { + "at": 1632, + "noteCount": 4, + "noteValue": 4 + } + ], + "dmxScenes": [ + { + "at": 0, + "id": "492cef2e-0b14-11e7-be89-c3fa25f9cabb", + "repeat": 4 + }, + { + "at": 512, + "id": "a44f8dee-0b14-11e7-b5b9-bf1015384192", + "repeat": 3 + }, + { + "at": 1408, + "id": "652e716a-0e7b-11e7-b92a-8f2ff28ba235", + "repeat": 2 + }, + { + "at": 1920, + "id": "5d3a415a-0b15-11e7-90b9-03c2b960e034", + "repeat": 0 + } + ], + "id": "3c1065c8-0b14-11e7-96eb-5b134621c411", + "name": "Test song" + } + ] +} diff --git a/pkg/loader/files/read.go b/pkg/loader/files/read.go new file mode 100755 index 0000000..022ecf5 --- /dev/null +++ b/pkg/loader/files/read.go @@ -0,0 +1,103 @@ +package files + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/StageAutoControl/controller/pkg/cntl" +) + +func (d *Database) readDir(dir string) (*fileData, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + data := new(fileData) + for _, file := range files { + file := filepath.Join(dir, file.Name()) + fData := new(fileData) + if err := d.readFile(file, fData); err != nil { + return nil, fmt.Errorf("error reading file %q: %v", file, err) + } + + data = mergeFileData(data, fData) + } + + return data, nil +} + +func (d *Database) readFile(file string, target interface{}) error { + b, err := ioutil.ReadFile(file) + if err != nil { + return fmt.Errorf("error reading file %q: %v", file, err) + } + + err = json.Unmarshal(b, target) + if err != nil { + return fmt.Errorf("unable to parse content of %q: %v", file, err) + } + + return nil +} + +func mergeFileData(data, fd *fileData) *fileData { + newData := new(fileData) + + newData.SetLists = append(data.SetLists, fd.SetLists...) + newData.Songs = append(data.Songs, fd.Songs...) + newData.DMXScenes = append(data.DMXScenes, fd.DMXScenes...) + newData.DMXPresets = append(data.DMXPresets, fd.DMXPresets...) + newData.DMXAnimations = append(data.DMXAnimations, fd.DMXAnimations...) + newData.DMXTransitions = append(data.DMXTransitions, fd.DMXTransitions...) + newData.DMXDevices = append(data.DMXDevices, fd.DMXDevices...) + newData.DMXDeviceTypes = append(data.DMXDeviceTypes, fd.DMXDeviceTypes...) + newData.DMXDeviceGroups = append(data.DMXDeviceGroups, fd.DMXDeviceGroups...) + newData.DMXColorVariables = append(data.DMXColorVariables, fd.DMXColorVariables...) + + return newData +} + +func expandData(data *cntl.DataStore, fileData *fileData) { + for _, sl := range fileData.SetLists { + data.SetLists[sl.ID] = sl + } + + for _, s := range fileData.Songs { + data.Songs[s.ID] = s + } + + for _, d := range fileData.DMXDevices { + data.DMXDevices[d.ID] = d + } + + for _, dg := range fileData.DMXDeviceGroups { + data.DMXDeviceGroups[dg.ID] = dg + } + + for _, dt := range fileData.DMXDeviceTypes { + data.DMXDeviceTypes[dt.ID] = dt + } + + for _, p := range fileData.DMXPresets { + data.DMXPresets[p.ID] = p + } + + for _, sc := range fileData.DMXScenes { + data.DMXScenes[sc.ID] = sc + } + + for _, a := range fileData.DMXAnimations { + data.DMXAnimations[a.ID] = a + } + + for _, t := range fileData.DMXTransitions { + data.DMXTransitions[t.ID] = t + } + + for _, t := range fileData.DMXColorVariables { + data.DMXColorVariables[t.ID] = t + } +} From 7ed513d6e47ab90cefbaa7c3a92710dbb58b398c Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 19 Mar 2019 17:38:17 +0100 Subject: [PATCH 34/94] Remove unused Selector types where not neccessary --- pkg/cntl/dmx/animation.go | 2 +- pkg/cntl/dmx/params.go | 20 ++++---- pkg/cntl/dmx/params_test.go | 60 +++++++++++----------- pkg/cntl/dmx/scene.go | 4 +- pkg/cntl/playback/player.go | 12 ++--- pkg/cntl/types.go | 43 ++++------------ pkg/cntl/types_equals.go | 10 ---- pkg/cntl/types_equals_lists.go | 4 +- pkg/internal/fixtures/fixtures.go | 57 +++++++------------- pkg/loader/files/fixtures/dmx_presets.json | 20 ++------ pkg/loader/files/fixtures/dmx_scenes.json | 30 +++-------- pkg/loader/files/fixtures/set_lists.json | 6 +-- pkg/process/manager_test.go | 10 ++++ 13 files changed, 103 insertions(+), 175 deletions(-) create mode 100644 pkg/process/manager_test.go diff --git a/pkg/cntl/dmx/animation.go b/pkg/cntl/dmx/animation.go index da43194..ddb50db 100644 --- a/pkg/cntl/dmx/animation.go +++ b/pkg/cntl/dmx/animation.go @@ -8,7 +8,7 @@ import ( // RenderAnimation renders the given DMXAnimation to an array of DMXCommands to be sent to a DMX device func RenderAnimation(ds *cntl.DataStore, dd []*cntl.DMXDevice, a *cntl.DMXAnimation) ([]cntl.DMXCommands, error) { - cmds := make([]cntl.DMXCommands, maxFrame(a)) + cmds := make([]cntl.DMXCommands, maxFrame(a)+1) for _, f := range a.Frames { ps, err := RenderParams(ds, dd, f.Params) if err != nil { diff --git a/pkg/cntl/dmx/params.go b/pkg/cntl/dmx/params.go index 5d78bf6..37aaef5 100644 --- a/pkg/cntl/dmx/params.go +++ b/pkg/cntl/dmx/params.go @@ -46,9 +46,9 @@ func RenderDeviceParams(ds *cntl.DataStore, dp *cntl.DMXDeviceParams) ([]cntl.DM var dd []*cntl.DMXDevice if dp.Group != nil { - g, ok := ds.DMXDeviceGroups[dp.Group.ID] + g, ok := ds.DMXDeviceGroups[*dp.Group] if !ok { - return []cntl.DMXCommands{}, fmt.Errorf("failed to find DMXDeviceGroup %q", dp.Group.ID) + return []cntl.DMXCommands{}, fmt.Errorf("failed to find DMXDeviceGroup %q", *dp.Group) } for _, sel := range g.Devices { @@ -62,12 +62,12 @@ func RenderDeviceParams(ds *cntl.DataStore, dp *cntl.DMXDeviceParams) ([]cntl.DM } if dp.Device != nil { - d, err := ResolveDeviceSelector(ds, dp.Device) - if err != nil { - return []cntl.DMXCommands{}, err + d, ok := ds.DMXDevices[*dp.Device] + if !ok { + return []cntl.DMXCommands{}, fmt.Errorf("failed to find DMXDevice %q", *dp.Device) } - dd = append(dd, d...) + dd = append(dd, d) } if len(dd) == 0 { @@ -75,18 +75,18 @@ func RenderDeviceParams(ds *cntl.DataStore, dp *cntl.DMXDeviceParams) ([]cntl.DM } if dp.Animation != nil { - a, ok := ds.DMXAnimations[dp.Animation.ID] + a, ok := ds.DMXAnimations[*dp.Animation] if !ok { - return []cntl.DMXCommands{}, fmt.Errorf("unable to find DMXAnimation %q", dp.Animation.ID) + return []cntl.DMXCommands{}, fmt.Errorf("failed to find DMXAnimation %q", *dp.Animation) } return RenderAnimation(ds, dd, a) } if dp.Transition != nil { - t, ok := ds.DMXTransitions[dp.Transition.ID] + t, ok := ds.DMXTransitions[*dp.Transition] if !ok { - return []cntl.DMXCommands{}, fmt.Errorf("unable to find DMXTransition %q", dp.Animation.ID) + return []cntl.DMXCommands{}, fmt.Errorf("failed to find DMXTransition %q", *dp.Animation) } return RenderTransition(ds, dd, t) diff --git a/pkg/cntl/dmx/params_test.go b/pkg/cntl/dmx/params_test.go index 49fefae..e7102b5 100644 --- a/pkg/cntl/dmx/params_test.go +++ b/pkg/cntl/dmx/params_test.go @@ -16,34 +16,34 @@ func TestCheckDeviceParams(t *testing.T) { }{ { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Group: &cntl.DMXDeviceGroupSelector{ID: "asdf2"}, - Transition: &cntl.TransitionSelector{ID: "anim1"}, - Animation: &cntl.AnimationSelector{ID: "anim2"}, + Device: fixtures.StrPtr("asdf"), + Group: fixtures.StrPtr("asdf2"), + Transition: fixtures.StrPtr("anim1"), + Animation: fixtures.StrPtr("anim2"), }, expectedErr: ErrDeviceParamsDevicesInvalid, }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Group: &cntl.DMXDeviceGroupSelector{ID: "asdf2"}, - Transition: &cntl.TransitionSelector{ID: "anim1"}, - Animation: &cntl.AnimationSelector{ID: "anim2"}, + Device: fixtures.StrPtr("asdf"), + Group: fixtures.StrPtr("asdf2"), + Transition: fixtures.StrPtr("anim1"), + Animation: fixtures.StrPtr("anim2"), }, expectedErr: ErrDeviceParamsDevicesInvalid, }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Transition: &cntl.TransitionSelector{ID: "anim1"}, - Animation: &cntl.AnimationSelector{ID: "anim2"}, + Device: fixtures.StrPtr("asdf"), + Transition: fixtures.StrPtr("anim1"), + Animation: fixtures.StrPtr("anim2"), }, expectedErr: ErrDeviceParamsValuesInvalid, }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Transition: &cntl.TransitionSelector{ID: "anim1"}, + Device: fixtures.StrPtr("asdf"), + Transition: fixtures.StrPtr("anim1"), Params: []cntl.DMXParams{ {Blue: fixtures.Value255}, }, @@ -52,8 +52,8 @@ func TestCheckDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Animation: &cntl.AnimationSelector{ID: "anim1"}, + Device: fixtures.StrPtr("asdf"), + Animation: fixtures.StrPtr("anim1"), Params: []cntl.DMXParams{ {Blue: fixtures.Value255}}, }, @@ -61,7 +61,7 @@ func TestCheckDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, + Device: fixtures.StrPtr("asdf"), Params: []cntl.DMXParams{ {Blue: fixtures.Value255}, }, @@ -70,21 +70,21 @@ func TestCheckDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Animation: &cntl.AnimationSelector{ID: "anim1"}, + Device: fixtures.StrPtr("asdf"), + Animation: fixtures.StrPtr("anim1"), }, expectedErr: nil, }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "asdf"}, - Transition: &cntl.TransitionSelector{ID: "anim1"}, + Device: fixtures.StrPtr("asdf"), + Transition: fixtures.StrPtr("anim1"), }, expectedErr: nil, }, { dp: &cntl.DMXDeviceParams{ - Group: &cntl.DMXDeviceGroupSelector{ID: "asdf"}, + Group: fixtures.StrPtr("asdf"), Params: []cntl.DMXParams{ {Blue: fixtures.Value255}, }, @@ -93,15 +93,15 @@ func TestCheckDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Group: &cntl.DMXDeviceGroupSelector{ID: "asdf"}, - Animation: &cntl.AnimationSelector{ID: "anim1"}, + Group: fixtures.StrPtr("asdf"), + Animation: fixtures.StrPtr("anim1"), }, expectedErr: nil, }, { dp: &cntl.DMXDeviceParams{ - Group: &cntl.DMXDeviceGroupSelector{ID: "asdf"}, - Transition: &cntl.TransitionSelector{ID: "anim1"}, + Group: fixtures.StrPtr("asdf"), + Transition: fixtures.StrPtr("anim1"), }, expectedErr: nil, }, @@ -124,7 +124,7 @@ func TestRenderDeviceParams(t *testing.T) { }{ { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "5e0335e0-0b17-11e7-ad6c-63a7138d926c"}, + Device: fixtures.StrPtr("5e0335e0-0b17-11e7-ad6c-63a7138d926c"), Params: []cntl.DMXParams{ { Red: fixtures.Value255, @@ -145,7 +145,7 @@ func TestRenderDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Group: &cntl.DMXDeviceGroupSelector{ID: "475b71a0-0b16-11e7-9406-e3f678e8b788"}, + Group: fixtures.StrPtr("475b71a0-0b16-11e7-9406-e3f678e8b788"), Params: []cntl.DMXParams{ { Red: fixtures.Value255, @@ -169,7 +169,7 @@ func TestRenderDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Group: &cntl.DMXDeviceGroupSelector{ID: "cb58bc10-0b16-11e7-b45a-7bee591b0adb"}, + Group: fixtures.StrPtr("cb58bc10-0b16-11e7-b45a-7bee591b0adb"), Params: []cntl.DMXParams{ {Mode: fixtures.Value200}, }, @@ -183,8 +183,8 @@ func TestRenderDeviceParams(t *testing.T) { }, { dp: &cntl.DMXDeviceParams{ - Device: &cntl.DMXDeviceSelector{ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e"}, - Animation: &cntl.AnimationSelector{ID: "a51f7b2a-0e7b-11e7-bfc8-57da167865d7"}, + Device: fixtures.StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), + Animation: fixtures.StrPtr("a51f7b2a-0e7b-11e7-bfc8-57da167865d7"), }, c: []cntl.DMXCommands{ { diff --git a/pkg/cntl/dmx/scene.go b/pkg/cntl/dmx/scene.go index 4f0498a..daf6be5 100644 --- a/pkg/cntl/dmx/scene.go +++ b/pkg/cntl/dmx/scene.go @@ -51,9 +51,9 @@ func RenderScene(ds *cntl.DataStore, sc *cntl.DMXScene) ([]cntl.DMXCommands, err } if ss.Preset != nil { - p, ok := ds.DMXPresets[ss.Preset.ID] + p, ok := ds.DMXPresets[*ss.Preset] if !ok { - return []cntl.DMXCommands{}, fmt.Errorf("cannot find DMXPreset %q", ss.Preset.ID) + return []cntl.DMXCommands{}, fmt.Errorf("cannot find DMXPreset %q", *ss.Preset) } pcs, err := RenderPreset(ds, p) diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index eab384c..71a4227 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -31,9 +31,9 @@ func NewPlayer(logger logging.Logger, ds *cntl.DataStore, writers []TransportWri } func (p *Player) checkSetList(setList *cntl.SetList) error { - for _, songSel := range setList.Songs { - if _, ok := p.dataStore.Songs[songSel.ID]; !ok { - return fmt.Errorf("cannot find Process %q", songSel.ID) + for _, songID := range setList.Songs { + if _, ok := p.dataStore.Songs[songID]; !ok { + return fmt.Errorf("cannot find Process %q", songID) } } @@ -51,7 +51,7 @@ func (p *Player) PlaySetList(ctx context.Context, setListID string) error { return err } - for _, songSel := range setList.Songs { + for _, songID := range setList.Songs { select { case <-ctx.Done(): p.logger.Warn("Aborting") @@ -59,9 +59,9 @@ func (p *Player) PlaySetList(ctx context.Context, setListID string) error { default: } - p.logger.Infof("Playing song %s", songSel.ID) + p.logger.Infof("Playing song %s", songID) - if err := p.PlaySong(ctx, songSel.ID); err != nil { + if err := p.PlaySong(ctx, songID); err != nil { return err } } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 18f514b..1da3aca 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -1,15 +1,10 @@ package cntl -// SongSelector is a selector for a song -type SongSelector struct { - ID string `json:"id" yaml:"id"` -} - // SetList is a set of songs in a specific order type SetList struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - Songs []SongSelector `json:"songs" yaml:"songs"` + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + Songs []string `json:"songs" yaml:"songs"` } // BarChange describes the changes of tempo and notes during a song @@ -83,11 +78,6 @@ type DMXDeviceSelector struct { Tags []Tag `json:"tags" yaml:"tags"` } -// DMXDeviceGroupSelector is a selector for DMX device groups -type DMXDeviceGroupSelector struct { - ID string `json:"id" yaml:"id"` -} - // DMXDeviceGroup is a DMX device group type DMXDeviceGroup struct { ID string `json:"id" yaml:"id"` @@ -95,23 +85,13 @@ type DMXDeviceGroup struct { Devices []DMXDeviceSelector `json:"devices" yaml:"devices"` } -// AnimationSelector selects an animation -type AnimationSelector struct { - ID string `json:"id" yaml:"id"` -} - -// TransitionSelector selects a transition -type TransitionSelector struct { - ID string `json:"id" yaml:"id"` -} - // DMXDeviceParams is an object storing DMX parameters including the selection of either groups or devices type DMXDeviceParams struct { - Group *DMXDeviceGroupSelector `json:"group" yaml:"group"` - Device *DMXDeviceSelector `json:"device" yaml:"device"` - Params []DMXParams `json:"params" yaml:"params"` - Animation *AnimationSelector `json:"animation" yaml:"animation"` - Transition *TransitionSelector `json:"transition" yaml:"transition"` + Group *string `json:"group" yaml:"group"` + Device *string `json:"device" yaml:"device"` + Params []DMXParams `json:"params" yaml:"params"` + Animation *string `json:"animation" yaml:"animation"` + Transition *string `json:"transition" yaml:"transition"` } // DMXScene is a whole light scene @@ -123,16 +103,11 @@ type DMXScene struct { SubScenes []DMXSubScene `json:"subScenes" yaml:"subScenes"` } -// PresetSelector is a selector for a preset -type PresetSelector struct { - ID string `json:"id" yaml:"id"` -} - // DMXSubScene is a sub scene of a light scene type DMXSubScene struct { At []uint64 `json:"at" yaml:"at"` DeviceParams []DMXDeviceParams `json:"deviceParams" yaml:"deviceParams"` - Preset *PresetSelector `json:"preset" yaml:"preset"` + Preset *string `json:"preset" yaml:"preset"` } // DMXColorVariable is a global variable for a DMX color diff --git a/pkg/cntl/types_equals.go b/pkg/cntl/types_equals.go index 4c3372b..5ed4c99 100644 --- a/pkg/cntl/types_equals.go +++ b/pkg/cntl/types_equals.go @@ -1,10 +1,5 @@ package cntl -// Equals returns whether the two given objects are equal -func (v1 SongSelector) Equals(v2 SongSelector) bool { - return v1.ID == v2.ID -} - // Equals returns whether the two given objects are equal func (v1 *SetList) Equals(v2 *SetList) bool { return v1.ID == v2.ID && @@ -82,11 +77,6 @@ func (v1 DMXDeviceSelector) Equals(v2 DMXDeviceSelector) bool { tagList(v1.Tags).Equals(tagList(v2.Tags)) } -// Equals returns whether the two given objects are equal -func (v1 DMXDeviceGroupSelector) Equals(v2 DMXDeviceGroupSelector) bool { - return v1.ID == v2.ID -} - // Equals returns whether the two given objects are equal func (v1 *DMXDeviceGroup) Equals(v2 *DMXDeviceGroup) bool { return v1.ID == v2.ID && diff --git a/pkg/cntl/types_equals_lists.go b/pkg/cntl/types_equals_lists.go index d06121d..94c3b6a 100644 --- a/pkg/cntl/types_equals_lists.go +++ b/pkg/cntl/types_equals_lists.go @@ -1,6 +1,6 @@ package cntl -type songSelectorList []SongSelector +type songSelectorList []string func (v1 songSelectorList) Equals(v2 songSelectorList) bool { if len(v1) != len(v2) { @@ -8,7 +8,7 @@ func (v1 songSelectorList) Equals(v2 songSelectorList) bool { } for i := range v1 { - if !v1[i].Equals(v2[i]) { + if v1[i] != v2[i] { return false } } diff --git a/pkg/internal/fixtures/fixtures.go b/pkg/internal/fixtures/fixtures.go index f26a8eb..429aa3b 100644 --- a/pkg/internal/fixtures/fixtures.go +++ b/pkg/internal/fixtures/fixtures.go @@ -24,8 +24,8 @@ var data = &cntl.DataStore{ "f5b4be8a-0b18-11e7-b837-4bac99d86956": { ID: "f5b4be8a-0b18-11e7-b837-4bac99d86956", Name: "Regular gig", - Songs: []cntl.SongSelector{ - {ID: "3c1065c8-0b14-11e7-96eb-5b134621c411"}, + Songs: []string{ + "3c1065c8-0b14-11e7-96eb-5b134621c411", }, }, }, @@ -53,9 +53,7 @@ var data = &cntl.DataStore{ Name: "Test-Preset 1", DeviceParams: []cntl.DMXDeviceParams{ { - Device: &cntl.DMXDeviceSelector{ - ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", - }, + Device: StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), Params: []cntl.DMXParams{ {ColorVar: StrPtr("Red255")}, }, @@ -66,9 +64,7 @@ var data = &cntl.DataStore{ Name: "Test-Preset 2", DeviceParams: []cntl.DMXDeviceParams{ { - Device: &cntl.DMXDeviceSelector{ - ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", - }, + Device: StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), Params: []cntl.DMXParams{ {ColorVar: StrPtr("Blue255")}, }, @@ -79,9 +75,7 @@ var data = &cntl.DataStore{ Name: "Test-Preset 3", DeviceParams: []cntl.DMXDeviceParams{ { - Device: &cntl.DMXDeviceSelector{ - ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", - }, + Device: StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), Params: []cntl.DMXParams{ {ColorVar: StrPtr("Green255")}, }, @@ -92,9 +86,8 @@ var data = &cntl.DataStore{ ID: "5d3a415a-0b15-11e7-90b9-03c2b960e034", Name: "Test-Preset 4", DeviceParams: []cntl.DMXDeviceParams{ - {Group: &cntl.DMXDeviceGroupSelector{ - ID: "cb58bc10-0b16-11e7-b45a-7bee591b0adb", - }, + { + Group: StrPtr("cb58bc10-0b16-11e7-b45a-7bee591b0adb"), Params: []cntl.DMXParams{ {Strobe: Value255}, }, @@ -106,7 +99,7 @@ var data = &cntl.DataStore{ Name: "Test-Preset 5", DeviceParams: []cntl.DMXDeviceParams{ { - Group: &cntl.DMXDeviceGroupSelector{ID: "475b71a0-0b16-11e7-9406-e3f678e8b788"}, + Group: StrPtr("475b71a0-0b16-11e7-9406-e3f678e8b788"), Params: []cntl.DMXParams{ {Red: Value200}, }, @@ -122,10 +115,8 @@ var data = &cntl.DataStore{ NoteValue: 4, SubScenes: []cntl.DMXSubScene{ { - At: []uint64{0, 1, 2, 3}, - Preset: &cntl.PresetSelector{ - ID: "0de258e0-0e7b-11e7-afd4-ebf6036983dc", - }, + At: []uint64{0, 1, 2, 3}, + Preset: StrPtr("0de258e0-0e7b-11e7-afd4-ebf6036983dc"), }, }, }, @@ -136,10 +127,8 @@ var data = &cntl.DataStore{ NoteValue: 4, SubScenes: []cntl.DMXSubScene{ { - At: []uint64{0, 1}, - Preset: &cntl.PresetSelector{ - ID: "11adf93e-0e7b-11e7-998c-5bd2bd0df396", - }, + At: []uint64{0, 1}, + Preset: StrPtr("11adf93e-0e7b-11e7-998c-5bd2bd0df396"), }, }, }, @@ -153,12 +142,8 @@ var data = &cntl.DataStore{ At: []uint64{0, 4, 8, 12}, DeviceParams: []cntl.DMXDeviceParams{ { - Device: &cntl.DMXDeviceSelector{ - ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", - }, - Animation: &cntl.AnimationSelector{ - ID: "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", - }, + Device: StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), + Animation: StrPtr("a51f7b2a-0e7b-11e7-bfc8-57da167865d7"), }, }, }, @@ -171,10 +156,8 @@ var data = &cntl.DataStore{ NoteValue: 4, SubScenes: []cntl.DMXSubScene{ { - At: []uint64{0, 1, 2, 3}, - Preset: &cntl.PresetSelector{ - ID: "0de258e0-0e7b-11e7-afd4-ebf6036983dc", - }, + At: []uint64{0, 1, 2, 3}, + Preset: StrPtr("0de258e0-0e7b-11e7-afd4-ebf6036983dc"), }, }, }, @@ -188,12 +171,8 @@ var data = &cntl.DataStore{ At: []uint64{0}, DeviceParams: []cntl.DMXDeviceParams{ { - Device: &cntl.DMXDeviceSelector{ - ID: "35cae00a-0b17-11e7-8bca-bbf30c56f20e", - }, - Transition: &cntl.TransitionSelector{ - ID: "a1a02b6c-12dd-4d7b-bc3e-24cc823adf21", - }, + Device: StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), + Transition: StrPtr("a1a02b6c-12dd-4d7b-bc3e-24cc823adf21"), }, }, }, diff --git a/pkg/loader/files/fixtures/dmx_presets.json b/pkg/loader/files/fixtures/dmx_presets.json index aca4878..18ff90e 100755 --- a/pkg/loader/files/fixtures/dmx_presets.json +++ b/pkg/loader/files/fixtures/dmx_presets.json @@ -3,9 +3,7 @@ { "deviceParams": [ { - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - }, + "device": "35cae00a-0b17-11e7-8bca-bbf30c56f20e", "params": [ { "red": 255 @@ -19,9 +17,7 @@ { "deviceParams": [ { - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - }, + "device": "35cae00a-0b17-11e7-8bca-bbf30c56f20e", "params": [ { "blue": 255 @@ -35,9 +31,7 @@ { "deviceParams": [ { - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - }, + "device": "35cae00a-0b17-11e7-8bca-bbf30c56f20e", "params": [ { "green": 255 @@ -51,9 +45,7 @@ { "deviceParams": [ { - "group": { - "id": "cb58bc10-0b16-11e7-b45a-7bee591b0adb" - }, + "group": "cb58bc10-0b16-11e7-b45a-7bee591b0adb", "params": [ { "strobe": 255 @@ -67,9 +59,7 @@ { "deviceParams": [ { - "group": { - "id": "475b71a0-0b16-11e7-9406-e3f678e8b788" - }, + "group": "475b71a0-0b16-11e7-9406-e3f678e8b788", "params": [ { "red": 200 diff --git a/pkg/loader/files/fixtures/dmx_scenes.json b/pkg/loader/files/fixtures/dmx_scenes.json index 89d42d3..10ab619 100755 --- a/pkg/loader/files/fixtures/dmx_scenes.json +++ b/pkg/loader/files/fixtures/dmx_scenes.json @@ -13,9 +13,7 @@ 2, 3 ], - "preset": { - "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" - } + "preset": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" } ] }, @@ -30,9 +28,7 @@ 0, 1 ], - "preset": { - "id": "11adf93e-0e7b-11e7-998c-5bd2bd0df396" - } + "preset": "11adf93e-0e7b-11e7-998c-5bd2bd0df396" } ] }, @@ -55,12 +51,8 @@ ], "deviceParams": [ { - "animation": { - "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" - }, - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - } + "animation": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", + "device": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" } ] } @@ -79,9 +71,7 @@ 2, 3 ], - "preset": { - "id": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" - } + "preset": "0de258e0-0e7b-11e7-afd4-ebf6036983dc" } ] }, @@ -97,16 +87,12 @@ ], "deviceParams": [ { - "transition": { - "id": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7" - }, - "device": { - "id": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" - } + "transition": "a51f7b2a-0e7b-11e7-bfc8-57da167865d7", + "device": "35cae00a-0b17-11e7-8bca-bbf30c56f20e" } ] } ] } ] -} \ No newline at end of file +} diff --git a/pkg/loader/files/fixtures/set_lists.json b/pkg/loader/files/fixtures/set_lists.json index 71b4409..9fa18b6 100755 --- a/pkg/loader/files/fixtures/set_lists.json +++ b/pkg/loader/files/fixtures/set_lists.json @@ -4,10 +4,8 @@ "id": "f5b4be8a-0b18-11e7-b837-4bac99d86956", "name": "Regular gig", "songs": [ - { - "id": "3c1065c8-0b14-11e7-96eb-5b134621c411" - } + "3c1065c8-0b14-11e7-96eb-5b134621c411" ] } ] -} \ No newline at end of file +} diff --git a/pkg/process/manager_test.go b/pkg/process/manager_test.go new file mode 100644 index 0000000..54f75d5 --- /dev/null +++ b/pkg/process/manager_test.go @@ -0,0 +1,10 @@ +package process + +import ( + "testing" +) + +func TestNewManager(t *testing.T) { + //ctx := context.Background() + +} From de80557a1bd3e2ff1e06e8a1d8d382b4fff9b8d6 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 19 Mar 2019 22:28:57 +0100 Subject: [PATCH 35/94] Fix wrongly as ArtNet Subnet identified IPv6 interface --- pkg/artnet/ip.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/artnet/ip.go b/pkg/artnet/ip.go index 162bb32..516afda 100644 --- a/pkg/artnet/ip.go +++ b/pkg/artnet/ip.go @@ -13,26 +13,24 @@ const ( // FindArtNetIP finds the matching interface with an IP address inside of the addressRange func FindArtNetIP() (net.IP, error) { - var ip net.IP - _, cidrnet, _ := net.ParseCIDR(addressRange) addrs, err := net.InterfaceAddrs() if err != nil { - return ip, fmt.Errorf("error getting ips: %s", err) + return nil, fmt.Errorf("error getting ips: %s", err) } for _, addr := range addrs { - ip = addr.(*net.IPNet).IP + ip := addr.(*net.IPNet).IP if strings.Contains(ip.String(), ":") { continue } if cidrnet.Contains(ip) { - break + return ip, nil } } - return ip, nil + return nil, nil } From a056b6e3b8378f54ca9308be94f4dcb2e81bc5c8 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 24 Mar 2019 23:35:25 +0100 Subject: [PATCH 36/94] Work on playback of a single song --- cmd/playback.go | 4 +-- cmd/server.go | 3 ++ pkg/api/datastore/song_controller.go | 26 ++++++++++++++++ pkg/api/playback/playback.go | 36 ++++++++++++++-------- pkg/cntl/dmx/device.go | 32 ++++++++++++++----- pkg/cntl/dmx/params.go | 1 + pkg/cntl/playback/const.go | 22 ++++++++++++- pkg/cntl/playback/default_config.go | 28 +++++++++++++++++ pkg/cntl/playback/process.go | 11 ++++++- pkg/cntl/playback/types.go | 28 ++++++++--------- pkg/cntl/song/const.go | 8 +++++ pkg/cntl/song/helper.go | 23 ++++++++------ pkg/cntl/song/song.go | 12 +++++--- pkg/cntl/song/song_test.go | 2 +- pkg/cntl/transport/midi.go | 12 ++------ pkg/disk/errors.go | 8 +++++ pkg/disk/loader.go | 46 +++++++++++++++++----------- pkg/disk/storage.go | 4 +++ pkg/process/manager.go | 23 +++++++++----- pkg/process/types.go | 4 +-- 20 files changed, 243 insertions(+), 90 deletions(-) create mode 100644 pkg/cntl/playback/default_config.go create mode 100644 pkg/cntl/song/const.go create mode 100644 pkg/disk/errors.go diff --git a/cmd/playback.go b/cmd/playback.go index 6bca9f6..f52138e 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -32,7 +32,7 @@ var ( usedTransports []string viualizerEndpoint string - midiDeviceID string + midiDeviceID int8 waiterTypes = []string{ waiter.TypeNone, @@ -158,7 +158,7 @@ func init() { playbackCmd.Flags().StringSliceVarP(&usedTransports, "transport", "t", []string{}, fmt.Sprintf("Which usedTransports to use from %s.", transportTypes)) playbackCmd.Flags().StringVar(&viualizerEndpoint, "visualizer-endpoint", "localhost:1337", "Endpoint of the visualizer backend if visualizer transport is chosen.") - playbackCmd.Flags().StringVarP(&midiDeviceID, "midi-device-id", "m", "", "DeviceID of MIDI output to use (On empty string the default device is used)") + playbackCmd.Flags().Int8VarP(&midiDeviceID, "midi-device-id", "m", -1, "DeviceID of MIDI output to use (On empty string the default device is used)") playbackCmd.Flags().StringSliceVarP(&usedWaiters, "wait-for", "w", []string{waiter.TypeNone}, fmt.Sprintf("Wait for a specific signal before playing a song (required to be used on stage, otherwise the next song would start immediately), one of %s", waiterTypes)) playbackCmd.Flags().Float32Var(&audioWaiterThreshold, "audio-waiter-threshold", 0.9, "Threshold frequency for audio waiter to trigger a signal") } diff --git a/cmd/server.go b/cmd/server.go index 0d9299e..9ec4d2a 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -32,6 +32,9 @@ var serverCmd = &cobra.Command{ loader := disk.NewLoader(storage) if !disableController { + if err := playback.EnsureDefaultConfig(storage); err != nil { + logger.Fatal(err) + } if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller), true); err != nil { logger.Fatal(err) } diff --git a/pkg/api/datastore/song_controller.go b/pkg/api/datastore/song_controller.go index 8982c34..76be2c1 100644 --- a/pkg/api/datastore/song_controller.go +++ b/pkg/api/datastore/song_controller.go @@ -1,11 +1,13 @@ package datastore import ( + "errors" "fmt" "net/http" "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/cntl/song" "github.com/jinzhu/copier" "github.com/satori/go.uuid" "github.com/sirupsen/logrus" @@ -25,6 +27,22 @@ func NewSongController(logger *logrus.Entry, storage api.Storage) *SongControlle } } +func (c *SongController) validate(entity *cntl.Song) error { + if entity.MIDICommands == nil { + entity.MIDICommands = make([]cntl.MIDICommand, 0) + } + + if entity.BarChanges == nil { + return errors.New("song needs to have at least one BarChange") + } + + if err := song.ValidateBarChanges(song.StreamlineBarChanges(entity)); err != nil { + return fmt.Errorf("failed to validate bar changes: %v", err) + } + + return nil +} + // Create a new Song func (c *SongController) Create(r *http.Request, entity *cntl.Song, reply *cntl.Song) error { if entity.ID == "" { @@ -35,6 +53,10 @@ func (c *SongController) Create(r *http.Request, entity *cntl.Song, reply *cntl. return api.ErrExists } + if err := c.validate(entity); err != nil { + return fmt.Errorf("failed to validate entity: %v", err) + } + if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to write to disk: %v", err) } @@ -48,6 +70,10 @@ func (c *SongController) Update(r *http.Request, entity *cntl.Song, reply *cntl. return api.ErrNotExists } + if err := c.validate(entity); err != nil { + return fmt.Errorf("failed to validate entity: %v", err) + } + if err := c.storage.Write(entity.ID, entity); err != nil { return fmt.Errorf("failed to update to disk: %v", err) } diff --git a/pkg/api/playback/playback.go b/pkg/api/playback/playback.go index f4aafcb..196d027 100644 --- a/pkg/api/playback/playback.go +++ b/pkg/api/playback/playback.go @@ -8,7 +8,6 @@ import ( "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/process" - "github.com/jinzhu/copier" ) var ( @@ -27,13 +26,13 @@ func NewController(pm process.Manager) *Controller { } } -// StartRequest starts the Playback of a Song or SetList -type StartRequest struct { - playback.Params +type Status struct { + Process process.Status `json:"process"` + Params playback.Params `json:"params"` } // Start a playback with either a song or a setlist -func (c *Controller) Start(r *http.Request, req *StartRequest, res *process.Status) error { +func (c *Controller) Start(r *http.Request, req *playback.Params, res *Status) error { if (req.Song.ID != "" && req.SetList.ID != "") || (req.Song.ID == "" && req.SetList.ID == "") { return errSongSetListNeedToBeDistinct } @@ -42,25 +41,38 @@ func (c *Controller) Start(r *http.Request, req *StartRequest, res *process.Stat if err != nil { return fmt.Errorf("failed to fetch playback process: %v", err) } - p.(*playback.Process).SetParams(req.Params) + p.(*playback.Process).SetParams(*req) - if s, err := c.pm.Start(playback.ProcessName); err != nil { + s, err := c.pm.Start(playback.ProcessName) + if err != nil { return fmt.Errorf("failed to start playback: %v", err) - - } else if err := copier.Copy(res, s); err != nil { - return fmt.Errorf("failed to write response body: %v", err) } + res.Process = *s + res.Params = *req + return nil } // Stop a playback -func (c *Controller) Stop(r *http.Request, req *api.IDBody, res *process.Status) error { +func (c *Controller) Stop(r *http.Request, req *api.IDBody, res *Status) error { _, err := c.pm.Stop(playback.ProcessName) + if err != nil { + return fmt.Errorf("failed to stop playback: %v", err) + } + return err } // Status returns the current status of a playback -func (c *Controller) Status(r *http.Request, req *api.IDBody, res *process.Status) error { +func (c *Controller) Status(r *http.Request, req *api.IDBody, res *Status) error { + p, s, err := c.pm.GetProcess(playback.ProcessName) + if err != nil { + return fmt.Errorf("failed to get playback status: %v", err) + } + + res.Process = *s + res.Params = p.(*playback.Process).GetParams() + return nil } diff --git a/pkg/cntl/dmx/device.go b/pkg/cntl/dmx/device.go index be955ae..05de99e 100644 --- a/pkg/cntl/dmx/device.go +++ b/pkg/cntl/dmx/device.go @@ -9,7 +9,7 @@ import ( func getDeviceChannel(ds *cntl.DataStore, d *cntl.DMXDevice, c cntl.DMXChannel, led uint16) (cntl.DMXChannel, error) { dt, ok := ds.DMXDeviceTypes[d.TypeID] if !ok { - return cntl.DMXChannel(0), fmt.Errorf("given DeviceType %q on device %q is unknown", d.TypeID, d.ID) + return 0, fmt.Errorf("given DeviceType %q on device %q is unknown", d.TypeID, d.ID) } // can a param affect multiple LEDs? // Should I switch the scheme of params to have an @@ -17,56 +17,72 @@ func getDeviceChannel(ds *cntl.DataStore, d *cntl.DMXDevice, c cntl.DMXChannel, ledLen := len(dt.LEDs) if ledLen > 0 && int(led) >= ledLen { - return cntl.DMXChannel(0), fmt.Errorf("given device has insufficient biggest index of LEDs %d to handle the given LED index %d", ledLen-1, led) + return 0, fmt.Errorf("given device has insufficient biggest index of LEDs %d to handle the given LED index %d", ledLen-1, led) } var channel cntl.DMXChannel switch c { case ChannelRed: + deviceLED := getLED(dt, led) + if deviceLED == nil { + return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) + } channel = getLED(dt, led).Red case ChannelGreen: + deviceLED := getLED(dt, led) + if deviceLED == nil { + return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) + } channel = getLED(dt, led).Green case ChannelBlue: + deviceLED := getLED(dt, led) + if deviceLED == nil { + return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) + } channel = getLED(dt, led).Blue case ChannelWhite: + deviceLED := getLED(dt, led) + if deviceLED == nil { + return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) + } channel = getLED(dt, led).White case ChannelStrobe: if !dt.StrobeEnabled { - return cntl.DMXChannel(0), ErrDeviceHasDisabledStrobeChannel + return 0, ErrDeviceHasDisabledStrobeChannel } channel = dt.StrobeChannel case ChannelMode: if !dt.ModeEnabled { - return cntl.DMXChannel(0), ErrDeviceHasDisabledModeChannel + return 0, ErrDeviceHasDisabledModeChannel } channel = dt.ModeChannel case ChannelDimmer: if !dt.DimmerEnabled { - return cntl.DMXChannel(0), ErrDeviceHasDisabledDimmerChannel + return 0, ErrDeviceHasDisabledDimmerChannel } channel = dt.DimmerChannel case ChannelTilt: if !dt.Moving { - return cntl.DMXChannel(0), ErrDeviceIsNotMoving + return 0, ErrDeviceIsNotMoving } channel = dt.TiltChannel case ChannelPan: if !dt.Moving { - return cntl.DMXChannel(0), ErrDeviceIsNotMoving + return 0, ErrDeviceIsNotMoving } channel = dt.PanChannel default: - return cntl.DMXChannel(0), fmt.Errorf("channel %q is unknown", c) + return 0, fmt.Errorf("channel %q is unknown", c) } return d.StartChannel + channel, nil diff --git a/pkg/cntl/dmx/params.go b/pkg/cntl/dmx/params.go index 37aaef5..a45e6f0 100644 --- a/pkg/cntl/dmx/params.go +++ b/pkg/cntl/dmx/params.go @@ -46,6 +46,7 @@ func RenderDeviceParams(ds *cntl.DataStore, dp *cntl.DMXDeviceParams) ([]cntl.DM var dd []*cntl.DMXDevice if dp.Group != nil { + g, ok := ds.DMXDeviceGroups[*dp.Group] if !ok { return []cntl.DMXCommands{}, fmt.Errorf("failed to find DMXDeviceGroup %q", *dp.Group) diff --git a/pkg/cntl/playback/const.go b/pkg/cntl/playback/const.go index 019e5a3..0766ac6 100644 --- a/pkg/cntl/playback/const.go +++ b/pkg/cntl/playback/const.go @@ -4,7 +4,8 @@ import "errors" // Player errors var ( - ErrCancelled = errors.New("playback cancelled") + ErrCancelled = errors.New("playback cancelled") + ErrNoSongIDOrSetListIDGiven = errors.New("no songID or setListID given") ) const ( @@ -12,3 +13,22 @@ const ( ProcessName = "playback" paramsStorageKey = "playback_process" ) + +var defaultConfig = ` +{ + "waiters": { + "audio": { + "enabled": true + } + }, + "transportWriters": { + "artNet": { + "enabled": true + }, + "midi": { + "enabled": false, + "outputDeviceId": 0 + } + } +} +` diff --git a/pkg/cntl/playback/default_config.go b/pkg/cntl/playback/default_config.go new file mode 100644 index 0000000..bcb3bc9 --- /dev/null +++ b/pkg/cntl/playback/default_config.go @@ -0,0 +1,28 @@ +package playback + +import ( + "encoding/json" + "fmt" + + "github.com/StageAutoControl/controller/pkg/disk" +) + +// EnsureDefaultConfig ensures that the default configuration exists in given storage +func EnsureDefaultConfig(storage storage) error { + config := &Config{} + if err := storage.Read(paramsStorageKey, config); err != nil { + if err != disk.ErrNotExists { + return fmt.Errorf("failed to find playback config: %v", err) + } + + if err := json.Unmarshal([]byte(defaultConfig), config); err != nil { + return fmt.Errorf("failed to decode the default config: %v", err) + } + + if err := storage.Write(paramsStorageKey, config); err != nil { + return fmt.Errorf("failed to write the default config to storage: %v", err) + } + } + + return nil +} diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index a4bf0f4..9b2cd2d 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -36,6 +36,11 @@ func (p *Process) SetParams(params Params) { p.params = params } +// GetParams returns the params the process is currently running with +func (p *Process) GetParams() Params { + return p.params +} + // SetLogger sets the logger for the process func (p *Process) SetLogger(logger logging.Logger) { p.logger = logger @@ -65,6 +70,8 @@ func (p *Process) Start(ctx context.Context) error { if err := p.player.PlaySong(ctx, p.params.Song.ID); err != nil { return fmt.Errorf("failed to start song playback: %v", err) } + } else { + return ErrNoSongIDOrSetListIDGiven } return nil @@ -114,7 +121,9 @@ func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { // Stop the process, i.e. cancel the playback context func (p *Process) Stop() error { - p.cancel() + if p.cancel != nil { + p.cancel() + } p.player = nil p.ctx = nil diff --git a/pkg/cntl/playback/types.go b/pkg/cntl/playback/types.go index 1b108c9..4c7115e 100644 --- a/pkg/cntl/playback/types.go +++ b/pkg/cntl/playback/types.go @@ -27,11 +27,11 @@ type loader interface { // Params specifies how to run a playback type Params struct { Song struct { - ID string - } + ID string `json:"id"` + } `json:"song"` SetList struct { - ID string - } + ID string `json:"id"` + } `json:"setList"` } type parsedConfig struct { @@ -43,17 +43,17 @@ type parsedConfig struct { type Config struct { Waiters struct { Audio struct { - Enabled bool - Threshold float32 - } - } + Enabled bool `json:"enabled"` + Threshold float32 `json:"threshold"` + } `json:"audio"` + } `json:"waiters"` TransportWriters struct { ArtNet struct { - Enabled bool - } + Enabled bool `json:"enabled"` + } `json:"artNet"` MIDI struct { - Enabled bool - OutputDeviceID string - } - } + Enabled bool `json:"enabled"` + OutputDeviceID int8 `json:"outputDeviceId"` + } `json:"midi"` + } `json:"transportWriters"` } diff --git a/pkg/cntl/song/const.go b/pkg/cntl/song/const.go new file mode 100644 index 0000000..10126ab --- /dev/null +++ b/pkg/cntl/song/const.go @@ -0,0 +1,8 @@ +package song + +import "errors" + +// Errors which can be thrown during validation +var ( + ErrSongMustHaveABarChangeAtFrame0 = errors.New("song needs to have a bar change at frame 0") +) diff --git a/pkg/cntl/song/helper.go b/pkg/cntl/song/helper.go index c83302e..5221cb9 100644 --- a/pkg/cntl/song/helper.go +++ b/pkg/cntl/song/helper.go @@ -2,7 +2,6 @@ package song import ( "errors" - "log" "reflect" "github.com/StageAutoControl/controller/pkg/cntl" @@ -49,15 +48,7 @@ func maxKey(search interface{}) uint64 { return biggest } -func makeCommand() cntl.Command { - return cntl.Command{ - MIDICommands: make([]cntl.MIDICommand, 0), - DMXCommands: make([]cntl.DMXCommand, 0), - } -} - func makeCommandArray(length uint64) []cntl.Command { - log.Print(length) cmds := make([]cntl.Command, length) for i := range cmds { @@ -67,7 +58,8 @@ func makeCommandArray(length uint64) []cntl.Command { return cmds } -func streamlineBarChanges(s *cntl.Song) map[uint64]cntl.BarChange { +// StreamlineBarChanges fills the bar changes of the given song into a map indexed by the frame the BC is at +func StreamlineBarChanges(s *cntl.Song) map[uint64]cntl.BarChange { bcs := make(map[uint64]cntl.BarChange) for _, bc := range s.BarChanges { bcs[bc.At] = bc @@ -76,6 +68,17 @@ func streamlineBarChanges(s *cntl.Song) map[uint64]cntl.BarChange { return bcs } +// Validate the given streamlined map of BarChanges +func ValidateBarChanges(bc map[uint64]cntl.BarChange) error { + if _, ok := bc[0]; !ok { + return ErrSongMustHaveABarChangeAtFrame0 + } + + // @TODO Add validation of bar change distance, so that one can't add a BC if the previous bar isn't finished yet + + return nil +} + // CalcBarLength calculates the length of a bar by given BarChange func CalcBarLength(bc *cntl.BarChange) uint64 { return uint64(bc.NoteCount) * CalcNoteLength(bc) diff --git a/pkg/cntl/song/song.go b/pkg/cntl/song/song.go index deeea7c..926c5a0 100644 --- a/pkg/cntl/song/song.go +++ b/pkg/cntl/song/song.go @@ -13,15 +13,19 @@ import ( func Render(ds *cntl.DataStore, songID string) ([]cntl.Command, error) { s, ok := ds.Songs[songID] if !ok { - return []cntl.Command{}, fmt.Errorf("cannot find Song %q", songID) + return nil, fmt.Errorf("cannot find Song %q", songID) } scs, err := dmx.StreamlineScenes(ds, s) if err != nil { - return []cntl.Command{}, err + return nil, err + } + + bcs := StreamlineBarChanges(s) + if err := ValidateBarChanges(bcs); err != nil { + return nil, fmt.Errorf("failed to validate bar changes: %v", err) } - bcs := streamlineBarChanges(s) mcs := midi.StreamlineMidiCommands(s) fb := &frameBrain{} @@ -44,7 +48,7 @@ func Render(ds *cntl.DataStore, songID string) ([]cntl.Command, error) { for _, sc := range scs { dcs, err := dmx.RenderScene(ds, sc) if err != nil { - return []cntl.Command{}, err + return nil, err } for j, dc := range dcs { diff --git a/pkg/cntl/song/song_test.go b/pkg/cntl/song/song_test.go index bec7c09..bcc05c9 100644 --- a/pkg/cntl/song/song_test.go +++ b/pkg/cntl/song/song_test.go @@ -37,7 +37,7 @@ func TestStreamlineBarChanges(t *testing.T) { } for i, e := range exp { - res := streamlineBarChanges(e.s) + res := StreamlineBarChanges(e.s) for k, v := range e.m { resv, ok := res[k] diff --git a/pkg/cntl/transport/midi.go b/pkg/cntl/transport/midi.go index a6a8274..26c5c1c 100644 --- a/pkg/cntl/transport/midi.go +++ b/pkg/cntl/transport/midi.go @@ -2,8 +2,6 @@ package transport import ( "errors" - "fmt" - "strconv" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/internal/logging" @@ -20,20 +18,16 @@ type MIDI struct { } // NewMIDI creates a new MIDI transport -func NewMIDI(logger logging.Logger, deviceID string) (*MIDI, error) { +func NewMIDI(logger logging.Logger, deviceID int8) (*MIDI, error) { if err := portmidi.Initialize(); err != nil { return nil, err } var d portmidi.DeviceID - if deviceID == "" { + if deviceID < 0 { d = portmidi.DefaultOutputDeviceID() } else { - i, err := strconv.Atoi(deviceID) - if err != nil { - return nil, fmt.Errorf("failed to transform deviceID %q to int: %v", deviceID, err) - } - d = portmidi.DeviceID(i) + d = portmidi.DeviceID(deviceID) } info := portmidi.Info(d) diff --git a/pkg/disk/errors.go b/pkg/disk/errors.go new file mode 100644 index 0000000..e90f855 --- /dev/null +++ b/pkg/disk/errors.go @@ -0,0 +1,8 @@ +package disk + +import "errors" + +// storage errors +var ( + ErrNotExists = errors.New("object does not exist in storage") +) diff --git a/pkg/disk/loader.go b/pkg/disk/loader.go index 258b7eb..837b89e 100644 --- a/pkg/disk/loader.go +++ b/pkg/disk/loader.go @@ -18,8 +18,8 @@ func NewLoader(storage *Storage) *Loader { func (l *Loader) Load() (*cntl.DataStore, error) { data := cntl.NewStore() - setList := &cntl.SetList{} - for _, id := range l.storage.List(setList) { + for _, id := range l.storage.List(&cntl.SetList{}) { + setList := &cntl.SetList{} err := l.storage.Read(id, setList) if err != nil { return nil, err @@ -28,8 +28,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.SetLists[id] = setList } - song := &cntl.Song{} - for _, id := range l.storage.List(song) { + for _, id := range l.storage.List(&cntl.Song{}) { + song := &cntl.Song{} err := l.storage.Read(id, song) if err != nil { return nil, err @@ -38,8 +38,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.Songs[id] = song } - dmxDevice := &cntl.DMXDevice{} - for _, id := range l.storage.List(dmxDevice) { + for _, id := range l.storage.List(&cntl.DMXDevice{}) { + dmxDevice := &cntl.DMXDevice{} err := l.storage.Read(id, dmxDevice) if err != nil { return nil, err @@ -48,8 +48,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXDevices[id] = dmxDevice } - dmxDeviceGroup := &cntl.DMXDeviceGroup{} - for _, id := range l.storage.List(dmxDeviceGroup) { + for _, id := range l.storage.List(&cntl.DMXDeviceGroup{}) { + dmxDeviceGroup := &cntl.DMXDeviceGroup{} err := l.storage.Read(id, dmxDeviceGroup) if err != nil { return nil, err @@ -58,8 +58,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXDeviceGroups[id] = dmxDeviceGroup } - dmxDeviceType := &cntl.DMXDeviceType{} - for _, id := range l.storage.List(dmxDeviceType) { + for _, id := range l.storage.List(&cntl.DMXDeviceType{}) { + dmxDeviceType := &cntl.DMXDeviceType{} err := l.storage.Read(id, dmxDeviceType) if err != nil { return nil, err @@ -68,8 +68,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXDeviceTypes[id] = dmxDeviceType } - dmxPreset := &cntl.DMXPreset{} - for _, id := range l.storage.List(dmxPreset) { + for _, id := range l.storage.List(&cntl.DMXPreset{}) { + dmxPreset := &cntl.DMXPreset{} err := l.storage.Read(id, dmxPreset) if err != nil { return nil, err @@ -78,8 +78,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXPresets[id] = dmxPreset } - dmxScene := &cntl.DMXScene{} - for _, id := range l.storage.List(dmxScene) { + for _, id := range l.storage.List(&cntl.DMXScene{}) { + dmxScene := &cntl.DMXScene{} err := l.storage.Read(id, dmxScene) if err != nil { return nil, err @@ -88,8 +88,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXScenes[id] = dmxScene } - dmxAnimation := &cntl.DMXAnimation{} - for _, id := range l.storage.List(dmxAnimation) { + for _, id := range l.storage.List(&cntl.DMXAnimation{}) { + dmxAnimation := &cntl.DMXAnimation{} err := l.storage.Read(id, dmxAnimation) if err != nil { return nil, err @@ -98,8 +98,8 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXAnimations[id] = dmxAnimation } - dmxTransition := &cntl.DMXTransition{} - for _, id := range l.storage.List(dmxTransition) { + for _, id := range l.storage.List(&cntl.DMXTransition{}) { + dmxTransition := &cntl.DMXTransition{} err := l.storage.Read(id, dmxTransition) if err != nil { return nil, err @@ -108,5 +108,15 @@ func (l *Loader) Load() (*cntl.DataStore, error) { data.DMXTransitions[id] = dmxTransition } + for _, id := range l.storage.List(&cntl.DMXColorVariable{}) { + dmxColorVariable := &cntl.DMXColorVariable{} + err := l.storage.Read(id, dmxColorVariable) + if err != nil { + return nil, err + } + + data.DMXColorVariables[id] = dmxColorVariable + } + return data, nil } diff --git a/pkg/disk/storage.go b/pkg/disk/storage.go index ad82c4d..66b12b3 100644 --- a/pkg/disk/storage.go +++ b/pkg/disk/storage.go @@ -67,6 +67,10 @@ func (s *Storage) Read(key string, value interface{}) error { b, err := s.disk.Read(fileName) if err != nil { + if os.IsNotExist(err) { + return ErrNotExists + } + return fmt.Errorf("failed to read value of type %s from disk: %v", s.getType(value), err) } diff --git a/pkg/process/manager.go b/pkg/process/manager.go index 701c5bc..892058c 100644 --- a/pkg/process/manager.go +++ b/pkg/process/manager.go @@ -16,7 +16,7 @@ type processInfo struct { type manager struct { ctx context.Context logger logging.Logger - processes map[string]processInfo + processes map[string]*processInfo } // NewManager returns a new process manager instance @@ -24,7 +24,7 @@ func NewManager(ctx context.Context, logger logging.Logger) Manager { m := &manager{ ctx: ctx, logger: logger, - processes: make(map[string]processInfo), + processes: make(map[string]*processInfo), } go m.listenExit() @@ -35,8 +35,12 @@ func NewManager(ctx context.Context, logger logging.Logger) Manager { func (m *manager) listenExit() { <-m.ctx.Done() for name := range m.processes { - if _, err := m.Stop(name); err != nil { + if p, _, err := m.GetProcess(name); err != nil { + m.logger.Errorf("failed to find process %q while shutting down: %v", name, err) + + } else if err := p.Stop(); err != nil { m.logger.Errorf("failed to stop process %q: %v", name, err) + } } } @@ -46,7 +50,7 @@ func (m *manager) AddProcess(name string, process Process, verbose bool) error { return errProcessAlreadyExists } - m.processes[name] = processInfo{ + m.processes[name] = &processInfo{ process: process, status: Status{ Name: name, @@ -86,10 +90,13 @@ func (m *manager) Start(name string) (*Status, error) { logger := NewBufferedLogger(&info.status.Logs, info.status.Verbose) info.process.SetLogger(logger) - if err := info.process.Start(m.ctx); err != nil { - info.status.Error = err - return m.Stop(name) - } + go func() { + if err := info.process.Start(m.ctx); err != nil { + info.status.Error = err + info.status.Running = false + m.logger.Warnf("failed to start process %s: %v", name, err) + } + }() return &info.status, nil } diff --git a/pkg/process/types.go b/pkg/process/types.go index 18ce49f..52765f4 100644 --- a/pkg/process/types.go +++ b/pkg/process/types.go @@ -10,8 +10,8 @@ import ( type Status struct { Name string `json:"name"` Running bool `json:"running"` - StartedAt JSONTime `json:"started_at"` - StoppedAt JSONTime `json:"stopped_at"` + StartedAt JSONTime `json:"startedAt"` + StoppedAt JSONTime `json:"stoppedAt"` Error error `json:"error"` Logs []Log `json:"logs"` Verbose bool `json:"verbose"` From 2cb3c1854eb58f330768a342506b1c744f9f837c Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 24 Mar 2019 23:41:20 +0100 Subject: [PATCH 37/94] Remove LED position, use array index instead --- pkg/cntl/dmx/device.go | 36 +++++-------------------------- pkg/cntl/types.go | 9 ++++---- pkg/cntl/types_equals.go | 3 +-- pkg/internal/fixtures/fixtures.go | 34 ++++++++++++++--------------- 4 files changed, 27 insertions(+), 55 deletions(-) diff --git a/pkg/cntl/dmx/device.go b/pkg/cntl/dmx/device.go index 05de99e..a060aff 100644 --- a/pkg/cntl/dmx/device.go +++ b/pkg/cntl/dmx/device.go @@ -16,7 +16,7 @@ func getDeviceChannel(ds *cntl.DataStore, d *cntl.DMXDevice, c cntl.DMXChannel, // slice of LEDs and apply all values to that? ledLen := len(dt.LEDs) - if ledLen > 0 && int(led) >= ledLen { + if int(led) >= ledLen { return 0, fmt.Errorf("given device has insufficient biggest index of LEDs %d to handle the given LED index %d", ledLen-1, led) } @@ -24,32 +24,16 @@ func getDeviceChannel(ds *cntl.DataStore, d *cntl.DMXDevice, c cntl.DMXChannel, switch c { case ChannelRed: - deviceLED := getLED(dt, led) - if deviceLED == nil { - return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) - } - channel = getLED(dt, led).Red + channel = dt.LEDs[led].Red case ChannelGreen: - deviceLED := getLED(dt, led) - if deviceLED == nil { - return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) - } - channel = getLED(dt, led).Green + channel = dt.LEDs[led].Green case ChannelBlue: - deviceLED := getLED(dt, led) - if deviceLED == nil { - return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) - } - channel = getLED(dt, led).Blue + channel = dt.LEDs[led].Blue case ChannelWhite: - deviceLED := getLED(dt, led) - if deviceLED == nil { - return 0, fmt.Errorf("failed to find LED %d for device type %s", led, dt.ID) - } - channel = getLED(dt, led).White + channel = dt.LEDs[led].White case ChannelStrobe: if !dt.StrobeEnabled { @@ -88,16 +72,6 @@ func getDeviceChannel(ds *cntl.DataStore, d *cntl.DMXDevice, c cntl.DMXChannel, return d.StartChannel + channel, nil } -func getLED(dt *cntl.DMXDeviceType, led uint16) *cntl.LED { - for _, l := range dt.LEDs { - if l.Position == led { - return &l - } - } - - return nil -} - // ResolveDeviceSelector returns all DMXDevices that match the given selector func ResolveDeviceSelector(ds *cntl.DataStore, sel *cntl.DMXDeviceSelector) ([]*cntl.DMXDevice, error) { if sel.ID != "" && len(sel.Tags) > 0 { diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 1da3aca..ddccaa4 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -65,11 +65,10 @@ type DMXDeviceType struct { // LED maps a single LEDs DMX channels type LED struct { - Position uint16 `json:"position" yaml:"position"` - Red DMXChannel `json:"red" yaml:"red"` - Green DMXChannel `json:"green" yaml:"green"` - Blue DMXChannel `json:"blue" yaml:"blue"` - White DMXChannel `json:"white" yaml:"white"` + Red DMXChannel `json:"red" yaml:"red"` + Green DMXChannel `json:"green" yaml:"green"` + Blue DMXChannel `json:"blue" yaml:"blue"` + White DMXChannel `json:"white" yaml:"white"` } // DMXDeviceSelector is a selector for DMX devices diff --git a/pkg/cntl/types_equals.go b/pkg/cntl/types_equals.go index 5ed4c99..f245ab8 100644 --- a/pkg/cntl/types_equals.go +++ b/pkg/cntl/types_equals.go @@ -64,8 +64,7 @@ func (v1 *DMXDeviceType) Equals(v2 *DMXDeviceType) bool { // Equals returns whether the two given objects are equal func (v1 LED) Equals(v2 LED) bool { - return v1.Position == v2.Position && - v1.Red == v2.Red && + return v1.Red == v2.Red && v1.Green == v2.Green && v1.Blue == v2.Blue && v1.White == v2.White diff --git a/pkg/internal/fixtures/fixtures.go b/pkg/internal/fixtures/fixtures.go index 429aa3b..da70276 100644 --- a/pkg/internal/fixtures/fixtures.go +++ b/pkg/internal/fixtures/fixtures.go @@ -221,22 +221,22 @@ var data = &cntl.DataStore{ StrobeEnabled: true, StrobeChannel: 2, LEDs: []cntl.LED{ - {Position: 0, Red: 0, Green: 1, Blue: 2, White: 3}, - {Position: 1, Red: 4, Green: 5, Blue: 6, White: 7}, - {Position: 2, Red: 8, Green: 9, Blue: 10, White: 11}, - {Position: 3, Red: 12, Green: 13, Blue: 14, White: 15}, - {Position: 4, Red: 16, Green: 17, Blue: 18, White: 19}, - {Position: 5, Red: 20, Green: 21, Blue: 22, White: 23}, - {Position: 6, Red: 24, Green: 25, Blue: 26, White: 27}, - {Position: 7, Red: 28, Green: 29, Blue: 30, White: 31}, - {Position: 8, Red: 32, Green: 33, Blue: 34, White: 35}, - {Position: 9, Red: 36, Green: 37, Blue: 38, White: 39}, - {Position: 10, Red: 40, Green: 41, Blue: 42, White: 43}, - {Position: 11, Red: 44, Green: 45, Blue: 46, White: 47}, - {Position: 12, Red: 48, Green: 49, Blue: 50, White: 51}, - {Position: 13, Red: 52, Green: 53, Blue: 54, White: 55}, - {Position: 14, Red: 56, Green: 57, Blue: 58, White: 59}, - {Position: 15, Red: 60, Green: 61, Blue: 62, White: 63}, + {Red: 0, Green: 1, Blue: 2, White: 3}, + {Red: 4, Green: 5, Blue: 6, White: 7}, + {Red: 8, Green: 9, Blue: 10, White: 11}, + {Red: 12, Green: 13, Blue: 14, White: 15}, + {Red: 16, Green: 17, Blue: 18, White: 19}, + {Red: 20, Green: 21, Blue: 22, White: 23}, + {Red: 24, Green: 25, Blue: 26, White: 27}, + {Red: 28, Green: 29, Blue: 30, White: 31}, + {Red: 32, Green: 33, Blue: 34, White: 35}, + {Red: 36, Green: 37, Blue: 38, White: 39}, + {Red: 40, Green: 41, Blue: 42, White: 43}, + {Red: 44, Green: 45, Blue: 46, White: 47}, + {Red: 48, Green: 49, Blue: 50, White: 51}, + {Red: 52, Green: 53, Blue: 54, White: 55}, + {Red: 56, Green: 57, Blue: 58, White: 59}, + {Red: 60, Green: 61, Blue: 62, White: 63}, }, }, "628fc3ea-1188-11e7-8824-5f72d80c17b6": { @@ -251,7 +251,7 @@ var data = &cntl.DataStore{ StrobeEnabled: true, StrobeChannel: 4, LEDs: []cntl.LED{ - {Position: 0, Red: 0, Green: 1, Blue: 2}, + {Red: 0, Green: 1, Blue: 2}, }, }, "5ccc43ee-118c-11e7-8d53-974b41748b71": { From af5f9e5193466e9763a5355f7653bd8ca7243682 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 25 Mar 2019 01:31:09 +0100 Subject: [PATCH 38/94] Remove DMXDeviceParams on a Scene --- pkg/api/playback/playback.go | 10 +++++++++- pkg/cntl/types.go | 11 +++++------ pkg/cntl/types_equals.go | 3 +-- pkg/process/manager.go | 8 ++++---- pkg/process/time.go | 12 ------------ pkg/process/types.go | 14 +++++++------- 6 files changed, 26 insertions(+), 32 deletions(-) diff --git a/pkg/api/playback/playback.go b/pkg/api/playback/playback.go index 196d027..0b019e5 100644 --- a/pkg/api/playback/playback.go +++ b/pkg/api/playback/playback.go @@ -56,11 +56,19 @@ func (c *Controller) Start(r *http.Request, req *playback.Params, res *Status) e // Stop a playback func (c *Controller) Stop(r *http.Request, req *api.IDBody, res *Status) error { - _, err := c.pm.Stop(playback.ProcessName) + p, s, err := c.pm.GetProcess(playback.ProcessName) + if err != nil { + return fmt.Errorf("failed to get playback status: %v", err) + } + + s, err = c.pm.Stop(playback.ProcessName) if err != nil { return fmt.Errorf("failed to stop playback: %v", err) } + res.Process = *s + res.Params = p.(*playback.Process).GetParams() + return err } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index ddccaa4..c2e802e 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -24,12 +24,11 @@ type DMXScenePosition struct { // Song is the whole container for everything that needs to be controlled during a song. type Song struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - BarChanges []BarChange `json:"barChanges" yaml:"barChanges"` - DMXScenes []DMXScenePosition `json:"dmxScenes" yaml:"dmxScenes"` - DMXDeviceParams []DMXDeviceParams `json:"dmxDeviceParams" yaml:"dmxDeviceParams"` - MIDICommands []MIDICommand `json:"midiCommands" yaml:"midiCommands"` + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + BarChanges []BarChange `json:"barChanges" yaml:"barChanges"` + DMXScenes []DMXScenePosition `json:"dmxScenes" yaml:"dmxScenes"` + MIDICommands []MIDICommand `json:"midiCommands" yaml:"midiCommands"` } // Tag is a string literal tagging a DMX device diff --git a/pkg/cntl/types_equals.go b/pkg/cntl/types_equals.go index f245ab8..0884006 100644 --- a/pkg/cntl/types_equals.go +++ b/pkg/cntl/types_equals.go @@ -27,8 +27,7 @@ func (v1 *Song) Equals(v2 *Song) bool { return v1.ID == v2.ID && v1.Name == v2.Name && barChangeList(v1.BarChanges).Equals(barChangeList(v2.BarChanges)) && - scenePositionList(v1.DMXScenes).Equals(scenePositionList(v2.DMXScenes)) && - dmxDeviceParamsList(v1.DMXDeviceParams).Equals(dmxDeviceParamsList(v2.DMXDeviceParams)) + scenePositionList(v1.DMXScenes).Equals(scenePositionList(v2.DMXScenes)) } // Equals returns whether the two given objects are equal diff --git a/pkg/process/manager.go b/pkg/process/manager.go index 892058c..01738e2 100644 --- a/pkg/process/manager.go +++ b/pkg/process/manager.go @@ -83,8 +83,8 @@ func (m *manager) Start(name string) (*Status, error) { info.status.Running = true info.status.Error = nil - info.status.StartedAt = JSONTime{Time: time.Now()} - info.status.StoppedAt = JSONTime{} + info.status.StartedAt = &JSONTime{Time: time.Now()} + info.status.StoppedAt = nil info.status.Logs = make([]Log, 0) logger := NewBufferedLogger(&info.status.Logs, info.status.Verbose) @@ -94,7 +94,7 @@ func (m *manager) Start(name string) (*Status, error) { if err := info.process.Start(m.ctx); err != nil { info.status.Error = err info.status.Running = false - m.logger.Warnf("failed to start process %s: %v", name, err) + m.logger.Errorf("failed to start process %s: %v", name, err) } }() @@ -117,7 +117,7 @@ func (m *manager) Stop(name string) (*Status, error) { } p.status.Running = false - p.status.StoppedAt = JSONTime{Time: time.Now()} + p.status.StoppedAt = &JSONTime{Time: time.Now()} return &p.status, nil } diff --git a/pkg/process/time.go b/pkg/process/time.go index 6c40e99..fcd14f3 100644 --- a/pkg/process/time.go +++ b/pkg/process/time.go @@ -20,15 +20,3 @@ func (t JSONTime) MarshalJSON() ([]byte, error) { date := fmt.Sprintf("%q", t.String()) return []byte(date), nil } - -func datesAreEqual(t1 *JSONTime, t2 *JSONTime) bool { - if (t1 == nil && t2 != nil) || (t1 != nil && t2 == nil) { - return false - } - - if t1 == nil && t2 == nil { - return true - } - - return (*t1).Equal(t2.Time) -} diff --git a/pkg/process/types.go b/pkg/process/types.go index 52765f4..afc09c2 100644 --- a/pkg/process/types.go +++ b/pkg/process/types.go @@ -8,13 +8,13 @@ import ( // Status of a process as handled by the manager type Status struct { - Name string `json:"name"` - Running bool `json:"running"` - StartedAt JSONTime `json:"startedAt"` - StoppedAt JSONTime `json:"stoppedAt"` - Error error `json:"error"` - Logs []Log `json:"logs"` - Verbose bool `json:"verbose"` + Name string `json:"name"` + Running bool `json:"running"` + StartedAt *JSONTime `json:"startedAt"` + StoppedAt *JSONTime `json:"stoppedAt"` + Error error `json:"error"` + Logs []Log `json:"logs"` + Verbose bool `json:"verbose"` } // Log represents a log line printed by the process From 85a88bf64da5df176d4ea49cdf7d97c0fb839778 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 25 Mar 2019 01:44:14 +0100 Subject: [PATCH 39/94] Fix context cancelling in playback process --- pkg/cntl/playback/player.go | 3 ++- pkg/cntl/playback/process.go | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index 71a4227..4c73d6b 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -113,12 +113,13 @@ func (p *Player) PlaySong(ctx context.Context, songID string) error { t := time.NewTicker(1 * time.Nanosecond) p.logger.Infof("Playing song %s", songID) + done := ctx.Done() var i int var cmd cntl.Command for { select { - case <-ctx.Done(): + case <-done: return ErrCancelled case <-t.C: diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index 9b2cd2d..6b3bfcc 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -18,7 +18,6 @@ type Process struct { params Params controller artnet.Controller player *Player - ctx context.Context cancel context.CancelFunc } @@ -60,14 +59,14 @@ func (p *Process) Start(ctx context.Context) error { cfg, err := p.parseConfig(config) p.player = NewPlayer(p.logger, ds, cfg.writers, cfg.waiters) - p.ctx, p.cancel = context.WithCancel(ctx) + ctx, p.cancel = context.WithCancel(ctx) if p.params.SetList.ID != "" { - if err := p.player.PlaySetList(ctx, p.params.SetList.ID); err != nil { + if err := p.player.PlaySetList(ctx, p.params.SetList.ID); err != nil && err != ErrCancelled { return fmt.Errorf("failed to start setlist playbaack: %v", err) } } else if p.params.Song.ID != "" { - if err := p.player.PlaySong(ctx, p.params.Song.ID); err != nil { + if err := p.player.PlaySong(ctx, p.params.Song.ID); err != nil && err != ErrCancelled { return fmt.Errorf("failed to start song playback: %v", err) } } else { @@ -125,7 +124,6 @@ func (p *Process) Stop() error { p.cancel() } p.player = nil - p.ctx = nil return nil } From 2c0beff3cf0abe3f9aa2454b2a4f3dcbb0842507 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 5 Apr 2019 17:33:21 +0200 Subject: [PATCH 40/94] Readd audio commands --- cmd/audio/audio.go | 20 ++++++++ cmd/audio/const.go | 3 ++ cmd/audio/dump_input.go | 103 ++++++++++++++++++++++++++++++++++++++ cmd/audio/list_devices.go | 68 +++++++++++++++++++++++++ cmd/audio/sine.go | 69 +++++++++++++++++++++++++ main.go | 1 + 6 files changed, 264 insertions(+) create mode 100755 cmd/audio/audio.go create mode 100755 cmd/audio/const.go create mode 100755 cmd/audio/dump_input.go create mode 100755 cmd/audio/list_devices.go create mode 100755 cmd/audio/sine.go diff --git a/cmd/audio/audio.go b/cmd/audio/audio.go new file mode 100755 index 0000000..1ae6607 --- /dev/null +++ b/cmd/audio/audio.go @@ -0,0 +1,20 @@ +// Copyright © 2017 Alexander Pinnecke +// + +package audio + +import ( + "github.com/StageAutoControl/controller/cmd" + "github.com/spf13/cobra" +) + +// AudioCmd represents the audio/audio command +var AudioCmd = &cobra.Command{ + Use: "audio", + Short: "A brief description of your command", + Long: ``, +} + +func init() { + cmd.RootCmd.AddCommand(AudioCmd) +} diff --git a/cmd/audio/const.go b/cmd/audio/const.go new file mode 100755 index 0000000..227d0da --- /dev/null +++ b/cmd/audio/const.go @@ -0,0 +1,3 @@ +package audio + +const sampleRate = 44100 diff --git a/cmd/audio/dump_input.go b/cmd/audio/dump_input.go new file mode 100755 index 0000000..f3908a2 --- /dev/null +++ b/cmd/audio/dump_input.go @@ -0,0 +1,103 @@ +// Copyright © 2017 Alexander Pinnecke +// + +package audio + +import ( + "fmt" + "os" + + "os/signal" + + "github.com/gordonklaus/portaudio" + "github.com/spf13/cobra" +) + +var ( + averageSamples int + threshold float32 +) + +// DumpInputCmd represents the DumpInputs command +var DumpInputCmd = &cobra.Command{ + Use: "dump-input", + Short: "Dumps the audio input of a device to console", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + if err := portaudio.Initialize(); err != nil { + panic(err) + } + defer portaudio.Terminate() + + buf := make([]float32, averageSamples) + s, err := portaudio.OpenDefaultStream(1, 0, sampleRate, len(buf), buf) + if err != nil { + panic(err) + } + defer s.Close() + + if err := s.Start(); err != nil { + panic(err) + } + defer s.Stop() + + var frame int64 + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, os.Kill) + + fmt.Println("Started listener, dumping input.") + + for { + if err := s.Read(); err != nil { + panic(err) + } + + go calcAverage(frame, buf) + frame++ + + select { + case <-c: + fmt.Println("cancelled.") + return + default: + } + } + }, +} + +func init() { + AudioCmd.AddCommand(DumpInputCmd) + + DumpInputCmd.PersistentFlags().IntVarP(&averageSamples, "average-samples", "a", 1000, "How many samples to calc the average from") + DumpInputCmd.PersistentFlags().Float32VarP(&threshold, "threshold", "t", 0.8, "Threshold of tick") + +} + +func calcAverage(frame int64, buf []float32) { + var avg, min, max, sum float32 + for _, s := range buf { + sum += s + + if min == 0 || s < min { + min = s + } else if min == 0 || s > max { + max = s + } + } + + tick := "" + if min < (threshold*-1) || max > threshold { + tick = "tick" + } + + avg = sum / float32(len(buf)) + + go fmt.Printf( + "%10d %14s %14s %14s %10s\n", + frame, + fmt.Sprintf("%5.10f", avg), + fmt.Sprintf("%5.10f", min), + fmt.Sprintf("%5.10f", max), + tick, + ) +} diff --git a/cmd/audio/list_devices.go b/cmd/audio/list_devices.go new file mode 100755 index 0000000..05820e9 --- /dev/null +++ b/cmd/audio/list_devices.go @@ -0,0 +1,68 @@ +// Copyright © 2017 Alexander Pinnecke +// + +package audio + +import ( + "fmt" + "os" + + "github.com/gordonklaus/portaudio" + "github.com/spf13/cobra" +) + +// DeviceCmd represents the Devices command +var DeviceCmd = &cobra.Command{ + Use: "devices", + Short: "Prints info about all devices", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + if err := portaudio.Initialize(); err != nil { + fmt.Println(err) + os.Exit(1) + } + defer portaudio.Terminate() + + devices, err := portaudio.Devices() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Printf("Found %d devices. \n", len(devices)) + if len(devices) == 0 { + return + } + + fmt.Println("\n\nID Name Input Output SampleRate") + + for i, device := range devices { + fmt.Printf("%v ", i) + printDevice(device) + } + + fmt.Println("\n\nDefault input device: ") + defaultInputDevice, err := portaudio.DefaultInputDevice() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + printDevice(defaultInputDevice) + + fmt.Println("\n\nDefault output device: ") + defaultOutputDevice, err := portaudio.DefaultOutputDevice() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + printDevice(defaultOutputDevice) + }, +} + +func printDevice(info *portaudio.DeviceInfo) { + fmt.Printf("%v %v %v %v\n", info.Name, info.MaxInputChannels, info.MaxOutputChannels, info.DefaultSampleRate) +} + +func init() { + AudioCmd.AddCommand(DeviceCmd) +} diff --git a/cmd/audio/sine.go b/cmd/audio/sine.go new file mode 100755 index 0000000..f6ae357 --- /dev/null +++ b/cmd/audio/sine.go @@ -0,0 +1,69 @@ +// Copyright © 2017 Alexander Pinnecke +// + +package audio + +import ( + "math" + "time" + + "github.com/gordonklaus/portaudio" + "github.com/spf13/cobra" +) + +var ( + frequency int + length int +) + +// SineCmd represents the Sines command +var SineCmd = &cobra.Command{ + Use: "sine", + Short: "Creates a sin curved audio", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + portaudio.Initialize() + defer portaudio.Terminate() + + s := newStereoSine(float64(frequency), sampleRate) + defer s.Close() + + if err := s.Start(); err != nil { + panic(err) + } + defer s.Stop() + + time.Sleep(time.Duration(length) * time.Millisecond) + }, +} + +func init() { + AudioCmd.AddCommand(SineCmd) + + SineCmd.PersistentFlags().IntVarP(&frequency, "frequency", "f", 18000, "Frequency of the sin") + SineCmd.PersistentFlags().IntVarP(&length, "length", "l", 100, "length of the sin in milliseconds") +} + +type stereoSine struct { + *portaudio.Stream + step, phase float64 +} + +func newStereoSine(freq, sampleRate float64) *stereoSine { + s := &stereoSine{nil, freq / sampleRate, 0} + + var err error + s.Stream, err = portaudio.OpenDefaultStream(0, 1, sampleRate, 0, s.processAudio) + if err != nil { + panic(err) + } + + return s +} + +func (g *stereoSine) processAudio(out [][]float32) { + for i := range out[0] { + out[0][i] = float32(math.Sin(2 * math.Pi * g.phase)) + _, g.phase = math.Modf(g.phase + g.step) + } +} diff --git a/main.go b/main.go index b89eafc..55d47b1 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ package main import ( "github.com/StageAutoControl/controller/cmd" + _ "github.com/StageAutoControl/controller/cmd/audio" _ "github.com/StageAutoControl/controller/cmd/midi" ) From be2e799fcf2068c4c8266861117511df065789fb Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 5 Apr 2019 18:07:26 +0200 Subject: [PATCH 41/94] Fix transition rendering with colorVar --- pkg/cntl/dmx/transition.go | 8 ++++++++ pkg/cntl/playback/const.go | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/cntl/dmx/transition.go b/pkg/cntl/dmx/transition.go index c601ab9..8d8ac39 100644 --- a/pkg/cntl/dmx/transition.go +++ b/pkg/cntl/dmx/transition.go @@ -35,6 +35,14 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM return []cntl.DMXCommands{}, err } + if err := resolveColorVar(ds, &p.From); err != nil { + return []cntl.DMXCommands{}, err + } + + if err := resolveColorVar(ds, &p.To); err != nil { + return []cntl.DMXCommands{}, err + } + if p.From.Red != nil && p.To.Red != nil && p.From.Red.Value != p.To.Red.Value { steps, err := calcTransitionSteps(p.From.Red.Value, p.To.Red.Value, t.Length, ease) if err != nil { diff --git a/pkg/cntl/playback/const.go b/pkg/cntl/playback/const.go index 0766ac6..b4871bb 100644 --- a/pkg/cntl/playback/const.go +++ b/pkg/cntl/playback/const.go @@ -18,7 +18,8 @@ var defaultConfig = ` { "waiters": { "audio": { - "enabled": true + "enabled": true, + "threshold": 0.8 } }, "transportWriters": { From b9127b2fbaf6a1690ba2046acf4fa6387a27b116 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 9 Apr 2019 20:06:22 +0200 Subject: [PATCH 42/94] Add the ability to play a single scene or preset --- cmd/playback.go | 3 - cmd/root.go | 2 + cmd/server.go | 2 +- pkg/api/playback/playback.go | 1 + .../playground/dmx_playground_controller.go | 81 ++++++++++++++++++- pkg/api/server/server.go | 6 +- pkg/api/types.go | 11 ++- pkg/artnet/controller.go | 20 ++++- pkg/artnet/types.go | 7 +- pkg/cntl/playback/player.go | 43 +++++++--- pkg/cntl/playback/player_test.go | 12 +-- pkg/cntl/song/helper.go | 2 +- pkg/cntl/song/helper_test.go | 10 +-- pkg/cntl/song/song_test.go | 8 +- pkg/cntl/types.go | 11 ++- pkg/internal/fixtures/fixtures.go | 8 +- 16 files changed, 184 insertions(+), 43 deletions(-) diff --git a/cmd/playback.go b/cmd/playback.go index f52138e..73539c7 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -11,7 +11,6 @@ import ( "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/cntl/transport" "github.com/StageAutoControl/controller/pkg/cntl/waiter" - "github.com/StageAutoControl/controller/pkg/disk" "github.com/apinnecke/go-exitcontext" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -57,8 +56,6 @@ var playbackCmd = &cobra.Command{ os.Exit(1) } - store := disk.New(storagePath) - loader := disk.NewLoader(store) data, err := loader.Load() if err != nil { logrus.Fatal(err) diff --git a/cmd/root.go b/cmd/root.go index 965bbd2..17effbf 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,6 +17,7 @@ var ( logger *logrus.Entry storagePath string storage *disk.Storage + loader *disk.Loader controller artnet.Controller disableController bool ) @@ -30,6 +31,7 @@ var RootCmd = &cobra.Command{ logger = createLogger(logLevel) storage = createStorage(logger, storagePath) controller = createController(logger, disableController) + loader = disk.NewLoader(storage) }, } diff --git a/cmd/server.go b/cmd/server.go index 9ec4d2a..ed3fad2 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -18,7 +18,7 @@ var serverCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := exitcontext.New() pm := process.NewManager(ctx, logger) - server, err := server.New(logger.WithField("module", "api"), storage, controller, pm) + server, err := server.New(logger.WithField("module", "api"), storage, loader, controller, pm) if err != nil { logger.Fatal(err) } diff --git a/pkg/api/playback/playback.go b/pkg/api/playback/playback.go index 0b019e5..def7262 100644 --- a/pkg/api/playback/playback.go +++ b/pkg/api/playback/playback.go @@ -26,6 +26,7 @@ func NewController(pm process.Manager) *Controller { } } +// Status Response of the playback process type Status struct { Process process.Status `json:"process"` Params playback.Params `json:"params"` diff --git a/pkg/api/playground/dmx_playground_controller.go b/pkg/api/playground/dmx_playground_controller.go index 1e3a328..3c97076 100644 --- a/pkg/api/playground/dmx_playground_controller.go +++ b/pkg/api/playground/dmx_playground_controller.go @@ -1,11 +1,16 @@ package playground import ( + "context" "errors" + "fmt" "net/http" "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/cntl/dmx" + "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/internal/logging" ) @@ -17,11 +22,13 @@ var ( type DMXPlaygroundController struct { logger logging.Logger controller artnet.Controller + loader api.Loader } // NewDMXPlaygroundController returns a new DMXPlaygroundController instance -func NewDMXPlaygroundController(logger logging.Logger, controller artnet.Controller) *DMXPlaygroundController { +func NewDMXPlaygroundController(logger logging.Logger, controller artnet.Controller, loader api.Loader) *DMXPlaygroundController { return &DMXPlaygroundController{ + loader: loader, logger: logger, controller: controller, } @@ -46,3 +53,75 @@ func (c *DMXPlaygroundController) SetChannelValues(r *http.Request, values *[]ar c.controller.SetDMXChannelValues(*values) return nil } + +// PlayOnceRequest is a request body to play a single entity (Scene, Preset) once +type PlayOnceRequest struct { + api.IDBody + cntl.BarParams +} + +func (c *DMXPlaygroundController) defaultBarParams(bp *cntl.BarParams) { + if bp.Speed == 0 { + bp.Speed = 140 + } + + if bp.NoteCount == 0 { + bp.NoteCount = 4 + } + + if bp.NoteValue == 0 { + bp.NoteValue = 4 + } +} + +// PlayScene plays the given Scene once +func (c *DMXPlaygroundController) PlayScene(r *http.Request, req *PlayOnceRequest, response *api.Empty) error { + ds, err := c.loader.Load() + if err != nil { + return err + } + + scene, ok := ds.DMXScenes[req.ID] + if !ok { + return fmt.Errorf("failed to find scene with id %s", req.ID) + } + + dmxCommands, err := dmx.RenderScene(ds, scene) + if err != nil { + return fmt.Errorf("failed to render scene %s: %v", req.ID, err) + } + + c.defaultBarParams(&req.BarParams) + commands := playback.ToPlayable(req.BarParams, dmxCommands) + if err := playback.Play(context.Background(), c.logger, []playback.TransportWriter{c.controller}, commands); err != nil { + + } + + return nil +} + +// PlayPreset plays the given Preset once +func (c *DMXPlaygroundController) PlayPreset(r *http.Request, req *PlayOnceRequest, response *api.Empty) error { + ds, err := c.loader.Load() + if err != nil { + return err + } + + preset, ok := ds.DMXPresets[req.ID] + if !ok { + return fmt.Errorf("failed to find preset with id %s", req.ID) + } + + dmxCommands, err := dmx.RenderPreset(ds, preset) + if err != nil { + return fmt.Errorf("failed to render preset %s: %v", req.ID, err) + } + + c.defaultBarParams(&req.BarParams) + commands := playback.ToPlayable(req.BarParams, dmxCommands) + if err := playback.Play(context.Background(), c.logger, []playback.TransportWriter{c.controller}, commands); err != nil { + + } + + return nil +} diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 7ec6c74..b8021bd 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -22,17 +22,19 @@ type Server struct { *rpc.Server logger *logrus.Entry storage api.Storage + loader api.Loader apiController map[string]interface{} cntl artnet.Controller pm process.Manager } // New returns a new Server instance -func New(logger *logrus.Entry, storage api.Storage, cntl artnet.Controller, pm process.Manager) (*Server, error) { +func New(logger *logrus.Entry, storage api.Storage, loader api.Loader, cntl artnet.Controller, pm process.Manager) (*Server, error) { server := &Server{ Server: rpc.NewServer(), logger: logger, storage: storage, + loader: loader, cntl: cntl, pm: pm, } @@ -56,7 +58,7 @@ func (s *Server) registerControllers() error { "DMXColorVariable": datastore.NewDMXColorVariableController(s.logger, s.storage), "Song": datastore.NewSongController(s.logger, s.storage), "SetList": datastore.NewSetListController(s.logger, s.storage), - "DMXPlayground": playground.NewDMXPlaygroundController(s.logger, s.cntl), + "DMXPlayground": playground.NewDMXPlaygroundController(s.logger, s.cntl, s.loader), "Playback": playback.NewController(s.pm), } diff --git a/pkg/api/types.go b/pkg/api/types.go index 2a202c0..05cfb9b 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1,6 +1,10 @@ package api -import "errors" +import ( + "errors" + + "github.com/StageAutoControl/controller/pkg/cntl" +) var ( // RPCPath to where the RPC server should listen on @@ -23,6 +27,11 @@ type Storage interface { Delete(key string, kind interface{}) error } +// Loader interface for abstraction in api usage +type Loader interface { + Load() (*cntl.DataStore, error) +} + // IDBody is a request object only storing an ID type IDBody struct { ID string `json:"id"` diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index 3f27203..5cfb4dc 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -8,9 +8,11 @@ import ( "strings" "time" - "github.com/StageAutoControl/controller/pkg/internal/logging" "github.com/jsimonetti/go-artnet" "github.com/sirupsen/logrus" + + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/logging" ) // Controller is a transport for the ArtNet protocol (DMX over UDP/IP) @@ -83,6 +85,22 @@ func (c *controller) SetDMXChannelValues(values []ChannelValue) { c.triggerSend() } +// Write implements the playback.TransportWriter interface to compatibility :) +func (c *controller) Write(cmd cntl.Command) error { + values := make([]ChannelValue, 0) + for _, dmxCmd := range cmd.DMXCommands { + values = append(values, ChannelValue{ + Universe: uint16(dmxCmd.Universe), + Channel: uint8(dmxCmd.Channel), + Value: dmxCmd.Value.Uint8(), + }) + } + + c.SetDMXChannelValues(values) + + return nil +} + func (c *controller) triggerSend() { c.sendTrigger <- struct{}{} } diff --git a/pkg/artnet/types.go b/pkg/artnet/types.go index 880afab..ac5f8d6 100644 --- a/pkg/artnet/types.go +++ b/pkg/artnet/types.go @@ -1,6 +1,10 @@ package artnet -import "github.com/jsimonetti/go-artnet" +import ( + "github.com/jsimonetti/go-artnet" + + "github.com/StageAutoControl/controller/pkg/cntl" +) // Sender is an artnet controller abstraction of the base implementation of jsimonetti type Sender interface { @@ -17,6 +21,7 @@ type ChannelValue struct { // Controller is a convenience interface to use within this application type Controller interface { + Write(cntl.Command) error SetDMXChannelValue(value ChannelValue) SetDMXChannelValues(values []ChannelValue) Start() error diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index 4c73d6b..b88bcfa 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -99,7 +99,7 @@ func (p *Player) wait(ctx context.Context) error { // PlaySong plays a full song func (p *Player) PlaySong(ctx context.Context, songID string) error { - cmds, err := song.Render(p.dataStore, songID) + commands, err := song.Render(p.dataStore, songID) if err != nil { return err } @@ -109,10 +109,19 @@ func (p *Player) PlaySong(ctx context.Context, songID string) error { return err } - l := len(cmds) - t := time.NewTicker(1 * time.Nanosecond) - p.logger.Infof("Playing song %s", songID) + return Play(ctx, p.logger, p.writers, commands) +} + +// CalcRenderSpeed calculates the render speed of a BarChange to a time.Duration +func CalcRenderSpeed(bc *cntl.BarChange) time.Duration { + return time.Minute / time.Duration(bc.Speed*uint16(bc.NoteValue)/4) / time.Duration(cntl.RenderFrames/bc.NoteValue) +} + +// Play plays a given slice of commands and send it to the given writers +func Play(ctx context.Context, logger logging.Logger, writers []TransportWriter, commands []cntl.Command) error { + l := len(commands) + t := time.NewTicker(1 * time.Nanosecond) done := ctx.Done() var i int @@ -128,16 +137,16 @@ func (p *Player) PlaySong(ctx context.Context, songID string) error { return nil } - cmd = cmds[i] + cmd = commands[i] if cmd.BarChange != nil { t.Stop() t = time.NewTicker(CalcRenderSpeed(cmd.BarChange)) } - for _, w := range p.writers { + for _, w := range writers { go func() { if err := w.Write(cmd); err != nil { - p.logger.Error(err) + logger.Error(err) } }() } @@ -147,7 +156,21 @@ func (p *Player) PlaySong(ctx context.Context, songID string) error { } } -// CalcRenderSpeed calculates the render speed of a BarChange to a time.Duration -func CalcRenderSpeed(bc *cntl.BarChange) time.Duration { - return time.Minute / time.Duration(bc.Speed*uint16(bc.NoteValue)/4) / time.Duration(cntl.RenderFrames/bc.NoteValue) +// ToPlayable takes a slice of DMXCommands and combines it with the given BarParams to a playable slice of Commands +func ToPlayable(bp cntl.BarParams, dmxCommands []cntl.DMXCommands) []cntl.Command { + commands := make([]cntl.Command, len(dmxCommands)) + for i, cmd := range dmxCommands { + commands[i] = cntl.Command{ + + DMXCommands: cmd, + MIDICommands: []cntl.MIDICommand{}, + } + } + + commands[0].BarChange = &cntl.BarChange{ + At: 0, + BarParams: bp, + } + + return commands } diff --git a/pkg/cntl/playback/player_test.go b/pkg/cntl/playback/player_test.go index 3348288..6deaa46 100644 --- a/pkg/cntl/playback/player_test.go +++ b/pkg/cntl/playback/player_test.go @@ -12,13 +12,13 @@ func TestCalcRenderSpeed(t *testing.T) { bc cntl.BarChange speed time.Duration }{ - {cntl.BarChange{Speed: 120, NoteValue: 4}, time.Minute / 1920}, - {cntl.BarChange{Speed: 120, NoteValue: 8}, time.Minute / 1920}, - {cntl.BarChange{Speed: 120, NoteValue: 16}, time.Minute / 1920}, - {cntl.BarChange{Speed: 120, NoteValue: 32}, time.Minute / 1920}, + {cntl.BarChange{BarParams: cntl.BarParams{Speed: 120, NoteValue: 4}}, time.Minute / 1920}, + {cntl.BarChange{BarParams: cntl.BarParams{Speed: 120, NoteValue: 8}}, time.Minute / 1920}, + {cntl.BarChange{BarParams: cntl.BarParams{Speed: 120, NoteValue: 16}}, time.Minute / 1920}, + {cntl.BarChange{BarParams: cntl.BarParams{Speed: 120, NoteValue: 32}}, time.Minute / 1920}, - {cntl.BarChange{Speed: 60, NoteValue: 4}, time.Minute / 960}, - {cntl.BarChange{Speed: 60, NoteValue: 8}, time.Minute / 960}, + {cntl.BarChange{BarParams: cntl.BarParams{Speed: 60, NoteValue: 4}}, time.Minute / 960}, + {cntl.BarChange{BarParams: cntl.BarParams{Speed: 60, NoteValue: 8}}, time.Minute / 960}, } for i, e := range exp { diff --git a/pkg/cntl/song/helper.go b/pkg/cntl/song/helper.go index 5221cb9..f68a95a 100644 --- a/pkg/cntl/song/helper.go +++ b/pkg/cntl/song/helper.go @@ -68,7 +68,7 @@ func StreamlineBarChanges(s *cntl.Song) map[uint64]cntl.BarChange { return bcs } -// Validate the given streamlined map of BarChanges +// ValidateBarChanges the given streamlined map of BarChanges func ValidateBarChanges(bc map[uint64]cntl.BarChange) error { if _, ok := bc[0]; !ok { return ErrSongMustHaveABarChangeAtFrame0 diff --git a/pkg/cntl/song/helper_test.go b/pkg/cntl/song/helper_test.go index 16ebc9a..7fa4ad7 100644 --- a/pkg/cntl/song/helper_test.go +++ b/pkg/cntl/song/helper_test.go @@ -11,11 +11,11 @@ func TestCalcBarLength(t *testing.T) { bc cntl.BarChange length uint64 }{ - {cntl.BarChange{At: 0, NoteCount: 3, NoteValue: 4}, 48}, - {cntl.BarChange{At: 63, NoteCount: 12, NoteValue: 8}, 96}, - {cntl.BarChange{At: 10, NoteCount: 11, NoteValue: 4}, 176}, - {cntl.BarChange{At: 104, NoteCount: 4, NoteValue: 4}, 64}, - {cntl.BarChange{At: 5, NoteCount: 9, NoteValue: 8}, 72}, + {cntl.BarChange{At: 0, BarParams: cntl.BarParams{NoteCount: 3, NoteValue: 4}}, 48}, + {cntl.BarChange{At: 63, BarParams: cntl.BarParams{NoteCount: 12, NoteValue: 8}}, 96}, + {cntl.BarChange{At: 10, BarParams: cntl.BarParams{NoteCount: 11, NoteValue: 4}}, 176}, + {cntl.BarChange{At: 104, BarParams: cntl.BarParams{NoteCount: 4, NoteValue: 4}}, 64}, + {cntl.BarChange{At: 5, BarParams: cntl.BarParams{NoteCount: 9, NoteValue: 8}}, 72}, } for i, e := range exp { diff --git a/pkg/cntl/song/song_test.go b/pkg/cntl/song/song_test.go index bcc05c9..0051849 100644 --- a/pkg/cntl/song/song_test.go +++ b/pkg/cntl/song/song_test.go @@ -28,10 +28,10 @@ func TestStreamlineBarChanges(t *testing.T) { { s: ds.Songs["3c1065c8-0b14-11e7-96eb-5b134621c411"], m: map[uint64]cntl.BarChange{ - 0: {At: 0, NoteCount: 4, NoteValue: 4, Speed: 160}, - 512: {At: 512, NoteCount: 3, NoteValue: 4}, - 1184: {At: 1184, NoteCount: 7, NoteValue: 8}, - 1632: {At: 1632, NoteCount: 4, NoteValue: 4}, + 0: {At: 0, BarParams: cntl.BarParams{NoteCount: 4, NoteValue: 4, Speed: 160}}, + 512: {At: 512, BarParams: cntl.BarParams{NoteCount: 3, NoteValue: 4}}, + 1184: {At: 1184, BarParams: cntl.BarParams{NoteCount: 7, NoteValue: 8}}, + 1632: {At: 1632, BarParams: cntl.BarParams{NoteCount: 4, NoteValue: 4}}, }, }, } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index c2e802e..02c7074 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -7,14 +7,19 @@ type SetList struct { Songs []string `json:"songs" yaml:"songs"` } -// BarChange describes the changes of tempo and notes during a song -type BarChange struct { - At uint64 `json:"at" yaml:"at"` +// BarParams are a reusable informational struct on how fast and in what scheme something should be played +type BarParams struct { NoteValue uint8 `json:"noteValue" yaml:"noteValue"` NoteCount uint8 `json:"noteCount" yaml:"noteCount"` Speed uint16 `json:"speed" yaml:"speed"` } +// BarChange describes the changes of tempo and notes during a song +type BarChange struct { + BarParams + At uint64 `json:"at" yaml:"at"` +} + // DMXScenePosition describes the position of a DMX scene within a song type DMXScenePosition struct { ID string `json:"id" yaml:"id"` diff --git a/pkg/internal/fixtures/fixtures.go b/pkg/internal/fixtures/fixtures.go index da70276..f0c2b76 100644 --- a/pkg/internal/fixtures/fixtures.go +++ b/pkg/internal/fixtures/fixtures.go @@ -34,10 +34,10 @@ var data = &cntl.DataStore{ ID: "3c1065c8-0b14-11e7-96eb-5b134621c411", Name: "Test song", BarChanges: []cntl.BarChange{ - {At: 0, NoteCount: 4, NoteValue: 4, Speed: 160}, - {At: 512, NoteCount: 3, NoteValue: 4}, - {At: 1184, NoteCount: 7, NoteValue: 8}, - {At: 1632, NoteCount: 4, NoteValue: 4}, + {At: 0, BarParams: cntl.BarParams{NoteCount: 4, NoteValue: 4, Speed: 160}}, + {At: 512, BarParams: cntl.BarParams{NoteCount: 3, NoteValue: 4}}, + {At: 1184, BarParams: cntl.BarParams{NoteCount: 7, NoteValue: 8}}, + {At: 1632, BarParams: cntl.BarParams{NoteCount: 4, NoteValue: 4}}, }, DMXScenes: []cntl.DMXScenePosition{ {At: 0, ID: "492cef2e-0b14-11e7-be89-c3fa25f9cabb", Repeat: 3}, From 640f98f3a53339e583283b0a8b07e2881c46c2fb Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 9 Apr 2019 21:34:54 +0200 Subject: [PATCH 43/94] Add mutex to controller to prevent concurrent read and write --- pkg/artnet/controller.go | 20 ++++++++++++++++---- pkg/cntl/dmx/params.go | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index 5cfb4dc..a156fa3 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "strings" + "sync" "time" "github.com/jsimonetti/go-artnet" @@ -17,6 +18,7 @@ import ( // Controller is a transport for the ArtNet protocol (DMX over UDP/IP) type controller struct { + mutex sync.RWMutex logger logging.Logger sender *artnet.Controller state State @@ -48,17 +50,18 @@ func NewController(logger logging.Logger) (Controller, error) { return nil, fmt.Errorf("failed to start Controller: %v", err) } - cntl := &controller{ + control := &controller{ + mutex: sync.RWMutex{}, logger: logger, sender: c, state: NewState(), sendTrigger: make(chan struct{}, 1), } - go cntl.sendBackground() - go cntl.debugDevices() + go control.sendBackground() + go control.debugDevices() - return cntl, nil + return control, nil } // Start the controller @@ -73,11 +76,17 @@ func (c *controller) Stop() { } func (c *controller) SetDMXChannelValue(value ChannelValue) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.state.Set(value.Universe, value.Channel, value.Value) c.triggerSend() } func (c *controller) SetDMXChannelValues(values []ChannelValue) { + c.mutex.Lock() + defer c.mutex.Unlock() + for _, value := range values { c.state.Set(value.Universe, value.Channel, value.Value) } @@ -113,6 +122,9 @@ func (c *controller) sendBackground() { } func (c *controller) send() { + c.mutex.RLock() + defer c.mutex.RUnlock() + for universe, dmx := range c.state { go c.sender.SendDMXToAddress(dmx, c.universeToAddress(universe)) } diff --git a/pkg/cntl/dmx/params.go b/pkg/cntl/dmx/params.go index a45e6f0..d3d82df 100644 --- a/pkg/cntl/dmx/params.go +++ b/pkg/cntl/dmx/params.go @@ -21,7 +21,7 @@ func checkDeviceParams(dp *cntl.DMXDeviceParams) error { } valuesSet := 0 - if dp.Params != nil { + if dp.Params != nil && len(dp.Params) > 0 { valuesSet++ } if dp.Animation != nil { From c49a82cf2098703c461e2cfd5861508a762fcf97 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 11 Apr 2019 21:05:35 +0200 Subject: [PATCH 44/94] Change controller state to use sync.Map instead of a wrapped mutex --- pkg/artnet/controller.go | 22 +++---------- pkg/artnet/state.go | 69 ++++++++++++++++++++++++---------------- pkg/artnet/state_test.go | 29 ++++++++++++----- pkg/artnet/types.go | 3 ++ 4 files changed, 71 insertions(+), 52 deletions(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index a156fa3..d5704f5 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "strings" - "sync" "time" "github.com/jsimonetti/go-artnet" @@ -18,10 +17,9 @@ import ( // Controller is a transport for the ArtNet protocol (DMX over UDP/IP) type controller struct { - mutex sync.RWMutex logger logging.Logger sender *artnet.Controller - state State + state *State sendTrigger chan struct{} } @@ -51,7 +49,6 @@ func NewController(logger logging.Logger) (Controller, error) { } control := &controller{ - mutex: sync.RWMutex{}, logger: logger, sender: c, state: NewState(), @@ -76,19 +73,13 @@ func (c *controller) Stop() { } func (c *controller) SetDMXChannelValue(value ChannelValue) { - c.mutex.Lock() - defer c.mutex.Unlock() - - c.state.Set(value.Universe, value.Channel, value.Value) + c.state.SetChannel(value.Universe, value.Channel, value.Value) c.triggerSend() } func (c *controller) SetDMXChannelValues(values []ChannelValue) { - c.mutex.Lock() - defer c.mutex.Unlock() - for _, value := range values { - c.state.Set(value.Universe, value.Channel, value.Value) + c.state.SetChannel(value.Universe, value.Channel, value.Value) } c.triggerSend() @@ -122,11 +113,8 @@ func (c *controller) sendBackground() { } func (c *controller) send() { - c.mutex.RLock() - defer c.mutex.RUnlock() - - for universe, dmx := range c.state { - go c.sender.SendDMXToAddress(dmx, c.universeToAddress(universe)) + for _, u := range c.state.GetUniverses() { + go c.sender.SendDMXToAddress([512]byte(c.state.GetUniverse(u)), c.universeToAddress(u)) } } diff --git a/pkg/artnet/state.go b/pkg/artnet/state.go index 1f0b791..ae24874 100644 --- a/pkg/artnet/state.go +++ b/pkg/artnet/state.go @@ -1,45 +1,60 @@ package artnet +import ( + "sync" +) + // State stores the state of universes -type State map[uint16][512]byte +type State struct { + data sync.Map +} // NewState returns a new state instance -func NewState() State { - return make(State) +func NewState() *State { + return &State{ + data: sync.Map{}, + } } -// Set sets a given channel on a given universe on a given value. -func (state State) Set(u uint16, c, v uint8) { - state.addUniverse(u) +// NewStateFromData takes the given data and stores it into a freshly created store +func NewStateFromData(data map[uint16]Universe) *State { + s := NewState() + for k, value := range data { + s.SetUniverse(k, value) + } + return s +} - dmx := state[u] +// SetChannel sets a given channel on a given universe on a given value. +func (s *State) SetChannel(u uint16, c, v uint8) { + dmx := s.GetUniverse(u) dmx[c] = byte(v) - state[u] = dmx + s.SetUniverse(u, dmx) } -func (state State) addUniverse(u uint16) { - if _, ok := state[u]; ok { - return - } - - state[u] = [512]byte{} +// SetUniverse sets a complete DMX universe data +func (s *State) SetUniverse(u uint16, dmx Universe) { + s.data.Store(u, dmx) } -// Equals compares two states for equality -func (state State) Equals(state2 State) bool { - if len(state) != len(state2) { - return false +// GetUniverse gets a complete DMX universe data +func (s *State) GetUniverse(u uint16) Universe { + dmx, ok := s.data.Load(u) + if !ok { + return Universe{} } - for u := range state { - if _, ok := state2[u]; !ok { - return false - } + return dmx.(Universe) +} - if state2[u] != state[u] { - return false - } - } +// GetUniverses returns a slice of all available universe indexes +func (s *State) GetUniverses() []uint16 { + universes := make([]uint16, 0) + + s.data.Range(func(key interface{}, value interface{}) bool { + universes = append(universes, key.(uint16)) + return true + }) - return true + return universes } diff --git a/pkg/artnet/state_test.go b/pkg/artnet/state_test.go index db3f6a9..b5eafc6 100644 --- a/pkg/artnet/state_test.go +++ b/pkg/artnet/state_test.go @@ -1,25 +1,26 @@ package artnet import ( + "reflect" "testing" ) func TestState_Set(t *testing.T) { cases := []struct { - before, after State + before, after *State u uint16 c, v uint8 }{ { - before: State(map[uint16][512]byte{}), - after: State(map[uint16][512]byte{12: {14: 16}}), + before: NewStateFromData(map[uint16]Universe{}), + after: NewStateFromData(map[uint16]Universe{12: {14: 16}}), u: 12, c: 14, v: 16, }, { - before: State(map[uint16][512]byte{12: {14: 16}}), - after: State(map[uint16][512]byte{12: {14: 16}, 2: {4: 6}}), + before: NewStateFromData(map[uint16]Universe{12: {14: 16}}), + after: NewStateFromData(map[uint16]Universe{12: {14: 16}, 2: {4: 6}}), u: 2, c: 4, v: 6, @@ -27,9 +28,21 @@ func TestState_Set(t *testing.T) { } for i, c := range cases { - c.before.Set(c.u, c.c, c.v) - if !c.before.Equals(c.after) { - t.Errorf("Expected to have state %+v at case %v, got %+v", c.after, i, c.before) + c.before.SetChannel(c.u, c.c, c.v) + + bu := c.before.GetUniverses() + au := c.after.GetUniverses() + + if !reflect.DeepEqual(bu, au) { + t.Errorf("Expected to get universes %+v at case %v, got %+v", au, c, bu) + continue } + + for _, u := range au { + if !reflect.DeepEqual(c.before.GetUniverse(u), c.after.GetUniverse(u)) { + t.Errorf("Expected to have state %+v at case %v, got %+v", c.after.GetUniverse(u), i, c.before.GetUniverse(u)) + } + } + } } diff --git a/pkg/artnet/types.go b/pkg/artnet/types.go index ac5f8d6..f73542b 100644 --- a/pkg/artnet/types.go +++ b/pkg/artnet/types.go @@ -27,3 +27,6 @@ type Controller interface { Start() error Stop() } + +// Universe wraps the 512 byte array for convenience +type Universe [512]byte From ed7bd6dbfb2cc82ecd8225328ec6ead5b0d04f52 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 11 Apr 2019 21:43:00 +0200 Subject: [PATCH 45/94] Add LEDAll option to DMXParams --- pkg/cntl/dmx/params.go | 40 ++++++++++++++++++++------ pkg/cntl/dmx/params_test.go | 57 ++++++++++++++++++++++++++++++++++--- pkg/cntl/types.go | 1 + 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/pkg/cntl/dmx/params.go b/pkg/cntl/dmx/params.go index d3d82df..215e572 100644 --- a/pkg/cntl/dmx/params.go +++ b/pkg/cntl/dmx/params.go @@ -174,23 +174,47 @@ func RenderParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, p cntl.DMXParams) (c }) } + // for each device in the resolved selectors and each channel set the correct LED value for _, d := range dd { for _, c := range channels { - ch, err := getDeviceChannel(ds, d, c.Channel, p.LED) - if err != nil { - return cntl.DMXCommands{}, err + for _, led := range resolveLEDs(ds, p, d) { + ch, err := getDeviceChannel(ds, d, c.Channel, led) + if err != nil { + return cntl.DMXCommands{}, err + } + cmds = append(cmds, cntl.DMXCommand{ + Universe: d.Universe, + Channel: ch, + Value: c.Value, + }) } - cmds = append(cmds, cntl.DMXCommand{ - Universe: d.Universe, - Channel: ch, - Value: c.Value, - }) } } return } +func resolveLEDs(ds *cntl.DataStore, p cntl.DMXParams, d *cntl.DMXDevice) []uint16 { + if !p.LEDAll { + return []uint16{p.LED} + } + + // in this place we assume that we already resolved the device type before ... + dt, ok := ds.DMXDeviceTypes[d.TypeID] + if !ok { + panic("Alex is an idiot, the comment is wrong. Thanks.") + } + + // so in order to iterate all LEDs we just returns a slice with every LED index, which in fact is + // the index of the slice ... wow :D + + leds := make([]uint16, len(dt.LEDs)) + for index := range dt.LEDs { + leds[index] = uint16(index) + } + return leds +} + func resolveColorVar(ds *cntl.DataStore, p *cntl.DMXParams) error { if p.ColorVar == nil || *p.ColorVar == "" { return nil diff --git a/pkg/cntl/dmx/params_test.go b/pkg/cntl/dmx/params_test.go index e7102b5..737c805 100644 --- a/pkg/cntl/dmx/params_test.go +++ b/pkg/cntl/dmx/params_test.go @@ -143,6 +143,55 @@ func TestRenderDeviceParams(t *testing.T) { }, err: nil, }, + { + dp: &cntl.DMXDeviceParams{ + Device: fixtures.StrPtr("35cae00a-0b17-11e7-8bca-bbf30c56f20e"), + Params: []cntl.DMXParams{ + { + Red: fixtures.Value255, + LEDAll: true, + }, + }, + }, + c: []cntl.DMXCommands{ + { + {Universe: 1, Channel: 222, Value: *fixtures.Value255}, + {Universe: 1, Channel: 226, Value: *fixtures.Value255}, + {Universe: 1, Channel: 230, Value: *fixtures.Value255}, + {Universe: 1, Channel: 234, Value: *fixtures.Value255}, + {Universe: 1, Channel: 238, Value: *fixtures.Value255}, + {Universe: 1, Channel: 242, Value: *fixtures.Value255}, + {Universe: 1, Channel: 246, Value: *fixtures.Value255}, + {Universe: 1, Channel: 250, Value: *fixtures.Value255}, + {Universe: 1, Channel: 254, Value: *fixtures.Value255}, + {Universe: 1, Channel: 258, Value: *fixtures.Value255}, + {Universe: 1, Channel: 262, Value: *fixtures.Value255}, + {Universe: 1, Channel: 266, Value: *fixtures.Value255}, + {Universe: 1, Channel: 270, Value: *fixtures.Value255}, + {Universe: 1, Channel: 274, Value: *fixtures.Value255}, + {Universe: 1, Channel: 278, Value: *fixtures.Value255}, + {Universe: 1, Channel: 282, Value: *fixtures.Value255}, + }, + }, + err: nil, + }, + { + dp: &cntl.DMXDeviceParams{ + Device: fixtures.StrPtr("5e0335e0-0b17-11e7-ad6c-63a7138d926c"), + Params: []cntl.DMXParams{ + { + Red: fixtures.Value255, + LEDAll: true, + }, + }, + }, + c: []cntl.DMXCommands{ + { + {Universe: 2, Channel: 26, Value: *fixtures.Value255}, + }, + }, + err: nil, + }, { dp: &cntl.DMXDeviceParams{ Group: fixtures.StrPtr("475b71a0-0b16-11e7-9406-e3f678e8b788"), @@ -207,21 +256,21 @@ func TestRenderDeviceParams(t *testing.T) { for i, e := range exp { c, err := RenderDeviceParams(ds, e.dp) if e.err != nil && (err == nil || err.Error() != e.err.Error()) { - t.Fatalf("Expected to get error %v, got %v at index %d", e.err, err, i) + t.Fatalf("Expected to get error %v, got %v at case %d", e.err, err, i) } if len(c) != len(e.c) { - t.Fatalf("Expected to get %d commands, got %d at index %d", len(e.c), len(c), i) + t.Fatalf("Expected to get %d commands, got %d at case %d", len(e.c), len(c), i) } for j := range e.c { if len(e.c[j]) != len(c[j]) { - t.Fatalf("Expected to get length %d at command index %d, got %d at index %d", len(e.c[j]), j, len(c[j]), i) + t.Fatalf("Expected to get length %d at command index %d, got %d at case %d", len(e.c[j]), j, len(c[j]), i) } for _, cmd := range e.c[j] { if !c[j].Contains(cmd) { - t.Errorf("Expected %+v to have %+v, but hasn't index %d", c[j], cmd, i) + t.Errorf("Expected %+v to have %+v, but hasn't at case %d", c[j], cmd, i) } } } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 02c7074..fe98b4c 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -125,6 +125,7 @@ type DMXColorVariable struct { // DMXParams is a DMX parameter object type DMXParams struct { + LEDAll bool `json:"ledAll"` LED uint16 `json:"led" yaml:"led"` ColorVar *string `json:"$color" yaml:"$color"` Red *DMXValue `json:"red" yaml:"red"` From 4564570384df1c7ca20ab7f564ffd7686f2d8604 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 13 Apr 2019 14:55:17 +0200 Subject: [PATCH 46/94] Add panFine, tiltFine and panTiltSpeed channels --- pkg/cntl/dmx/const.go | 3 +++ pkg/cntl/dmx/device.go | 18 +++++++++++++ pkg/cntl/types.go | 58 +++++++++++++++++++++++------------------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/pkg/cntl/dmx/const.go b/pkg/cntl/dmx/const.go index 41220cc..9207535 100644 --- a/pkg/cntl/dmx/const.go +++ b/pkg/cntl/dmx/const.go @@ -12,5 +12,8 @@ const ( ChannelMode ChannelDimmer ChannelTilt + ChannelTiltFine ChannelPan + ChannelPanFine + ChannelPanTiltSpeed ) diff --git a/pkg/cntl/dmx/device.go b/pkg/cntl/dmx/device.go index a060aff..351a2f5 100644 --- a/pkg/cntl/dmx/device.go +++ b/pkg/cntl/dmx/device.go @@ -59,12 +59,30 @@ func getDeviceChannel(ds *cntl.DataStore, d *cntl.DMXDevice, c cntl.DMXChannel, } channel = dt.TiltChannel + case ChannelTiltFine: + if !dt.Moving { + return 0, ErrDeviceIsNotMoving + } + channel = dt.TiltFineChannel + case ChannelPan: if !dt.Moving { return 0, ErrDeviceIsNotMoving } channel = dt.PanChannel + case ChannelPanFine: + if !dt.Moving { + return 0, ErrDeviceIsNotMoving + } + channel = dt.PanFineChannel + + case ChannelPanTiltSpeed: + if !dt.Moving { + return 0, ErrDeviceIsNotMoving + } + channel = dt.PanTiltSpeedChannel + default: return 0, fmt.Errorf("channel %q is unknown", c) } diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index fe98b4c..926e4a1 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -51,20 +51,23 @@ type DMXDevice struct { // DMXDeviceType is the type of a DMXDevice type DMXDeviceType struct { - ID string `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - ChannelCount uint16 `json:"channelCount" yaml:"channelCount"` - ChannelsPerLED uint16 `json:"channelsPerLED" yaml:"channelsPerLED"` - StrobeEnabled bool `json:"strobeEnabled" yaml:"strobeEnabled"` - StrobeChannel DMXChannel `json:"strobeChannel" yaml:"strobeChannel"` - DimmerEnabled bool `json:"dimmerEnabled" yaml:"dimmerEnabled"` - DimmerChannel DMXChannel `json:"dimmerChannel" yaml:"dimmerChannel"` - ModeEnabled bool `json:"modeEnabled" yaml:"modeEnabled"` - ModeChannel DMXChannel `json:"modeChannel" yaml:"modeChannel"` - Moving bool `json:"moving" yaml:"moving"` - TiltChannel DMXChannel `json:"tiltChannel" yaml:"tiltChannel"` - PanChannel DMXChannel `json:"panChannel" yaml:"panChannel"` - LEDs []LED `json:"leds"` + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + ChannelCount uint16 `json:"channelCount" yaml:"channelCount"` + ChannelsPerLED uint16 `json:"channelsPerLED" yaml:"channelsPerLED"` + StrobeEnabled bool `json:"strobeEnabled" yaml:"strobeEnabled"` + StrobeChannel DMXChannel `json:"strobeChannel" yaml:"strobeChannel"` + DimmerEnabled bool `json:"dimmerEnabled" yaml:"dimmerEnabled"` + DimmerChannel DMXChannel `json:"dimmerChannel" yaml:"dimmerChannel"` + ModeEnabled bool `json:"modeEnabled" yaml:"modeEnabled"` + ModeChannel DMXChannel `json:"modeChannel" yaml:"modeChannel"` + Moving bool `json:"moving" yaml:"moving"` + PanChannel DMXChannel `json:"panChannel" yaml:"panChannel"` + PanFineChannel DMXChannel `json:"panFineChannel" yaml:"panFineChannel"` + TiltChannel DMXChannel `json:"tiltChannel" yaml:"tiltChannel"` + TiltFineChannel DMXChannel `json:"tiltFineChannel" yaml:"tiltFineChannel"` + PanTiltSpeedChannel DMXChannel `json:"panTiltSpeedChannel" yaml:"panTiltSpeedChannel"` + LEDs []LED `json:"leds"` } // LED maps a single LEDs DMX channels @@ -125,18 +128,21 @@ type DMXColorVariable struct { // DMXParams is a DMX parameter object type DMXParams struct { - LEDAll bool `json:"ledAll"` - LED uint16 `json:"led" yaml:"led"` - ColorVar *string `json:"$color" yaml:"$color"` - Red *DMXValue `json:"red" yaml:"red"` - Green *DMXValue `json:"green" yaml:"green"` - Blue *DMXValue `json:"blue" yaml:"blue"` - White *DMXValue `json:"white" yaml:"white"` - Pan *DMXValue `json:"pan" yaml:"pan"` - Tilt *DMXValue `json:"tilt" yaml:"tilt"` - Strobe *DMXValue `json:"strobe" yaml:"strobe"` - Mode *DMXValue `json:"mode" yaml:"mode"` - Dimmer *DMXValue `json:"dimmer" yaml:"dimmer"` + LEDAll bool `json:"ledAll"` + LED uint16 `json:"led" yaml:"led"` + ColorVar *string `json:"$color" yaml:"$color"` + Red *DMXValue `json:"red" yaml:"red"` + Green *DMXValue `json:"green" yaml:"green"` + Blue *DMXValue `json:"blue" yaml:"blue"` + White *DMXValue `json:"white" yaml:"white"` + Pan *DMXValue `json:"pan" yaml:"pan"` + PanFine *DMXValue `json:"panFine" yaml:"panFine"` + Tilt *DMXValue `json:"tilt" yaml:"tilt"` + TiltFine *DMXValue `json:"tiltFine" yaml:"tiltFine"` + PanTiltSpeed *DMXValue `json:"panTiltSpeed" yaml:"panTiltSpeed"` + Strobe *DMXValue `json:"strobe" yaml:"strobe"` + Mode *DMXValue `json:"mode" yaml:"mode"` + Dimmer *DMXValue `json:"dimmer" yaml:"dimmer"` } // DMXAnimation is an animation of dmx params in relation to time From a302fac8509504d008012a93aa55f98c212e2e84 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 15 Apr 2019 18:49:11 +0200 Subject: [PATCH 47/94] New controller implementation --- cmd/server.go | 5 +++-- pkg/artnet/controller.go | 34 +++++++++++++---------------- pkg/artnet/state.go | 44 ++++++++++++++++++++++++++------------ pkg/artnet/types.go | 7 ++++++ pkg/cntl/dmx/transition.go | 12 +++++------ 5 files changed, 61 insertions(+), 41 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index ed3fad2..b3d1d97 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -3,12 +3,13 @@ package cmd import ( "fmt" + "github.com/apinnecke/go-exitcontext" + "github.com/spf13/cobra" + "github.com/StageAutoControl/controller/pkg/api/server" "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/disk" "github.com/StageAutoControl/controller/pkg/process" - "github.com/apinnecke/go-exitcontext" - "github.com/spf13/cobra" ) // serverCmd represents the server command diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index d5704f5..fc317fe 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -20,7 +20,7 @@ type controller struct { logger logging.Logger sender *artnet.Controller state *State - sendTrigger chan struct{} + sendTrigger chan *UniverseStateMap } // NewController returns a artnet Controller as an anonymous interface @@ -52,7 +52,7 @@ func NewController(logger logging.Logger) (Controller, error) { logger: logger, sender: c, state: NewState(), - sendTrigger: make(chan struct{}, 1), + sendTrigger: make(chan *UniverseStateMap, 100), } go control.sendBackground() @@ -78,22 +78,17 @@ func (c *controller) SetDMXChannelValue(value ChannelValue) { } func (c *controller) SetDMXChannelValues(values []ChannelValue) { - for _, value := range values { - c.state.SetChannel(value.Universe, value.Channel, value.Value) - } - + c.state.SetChannelValues(values) c.triggerSend() } // Write implements the playback.TransportWriter interface to compatibility :) func (c *controller) Write(cmd cntl.Command) error { - values := make([]ChannelValue, 0) - for _, dmxCmd := range cmd.DMXCommands { - values = append(values, ChannelValue{ - Universe: uint16(dmxCmd.Universe), - Channel: uint8(dmxCmd.Channel), - Value: dmxCmd.Value.Uint8(), - }) + values := make([]ChannelValue, len(cmd.DMXCommands)) + for i, dmxCmd := range cmd.DMXCommands { + values[i].Universe = uint16(dmxCmd.Universe) + values[i].Channel = uint8(dmxCmd.Channel) + values[i].Value = dmxCmd.Value.Uint8() } c.SetDMXChannelValues(values) @@ -102,19 +97,20 @@ func (c *controller) Write(cmd cntl.Command) error { } func (c *controller) triggerSend() { - c.sendTrigger <- struct{}{} + data := c.state.Get() + c.sendTrigger <- &data } func (c *controller) sendBackground() { - for range c.sendTrigger { + for data := range c.sendTrigger { c.logger.Debug("Sending DMX Values") - c.send() + c.send(data) } } -func (c *controller) send() { - for _, u := range c.state.GetUniverses() { - go c.sender.SendDMXToAddress([512]byte(c.state.GetUniverse(u)), c.universeToAddress(u)) +func (c *controller) send(data *UniverseStateMap) { + for u, dmx := range *data { + c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u)) } } diff --git a/pkg/artnet/state.go b/pkg/artnet/state.go index ae24874..25e9c8c 100644 --- a/pkg/artnet/state.go +++ b/pkg/artnet/state.go @@ -1,23 +1,24 @@ package artnet -import ( - "sync" -) - // State stores the state of universes type State struct { - data sync.Map + data UniverseStateMap } // NewState returns a new state instance func NewState() *State { return &State{ - data: sync.Map{}, + data: UniverseStateMap{}, } } // NewStateFromData takes the given data and stores it into a freshly created store func NewStateFromData(data map[uint16]Universe) *State { + return NewStateFromUniverseStateMap(UniverseStateMap(data)) +} + +// NewStateFromUniverseStateMap takes the given data and stores it into a freshly created store +func NewStateFromUniverseStateMap(data map[uint16]Universe) *State { s := NewState() for k, value := range data { s.SetUniverse(k, value) @@ -32,29 +33,44 @@ func (s *State) SetChannel(u uint16, c, v uint8) { s.SetUniverse(u, dmx) } +func (s *State) SetChannelValue(value ChannelValue) { + s.SetChannel(value.Universe, value.Channel, value.Value) +} + +// SetChannelValues sets a range of ChannelValues for convenience +func (s *State) SetChannelValues(values []ChannelValue) { + for _, value := range values { + s.SetChannelValue(value) + } +} + // SetUniverse sets a complete DMX universe data func (s *State) SetUniverse(u uint16, dmx Universe) { - s.data.Store(u, dmx) + s.data[u] = dmx } // GetUniverse gets a complete DMX universe data func (s *State) GetUniverse(u uint16) Universe { - dmx, ok := s.data.Load(u) + dmx, ok := s.data[u] if !ok { - return Universe{} + dmx = Universe{} } - return dmx.(Universe) + return dmx +} + +// Get returns all of the current state +func (s *State) Get() UniverseStateMap { + return s.data } // GetUniverses returns a slice of all available universe indexes func (s *State) GetUniverses() []uint16 { universes := make([]uint16, 0) - s.data.Range(func(key interface{}, value interface{}) bool { - universes = append(universes, key.(uint16)) - return true - }) + for u := range s.data { + universes = append(universes, u) + } return universes } diff --git a/pkg/artnet/types.go b/pkg/artnet/types.go index f73542b..21720d3 100644 --- a/pkg/artnet/types.go +++ b/pkg/artnet/types.go @@ -30,3 +30,10 @@ type Controller interface { // Universe wraps the 512 byte array for convenience type Universe [512]byte + +func (u Universe) toByteSlice() [512]byte { + return [512]byte(u) +} + +// UniverseStateMap holds the state of all used universes +type UniverseStateMap map[uint16]Universe diff --git a/pkg/cntl/dmx/transition.go b/pkg/cntl/dmx/transition.go index 8d8ac39..8b14077 100644 --- a/pkg/cntl/dmx/transition.go +++ b/pkg/cntl/dmx/transition.go @@ -50,7 +50,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM } for i, step := range steps { - stepParam := cntl.DMXParams{LED: p.From.LED} + stepParam := cntl.DMXParams{LED: p.From.LED, LEDAll: p.From.LEDAll} stepParam.Red = &cntl.DMXValue{Value: step} cmd, err := RenderParams(ds, dd, stepParam) @@ -69,7 +69,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM } for i, step := range steps { - stepParam := cntl.DMXParams{LED: p.From.LED} + stepParam := cntl.DMXParams{LED: p.From.LED, LEDAll: p.From.LEDAll} stepParam.Green = &cntl.DMXValue{Value: step} cmd, err := RenderParams(ds, dd, stepParam) @@ -88,7 +88,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM } for i, step := range steps { - stepParam := cntl.DMXParams{LED: p.From.LED} + stepParam := cntl.DMXParams{LED: p.From.LED, LEDAll: p.From.LEDAll} stepParam.Blue = &cntl.DMXValue{Value: step} cmd, err := RenderParams(ds, dd, stepParam) @@ -107,7 +107,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM } for i, step := range steps { - stepParam := cntl.DMXParams{LED: p.From.LED} + stepParam := cntl.DMXParams{LED: p.From.LED, LEDAll: p.From.LEDAll} stepParam.White = &cntl.DMXValue{Value: step} cmd, err := RenderParams(ds, dd, stepParam) @@ -126,7 +126,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM } for i, step := range steps { - stepParam := cntl.DMXParams{LED: p.From.LED} + stepParam := cntl.DMXParams{LED: p.From.LED, LEDAll: p.From.LEDAll} stepParam.Pan = &cntl.DMXValue{Value: step} cmd, err := RenderParams(ds, dd, stepParam) @@ -145,7 +145,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM } for i, step := range steps { - stepParam := cntl.DMXParams{LED: p.From.LED} + stepParam := cntl.DMXParams{LED: p.From.LED, LEDAll: p.From.LEDAll} stepParam.Tilt = &cntl.DMXValue{Value: step} cmd, err := RenderParams(ds, dd, stepParam) From 71c9086115bcc4da02dc716c874b03bc88e50843 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 15 Apr 2019 23:59:44 +0200 Subject: [PATCH 48/94] Fix timing bugs --- cmd/server.go | 10 +++++++--- pkg/artnet/controller.go | 32 ++++++++++++++++++-------------- pkg/artnet/state.go | 19 ++++++++++++++++++- pkg/artnet/types.go | 9 ++++++--- pkg/cntl/transport/artnet.go | 2 +- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index b3d1d97..613fa11 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -6,7 +6,7 @@ import ( "github.com/apinnecke/go-exitcontext" "github.com/spf13/cobra" - "github.com/StageAutoControl/controller/pkg/api/server" + apiServer "github.com/StageAutoControl/controller/pkg/api/server" "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/disk" "github.com/StageAutoControl/controller/pkg/process" @@ -19,7 +19,7 @@ var serverCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := exitcontext.New() pm := process.NewManager(ctx, logger) - server, err := server.New(logger.WithField("module", "api"), storage, loader, controller, pm) + server, err := apiServer.New(logger.WithField("module", "api"), storage, loader, controller, pm) if err != nil { logger.Fatal(err) } @@ -39,8 +39,12 @@ var serverCmd = &cobra.Command{ if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller), true); err != nil { logger.Fatal(err) } - + if err := controller.Start(ctx); err != nil { + logger.Fatal(err) + } + logger.Info("Started ArtNet Controller") } + if err := server.Run(ctx, endpoint); err != nil { logger.Fatal(err) } diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index fc317fe..b86b84a 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -1,6 +1,7 @@ package artnet import ( + "context" "encoding/binary" "errors" "fmt" @@ -21,6 +22,7 @@ type controller struct { sender *artnet.Controller state *State sendTrigger chan *UniverseStateMap + context context.Context } // NewController returns a artnet Controller as an anonymous interface @@ -36,34 +38,34 @@ func NewController(logger logging.Logger) (Controller, error) { host, err := os.Hostname() if err != nil { - panic(err) + return nil, fmt.Errorf("failed to resolve hostname: %v", err) } host = strings.ToLower(strings.Split(host, ".")[0]) - logger.Infof("Using ArtNet IP %s and hostname %s", ip.String(), host) - c := artnet.NewController(host, ip, artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet"))) - if err := c.Start(); err != nil { - return nil, fmt.Errorf("failed to start Controller: %v", err) - } - + senderLogger := artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet")) control := &controller{ logger: logger, - sender: c, + sender: artnet.NewController(host, ip, senderLogger), state: NewState(), sendTrigger: make(chan *UniverseStateMap, 100), } - go control.sendBackground() - go control.debugDevices() - return control, nil } // Start the controller -func (c *controller) Start() error { - return c.sender.Start() +func (c *controller) Start(ctx context.Context) error { + if err := c.sender.Start(); err != nil { + return fmt.Errorf("failed to start Controller: %v", err) + } + + c.context = ctx + go c.sendBackground() + go c.debugDevices() + + return nil } // Stop the controller @@ -87,10 +89,12 @@ func (c *controller) Write(cmd cntl.Command) error { values := make([]ChannelValue, len(cmd.DMXCommands)) for i, dmxCmd := range cmd.DMXCommands { values[i].Universe = uint16(dmxCmd.Universe) - values[i].Channel = uint8(dmxCmd.Channel) + values[i].Channel = uint16(dmxCmd.Channel) values[i].Value = dmxCmd.Value.Uint8() } + //c.logger.Warnf("%+v", values) + c.SetDMXChannelValues(values) return nil diff --git a/pkg/artnet/state.go b/pkg/artnet/state.go index 25e9c8c..d2bc37c 100644 --- a/pkg/artnet/state.go +++ b/pkg/artnet/state.go @@ -1,14 +1,20 @@ package artnet +import ( + "sync" +) + // State stores the state of universes type State struct { data UniverseStateMap + m sync.RWMutex } // NewState returns a new state instance func NewState() *State { return &State{ data: UniverseStateMap{}, + m: sync.RWMutex{}, } } @@ -27,7 +33,7 @@ func NewStateFromUniverseStateMap(data map[uint16]Universe) *State { } // SetChannel sets a given channel on a given universe on a given value. -func (s *State) SetChannel(u uint16, c, v uint8) { +func (s *State) SetChannel(u, c uint16, v uint8) { dmx := s.GetUniverse(u) dmx[c] = byte(v) s.SetUniverse(u, dmx) @@ -46,11 +52,16 @@ func (s *State) SetChannelValues(values []ChannelValue) { // SetUniverse sets a complete DMX universe data func (s *State) SetUniverse(u uint16, dmx Universe) { + s.m.Lock() + defer s.m.Unlock() s.data[u] = dmx } // GetUniverse gets a complete DMX universe data func (s *State) GetUniverse(u uint16) Universe { + s.m.RLock() + defer s.m.RUnlock() + dmx, ok := s.data[u] if !ok { dmx = Universe{} @@ -61,6 +72,9 @@ func (s *State) GetUniverse(u uint16) Universe { // Get returns all of the current state func (s *State) Get() UniverseStateMap { + s.m.RLock() + defer s.m.RUnlock() + return s.data } @@ -68,6 +82,9 @@ func (s *State) Get() UniverseStateMap { func (s *State) GetUniverses() []uint16 { universes := make([]uint16, 0) + s.m.RLock() + defer s.m.RUnlock() + for u := range s.data { universes = append(universes, u) } diff --git a/pkg/artnet/types.go b/pkg/artnet/types.go index 21720d3..a82fa46 100644 --- a/pkg/artnet/types.go +++ b/pkg/artnet/types.go @@ -1,6 +1,8 @@ package artnet import ( + "context" + "github.com/jsimonetti/go-artnet" "github.com/StageAutoControl/controller/pkg/cntl" @@ -15,8 +17,9 @@ type Sender interface { // ChannelValue defines an ArtNet Universe and the value of the DMX channel type ChannelValue struct { - Universe uint16 - Channel, Value uint8 + Universe uint16 + Channel uint16 + Value uint8 } // Controller is a convenience interface to use within this application @@ -24,7 +27,7 @@ type Controller interface { Write(cntl.Command) error SetDMXChannelValue(value ChannelValue) SetDMXChannelValues(values []ChannelValue) - Start() error + Start(ctx context.Context) error Stop() } diff --git a/pkg/cntl/transport/artnet.go b/pkg/cntl/transport/artnet.go index 64a6152..2ba70a4 100644 --- a/pkg/cntl/transport/artnet.go +++ b/pkg/cntl/transport/artnet.go @@ -23,7 +23,7 @@ func (a *ArtNet) Write(cmd cntl.Command) error { for _, c := range cmd.DMXCommands { values = append(values, art.ChannelValue{ Universe: uint16(c.Universe), - Channel: uint8(c.Channel), + Channel: uint16(c.Channel), Value: c.Value.Uint8(), }) } From 557e73adbb43cc144b4160607fe087504487b01c Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 16 Apr 2019 20:32:19 +0200 Subject: [PATCH 49/94] Fix state test --- pkg/artnet/state_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/artnet/state_test.go b/pkg/artnet/state_test.go index b5eafc6..a822a0d 100644 --- a/pkg/artnet/state_test.go +++ b/pkg/artnet/state_test.go @@ -8,8 +8,8 @@ import ( func TestState_Set(t *testing.T) { cases := []struct { before, after *State - u uint16 - c, v uint8 + u, c uint16 + v uint8 }{ { before: NewStateFromData(map[uint16]Universe{}), From dd6fb860234f08f640612991a6f3de9fcd0031c2 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 18 Apr 2019 21:45:38 +0200 Subject: [PATCH 50/94] Update glide dependencies --- glide.lock | 9 ++++----- glide.yaml | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/glide.lock b/glide.lock index b23f071..6e33929 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 4a22eeea5709acfd04e88ed3dbdd1980fbae5816b39c4d81487823d65d3cc8f9 -updated: 2019-03-15T23:30:34.346436+01:00 +hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 +updated: 2019-04-18T21:45:28.684088+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -12,7 +12,7 @@ imports: - name: github.com/gorilla/handlers version: 7e0847f9db758cdebd26c149d0ae9d5d0b9c98ce - name: github.com/gorilla/rpc - version: 22c016f3df3febe0c1f6727598b6389507e03a18 + version: bffcfa752ad4e523cc8f720afeb5b985ed41ae16 subpackages: - json - v2 @@ -22,8 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 824feaef253c302b68768088dcc293a9b71a2b4e - repo: git@github.com:StageAutoControl/go-artnet + version: e4d7b9ff00334f00ac3073661fbe0888d32548a2 subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index b841d3a..4109321 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,9 +10,9 @@ import: - v2 - v2/json - package: github.com/jsimonetti/go-artnet - subpackages: - - packet/code - repo: git@github.com:StageAutoControl/go-artnet +# subpackages: +# - packet/code +# repo: git@github.com:StageAutoControl/go-artnet - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From 06a9c6a6fb29fa2426b88d7f8c964b4d74eab5d5 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 21 Apr 2019 11:34:18 +0200 Subject: [PATCH 51/94] Change rpc path to /api --- pkg/api/server/server.go | 2 +- pkg/api/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index b8021bd..2e81135 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -87,7 +87,7 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { h = handlers.CORS( handlers.AllowCredentials(), handlers.AllowedOrigins([]string{"*"}), - handlers.AllowedMethods([]string{"POST", "GET"}), + handlers.AllowedMethods([]string{"POST", "GET", "HEAD"}), handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}), )(h) diff --git a/pkg/api/types.go b/pkg/api/types.go index 05cfb9b..0ae2205 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -8,7 +8,7 @@ import ( var ( // RPCPath to where the RPC server should listen on - RPCPath = "/rpc" + RPCPath = "/api" // ErrNoIDGiven is returned when the request did not contain a valid ID ErrNoIDGiven = errors.New("no ID was given with request") From 78821adbfae9c18defa0ea477e276321abff7238 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 21 Apr 2019 12:11:05 +0200 Subject: [PATCH 52/94] Update go-artnet package --- glide.lock | 7 ++++--- glide.yaml | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/glide.lock b/glide.lock index 6e33929..7be16da 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 -updated: 2019-04-18T21:45:28.684088+02:00 +hash: f4e6870f49d0aa79d8d89005b188a472ac12ef0f9b5f428d2ee8850f311ec4ab +updated: 2019-04-21T12:10:37.25958+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,8 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: e4d7b9ff00334f00ac3073661fbe0888d32548a2 + version: 824feaef253c302b68768088dcc293a9b71a2b4e + repo: git@github.com:StageAutoControl/go-artnet subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index 4109321..6959445 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,9 +10,7 @@ import: - v2 - v2/json - package: github.com/jsimonetti/go-artnet -# subpackages: -# - packet/code -# repo: git@github.com:StageAutoControl/go-artnet + repo: git@github.com:StageAutoControl/go-artnet - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From 825931c8a0e804aeb36e4d182619c727ad2aa6e1 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 21 Apr 2019 12:14:46 +0200 Subject: [PATCH 53/94] Use https to clone the go-artnet fork --- glide.lock | 8 ++++---- glide.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/glide.lock b/glide.lock index 7be16da..e5c10c9 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: f4e6870f49d0aa79d8d89005b188a472ac12ef0f9b5f428d2ee8850f311ec4ab -updated: 2019-04-21T12:10:37.25958+02:00 +hash: 545bc7234b824797dad72e8e0edfde8d0a8792abc3e7deaa15ccd852b25014cb +updated: 2019-04-21T12:14:25.603471+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,8 +22,8 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 824feaef253c302b68768088dcc293a9b71a2b4e - repo: git@github.com:StageAutoControl/go-artnet + version: 750f64a82a4a1596a2ddb3db029f25c968fc7925 + repo: https://github.com/StageAutoControl/go-artnet.git subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index 6959445..28bac0f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,7 +10,7 @@ import: - v2 - v2/json - package: github.com/jsimonetti/go-artnet - repo: git@github.com:StageAutoControl/go-artnet + repo: https://github.com/StageAutoControl/go-artnet.git - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From a92c290259b4e7eed206ec88305dc113ba9e7cc3 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sun, 21 Apr 2019 22:09:48 +0200 Subject: [PATCH 54/94] Update dependencies --- glide.lock | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/glide.lock b/glide.lock index e5c10c9..6fa40ba 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,12 @@ hash: 545bc7234b824797dad72e8e0edfde8d0a8792abc3e7deaa15ccd852b25014cb -updated: 2019-04-21T12:14:25.603471+02:00 +updated: 2019-04-21T22:06:36.310522+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 - name: github.com/creasty/go-easing version: 0cfd96d3a544aad2e643739e4a4f6c081b12cda0 - name: github.com/google/btree - version: 4030bb1f1f0c35b30ca7009e9ebd06849dd45306 + version: 20236160a414454a9c64b6c8829381c6f4bddcaa - name: github.com/gordonklaus/portaudio version: 00e7307ccd93051979a933c6fd5ead641eba5686 - name: github.com/gorilla/handlers @@ -22,7 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 750f64a82a4a1596a2ddb3db029f25c968fc7925 + version: 964cdb29d889ef234b4a6e8757889a283c61b197 repo: https://github.com/StageAutoControl/go-artnet.git subpackages: - packet @@ -37,20 +37,15 @@ imports: - name: github.com/satori/go.uuid version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 - name: github.com/sirupsen/logrus - version: dae0fa8d5b0c810a8ab733fbd5510c7cae84eca4 + version: 9b3cdde74fbe9443d704467498a7dcb61a79de9b - name: github.com/spf13/cobra - version: ba1052d4cbce7aac421a96de820558f75199ccbc + version: 67fc4837d267bc9bfd6e47f77783fcc3dffc68de - name: github.com/spf13/pflag version: 24fa6976df40757dce6aea913e7b81ade90530e1 - name: github.com/spf13/viper version: 9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9 -- name: golang.org/x/crypto - version: a1f597ede03a7bef967a422b5b3a5bd08805a01e - subpackages: - - ssh/terminal - name: golang.org/x/sys - version: fead79001313d15903fb4605b4a1b781532cd93e + version: e8e3143a4f4a00f1fafef0dd82ba78223281b01b subpackages: - unix - - windows testImports: [] From 64140ad05f2fbe418ff3317fafdb6c4cde88e800 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 11:37:59 +0200 Subject: [PATCH 55/94] Fix closing of portaudio sockets after waiting in playback --- cmd/playback.go | 14 +++----- glide.lock | 7 ++-- glide.yaml | 3 +- pkg/cntl/playback/player.go | 12 +++---- pkg/cntl/playback/process.go | 11 +++--- pkg/cntl/playback/types.go | 2 +- pkg/cntl/waiter/audio.go | 66 +++++++++++++++++++----------------- pkg/cntl/waiter/none.go | 2 +- 8 files changed, 56 insertions(+), 61 deletions(-) diff --git a/cmd/playback.go b/cmd/playback.go index 73539c7..6b8a9fe 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -6,14 +6,15 @@ import ( "fmt" "os" + "github.com/apinnecke/go-exitcontext" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/cntl/transport" "github.com/StageAutoControl/controller/pkg/cntl/waiter" - "github.com/apinnecke/go-exitcontext" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" ) const ( @@ -119,12 +120,7 @@ var playbackCmd = &cobra.Command{ break case waiter.TypeAudio: - a, err := waiter.NewAudio(logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeAudio), audioWaiterThreshold) - if err != nil { - logger.Fatal(err) - } - - waiters = append(waiters, a) + waiters = append(waiters, waiter.NewAudio(logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeAudio), audioWaiterThreshold)) break } diff --git a/glide.lock b/glide.lock index 6fa40ba..c3c3fd5 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 545bc7234b824797dad72e8e0edfde8d0a8792abc3e7deaa15ccd852b25014cb -updated: 2019-04-21T22:06:36.310522+02:00 +hash: 66900fc590a9d0613ea5b56313b40ca4a7f352f06fbceafb2629e357a29e6e1f +updated: 2019-04-22T10:38:12.018545+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,8 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 964cdb29d889ef234b4a6e8757889a283c61b197 - repo: https://github.com/StageAutoControl/go-artnet.git + version: 1330e2e425c0491ac9a89453f203dbfbf88b6c75 subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index 28bac0f..f928f2b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,7 +10,8 @@ import: - v2 - v2/json - package: github.com/jsimonetti/go-artnet - repo: https://github.com/StageAutoControl/go-artnet.git +# repo: https://github.com/StageAutoControl/go-artnet.git + version: controller-self-logic - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index b88bcfa..77968b6 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -2,13 +2,12 @@ package playback import ( "context" + "fmt" "time" "github.com/StageAutoControl/controller/pkg/cntl" "github.com/StageAutoControl/controller/pkg/internal/logging" - "fmt" - "github.com/StageAutoControl/controller/pkg/cntl/song" ) @@ -70,10 +69,13 @@ func (p *Player) PlaySetList(ctx context.Context, setListID string) error { } func (p *Player) wait(ctx context.Context) error { + if len(p.waiters) == 0 { + return nil + } + chanLen := len(p.waiters) + 1 done := make(chan struct{}, chanLen) cancel := make(chan struct{}, chanLen) - err := make(chan error, chanLen) defer func() { cancel <- struct{}{} @@ -81,7 +83,7 @@ func (p *Player) wait(ctx context.Context) error { for _, w := range p.waiters { go func() { - if err := w.Wait(done, cancel, err); err != nil { + if err := w.Wait(done, cancel); err != nil { p.logger.Error(err) } }() @@ -92,8 +94,6 @@ func (p *Player) wait(ctx context.Context) error { return ErrCancelled case <-done: return nil - case err := <-err: - return err } } diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index 6b3bfcc..c357281 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -58,6 +58,9 @@ func (p *Process) Start(ctx context.Context) error { } cfg, err := p.parseConfig(config) + if err != nil { + return err + } p.player = NewPlayer(p.logger, ds, cfg.writers, cfg.waiters) ctx, p.cancel = context.WithCancel(ctx) @@ -106,13 +109,7 @@ func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { } if config.Waiters.Audio.Enabled { - audio, err := waiter.NewAudio(p.logger, config.Waiters.Audio.Threshold) - if err != nil { - return nil, fmt.Errorf("failed to create audio waiter: %v", err) - } - cfg.waiters = append(cfg.waiters, audio) - } else { - cfg.waiters = append(cfg.waiters, waiter.NewNone(p.logger)) + cfg.waiters = append(cfg.waiters, waiter.NewAudio(p.logger, config.Waiters.Audio.Threshold)) } return cfg, nil diff --git a/pkg/cntl/playback/types.go b/pkg/cntl/playback/types.go index 4c7115e..6eb6e4a 100644 --- a/pkg/cntl/playback/types.go +++ b/pkg/cntl/playback/types.go @@ -9,7 +9,7 @@ type TransportWriter interface { // Waiter waits for a trigger to happen type Waiter interface { - Wait(done chan struct{}, cancel chan struct{}, err chan error) error + Wait(done chan struct{}, cancel chan struct{}) error } type storage interface { diff --git a/pkg/cntl/waiter/audio.go b/pkg/cntl/waiter/audio.go index 0e9d69e..af80a23 100644 --- a/pkg/cntl/waiter/audio.go +++ b/pkg/cntl/waiter/audio.go @@ -3,8 +3,9 @@ package waiter import ( "fmt" - "github.com/StageAutoControl/controller/pkg/internal/logging" "github.com/gordonklaus/portaudio" + + "github.com/StageAutoControl/controller/pkg/internal/logging" ) // Audio is a waiter that does nothing @@ -14,35 +15,35 @@ type Audio struct { fanOut []chan struct{} buf []float32 stream *portaudio.Stream - stop chan struct{} + cancel chan struct{} err chan error } // NewAudio creates a new Audio waiter -func NewAudio(logger logging.Logger, threshold float32) (*Audio, error) { - if err := portaudio.Initialize(); err != nil { - return nil, fmt.Errorf("failed to initialize portaudio: %v", err) - } - - buf := make([]float32, 64) - stream, err := portaudio.OpenDefaultStream(1, 0, sampleRate, len(buf), buf) - if err != nil { - return nil, err - } - - a := &Audio{ +func NewAudio(logger logging.Logger, threshold float32) *Audio { + return &Audio{ logger: logger, threshold: threshold, fanOut: make([]chan struct{}, 0), - buf: buf, - stream: stream, - stop: make(chan struct{}, 1), + buf: make([]float32, 64), + cancel: make(chan struct{}, 1), err: make(chan error, 1), } +} + +func (a *Audio) start() (err error) { + if err := portaudio.Initialize(); err != nil { + return fmt.Errorf("failed to initialize portaudio: %v", err) + } + + a.stream, err = portaudio.OpenDefaultStream(1, 0, sampleRate, len(a.buf), a.buf) + if err != nil { + return fmt.Errorf("failed to open default portaudio stream: %v", err) + } go a.readStream() - return a, nil + return nil } func (a *Audio) readStream() { @@ -68,7 +69,7 @@ func (a *Audio) readStream() { a.checkForPeak() select { - case <-a.stop: + case <-a.cancel: return default: } @@ -90,33 +91,34 @@ func (a *Audio) notifyWait() { } } -// Wait waits for a specific event to happen. In this case, nothing. -func (a *Audio) Wait(done chan struct{}, cancel chan struct{}, err chan error) error { +// Wait for a peak in the incoming audio stream +func (a *Audio) Wait(done chan struct{}, cancel chan struct{}) error { + if err := a.start(); err != nil { + return err + } + waitForPeak := make(chan struct{}, 1) a.fanOut = append(a.fanOut, waitForPeak) - // remove channel from fanout, we don't want to have further updates - defer func() { - a.fanOut = a.fanOut[:len(a.fanOut)-1] - }() - +loop: for { select { case <-waitForPeak: a.logger.Info("Found peak. Starting playback!") done <- struct{}{} - return nil + break loop case <-cancel: - return nil - case err := <-a.err: - return err + break loop } } + + a.fanOut = a.fanOut[:len(a.fanOut)-1] + return a.stop() } // Stop stops the audio stream -func (a *Audio) Stop() (err error) { - a.stop <- struct{}{} +func (a *Audio) stop() (err error) { + a.cancel <- struct{}{} defer func() { if err := portaudio.Terminate(); err != nil { diff --git a/pkg/cntl/waiter/none.go b/pkg/cntl/waiter/none.go index d7b7252..df9d540 100644 --- a/pkg/cntl/waiter/none.go +++ b/pkg/cntl/waiter/none.go @@ -13,7 +13,7 @@ func NewNone(logger logging.Logger) *None { } // Wait waits for a specific event to happen. In this case, nothing. -func (t *None) Wait(done chan struct{}, cancel chan struct{}, err chan error) error { +func (t *None) Wait(done chan struct{}, cancel chan struct{}) error { t.logger.Info("Not waiting") done <- struct{}{} return nil From 2ba3b05fae7411456d3b85a072a11d744ca6a4d3 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 11:45:02 +0200 Subject: [PATCH 56/94] Revert to go-artnet fork --- glide.lock | 7 ++++--- glide.yaml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/glide.lock b/glide.lock index c3c3fd5..38d9064 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 66900fc590a9d0613ea5b56313b40ca4a7f352f06fbceafb2629e357a29e6e1f -updated: 2019-04-22T10:38:12.018545+02:00 +hash: 545bc7234b824797dad72e8e0edfde8d0a8792abc3e7deaa15ccd852b25014cb +updated: 2019-04-22T11:44:49.626666+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,8 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 1330e2e425c0491ac9a89453f203dbfbf88b6c75 + version: 964cdb29d889ef234b4a6e8757889a283c61b197 + repo: https://github.com/StageAutoControl/go-artnet.git subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index f928f2b..fe4a317 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,8 +10,8 @@ import: - v2 - v2/json - package: github.com/jsimonetti/go-artnet -# repo: https://github.com/StageAutoControl/go-artnet.git - version: controller-self-logic + repo: https://github.com/StageAutoControl/go-artnet.git +# version: controller-self-logic - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From 7cdf9a7ea384eef4cf451e6fb40229f154f2342c Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 14:38:18 +0200 Subject: [PATCH 57/94] Update go-artnet package --- glide.lock | 6 +++--- glide.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glide.lock b/glide.lock index 38d9064..51c7f91 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 545bc7234b824797dad72e8e0edfde8d0a8792abc3e7deaa15ccd852b25014cb -updated: 2019-04-22T11:44:49.626666+02:00 +hash: 9c0104253964b2541b50307ca3945404a033b411314dbff9fd35abe0b9a9644f +updated: 2019-04-22T14:38:05.559218+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 964cdb29d889ef234b4a6e8757889a283c61b197 + version: fe904701874ddfaecd7f87d337ba8199b99e8ee1 repo: https://github.com/StageAutoControl/go-artnet.git subpackages: - packet diff --git a/glide.yaml b/glide.yaml index fe4a317..b06ed53 100644 --- a/glide.yaml +++ b/glide.yaml @@ -11,7 +11,7 @@ import: - v2/json - package: github.com/jsimonetti/go-artnet repo: https://github.com/StageAutoControl/go-artnet.git -# version: controller-self-logic + version: controller-self-logic - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From 54980266b64cc3c91fac76cfc19f984bec0e2b47 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 15:01:27 +0200 Subject: [PATCH 58/94] Update go-artnet package --- glide.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glide.lock b/glide.lock index 51c7f91..c725663 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 9c0104253964b2541b50307ca3945404a033b411314dbff9fd35abe0b9a9644f -updated: 2019-04-22T14:38:05.559218+02:00 +updated: 2019-04-22T15:01:14.153222+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: fe904701874ddfaecd7f87d337ba8199b99e8ee1 + version: 528cc72e6c51531b15a7f07e35cee3f93fa981cb repo: https://github.com/StageAutoControl/go-artnet.git subpackages: - packet From b2d18a32ed64259c2b79406260257a65242c8f43 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 17:52:34 +0200 Subject: [PATCH 59/94] Use upstream go-artnet master version --- glide.lock | 7 +++---- glide.yaml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/glide.lock b/glide.lock index c725663..b0e5951 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 9c0104253964b2541b50307ca3945404a033b411314dbff9fd35abe0b9a9644f -updated: 2019-04-22T15:01:14.153222+02:00 +hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 +updated: 2019-04-22T17:30:18.910675+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,8 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 528cc72e6c51531b15a7f07e35cee3f93fa981cb - repo: https://github.com/StageAutoControl/go-artnet.git + version: 95b99e5cf00022b85be1abbb668b1a482e01bf8b subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index b06ed53..306b247 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,8 +10,8 @@ import: - v2 - v2/json - package: github.com/jsimonetti/go-artnet - repo: https://github.com/StageAutoControl/go-artnet.git - version: controller-self-logic +# repo: https://github.com/StageAutoControl/go-artnet.git +# version: controller-self-logic - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From d17ed7725a3250f7a87ead9b2ea72a9e5fa08164 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 20:35:47 +0200 Subject: [PATCH 60/94] Stop playback process after finished, aka no more data --- pkg/cntl/playback/process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index c357281..c2fd83a 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -76,7 +76,7 @@ func (p *Process) Start(ctx context.Context) error { return ErrNoSongIDOrSetListIDGiven } - return nil + return p.Stop() } func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { From 54c32c7fa0b08919a315da8b84db5a9fa4f8adbf Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 21:07:59 +0200 Subject: [PATCH 61/94] Handle graceful finishing processes --- pkg/artnet/state.go | 3 +++ pkg/artnet/state_test.go | 2 +- pkg/cntl/playback/process.go | 5 +++++ pkg/process/manager.go | 6 ++++++ pkg/process/types.go | 3 +++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/artnet/state.go b/pkg/artnet/state.go index d2bc37c..c5354fe 100644 --- a/pkg/artnet/state.go +++ b/pkg/artnet/state.go @@ -1,6 +1,7 @@ package artnet import ( + "sort" "sync" ) @@ -89,5 +90,7 @@ func (s *State) GetUniverses() []uint16 { universes = append(universes, u) } + sort.Slice(universes, func(i, j int) bool { return universes[i] < universes[j] }) + return universes } diff --git a/pkg/artnet/state_test.go b/pkg/artnet/state_test.go index a822a0d..e7c2ebc 100644 --- a/pkg/artnet/state_test.go +++ b/pkg/artnet/state_test.go @@ -34,7 +34,7 @@ func TestState_Set(t *testing.T) { au := c.after.GetUniverses() if !reflect.DeepEqual(bu, au) { - t.Errorf("Expected to get universes %+v at case %v, got %+v", au, c, bu) + t.Errorf("Expected to get universes %+v at case %v, got %+v", au, i, bu) continue } diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index c2fd83a..8f6d24b 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -124,3 +124,8 @@ func (p *Process) Stop() error { return nil } + +// Blocking returns true if calling Start() is a blocking operation and the process is stopped after start returned +func (p *Process) Blocking() bool { + return true +} diff --git a/pkg/process/manager.go b/pkg/process/manager.go index 01738e2..fd56667 100644 --- a/pkg/process/manager.go +++ b/pkg/process/manager.go @@ -96,6 +96,11 @@ func (m *manager) Start(name string) (*Status, error) { info.status.Running = false m.logger.Errorf("failed to start process %s: %v", name, err) } + if info.process.Blocking() { + if _, err := m.Stop(name); err != nil { + m.logger.Error(err) + } + } }() return &info.status, nil @@ -118,6 +123,7 @@ func (m *manager) Stop(name string) (*Status, error) { p.status.Running = false p.status.StoppedAt = &JSONTime{Time: time.Now()} + m.logger.Infof("Process %s finished successfully", name) return &p.status, nil } diff --git a/pkg/process/types.go b/pkg/process/types.go index afc09c2..4a8625e 100644 --- a/pkg/process/types.go +++ b/pkg/process/types.go @@ -35,6 +35,9 @@ type Process interface { // Stop should fully stop the process and also clean up any leftovers (state, files, go routines, ...) Stop() error + + // Blocking returns true if calling Start() is a blocking operation and the process is stopped after start returned + Blocking() bool } // Manager to handle a set of processes as pets From 873feb906bedf85675e2edd0f4f1328424d8dc76 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 23:06:18 +0200 Subject: [PATCH 62/94] Improve SetList playback logs --- pkg/cntl/playback/player.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index 77968b6..3fe4ae3 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -6,9 +6,8 @@ import ( "time" "github.com/StageAutoControl/controller/pkg/cntl" - "github.com/StageAutoControl/controller/pkg/internal/logging" - "github.com/StageAutoControl/controller/pkg/cntl/song" + "github.com/StageAutoControl/controller/pkg/internal/logging" ) // Player plays various things from a given data store, for example songs or a whole SetList. @@ -58,7 +57,12 @@ func (p *Player) PlaySetList(ctx context.Context, setListID string) error { default: } - p.logger.Infof("Playing song %s", songID) + s, ok := p.dataStore.Songs[songID] + if !ok { + return fmt.Errorf("failed to find song %v", songID) + } + + p.logger.Infof("Playing song %v", s.Name) if err := p.PlaySong(ctx, songID); err != nil { return err @@ -104,12 +108,19 @@ func (p *Player) PlaySong(ctx context.Context, songID string) error { return err } - p.logger.Infof("Waiting for waiters before playing song %s", songID) + s, ok := p.dataStore.Songs[songID] + if !ok { + return fmt.Errorf("failed to find song %v", songID) + } + + p.logger.Infof("Playing song %v", s.Name) + + p.logger.Infof("Waiting for waiters before playing song %v", s.Name) if err := p.wait(ctx); err != nil { return err } - p.logger.Infof("Playing song %s", songID) + p.logger.Infof("Playing song %v", s.Name) return Play(ctx, p.logger, p.writers, commands) } From 5a1fc429a3d1ed6e4205f9864f22a6f68c885e26 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 22 Apr 2019 23:11:05 +0200 Subject: [PATCH 63/94] Improve SetList playback logs --- pkg/cntl/playback/player.go | 7 ------- pkg/cntl/playback/process.go | 4 +++- pkg/cntl/waiter/audio.go | 33 +++++++++++++++++++-------------- pkg/process/manager.go | 19 ++++++++++++------- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index 3fe4ae3..0834ffb 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -57,13 +57,6 @@ func (p *Player) PlaySetList(ctx context.Context, setListID string) error { default: } - s, ok := p.dataStore.Songs[songID] - if !ok { - return fmt.Errorf("failed to find song %v", songID) - } - - p.logger.Infof("Playing song %v", s.Name) - if err := p.PlaySong(ctx, songID); err != nil { return err } diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index 8f6d24b..93caa3d 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -76,7 +76,9 @@ func (p *Process) Start(ctx context.Context) error { return ErrNoSongIDOrSetListIDGiven } - return p.Stop() + // return p.Stop() + // we don't need to explicitly stop the process when it's done as it's marked as blocking + return nil } func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { diff --git a/pkg/cntl/waiter/audio.go b/pkg/cntl/waiter/audio.go index af80a23..3a82412 100644 --- a/pkg/cntl/waiter/audio.go +++ b/pkg/cntl/waiter/audio.go @@ -27,7 +27,7 @@ func NewAudio(logger logging.Logger, threshold float32) *Audio { fanOut: make([]chan struct{}, 0), buf: make([]float32, 64), cancel: make(chan struct{}, 1), - err: make(chan error, 1), + err: make(chan error, 5), } } @@ -52,13 +52,6 @@ func (a *Audio) readStream() { return } - defer func() { - if err := a.stream.Stop(); err != nil { - a.err <- err - return - } - }() - for { err := a.stream.Read() if err != nil { @@ -71,6 +64,7 @@ func (a *Audio) readStream() { select { case <-a.cancel: return + default: } } @@ -109,6 +103,8 @@ loop: break loop case <-cancel: break loop + case e := <-a.err: + return e } } @@ -120,11 +116,20 @@ loop: func (a *Audio) stop() (err error) { a.cancel <- struct{}{} - defer func() { - if err := portaudio.Terminate(); err != nil { - a.logger.Errorf("failed to terminate portaudio: %v", err) - } - }() + if err := a.stream.Abort(); err != nil { + a.err <- err + a.logger.Errorf("failed to stop portaudio stream: %v", err) + } - return a.stream.Close() + if err := a.stream.Close(); err != nil { + a.err <- err + a.logger.Errorf("failed to close portaudio stream: %v", err) + } + + if err := portaudio.Terminate(); err != nil { + a.logger.Errorf("failed to terminate portaudio: %v", err) + a.err <- err + } + + return nil } diff --git a/pkg/process/manager.go b/pkg/process/manager.go index fd56667..31f7fc6 100644 --- a/pkg/process/manager.go +++ b/pkg/process/manager.go @@ -11,6 +11,7 @@ import ( type processInfo struct { process Process status Status + logger logging.Logger } type manager struct { @@ -38,7 +39,7 @@ func (m *manager) listenExit() { if p, _, err := m.GetProcess(name); err != nil { m.logger.Errorf("failed to find process %q while shutting down: %v", name, err) - } else if err := p.Stop(); err != nil { + } else if err := p.Stop(); err != nil && err != errProcessNotRunning { m.logger.Errorf("failed to stop process %q: %v", name, err) } @@ -86,19 +87,21 @@ func (m *manager) Start(name string) (*Status, error) { info.status.StartedAt = &JSONTime{Time: time.Now()} info.status.StoppedAt = nil info.status.Logs = make([]Log, 0) - - logger := NewBufferedLogger(&info.status.Logs, info.status.Verbose) - info.process.SetLogger(logger) + info.logger = NewBufferedLogger(&info.status.Logs, info.status.Verbose) + info.process.SetLogger(info.logger) go func() { if err := info.process.Start(m.ctx); err != nil { info.status.Error = err info.status.Running = false m.logger.Errorf("failed to start process %s: %v", name, err) + return } + if info.process.Blocking() { - if _, err := m.Stop(name); err != nil { + if _, err := m.Stop(name); err != nil && err != errProcessNotRunning { m.logger.Error(err) + info.logger.Error(err) } } }() @@ -118,12 +121,14 @@ func (m *manager) Stop(name string) (*Status, error) { } if err := p.process.Stop(); err != nil { - return nil, fmt.Errorf("failed to stop process %q: %v", name, err) + err = fmt.Errorf("failed to stop process %q: %v", name, err) + p.logger.Error(err) + return &p.status, err } p.status.Running = false p.status.StoppedAt = &JSONTime{Time: time.Now()} - m.logger.Infof("Process %s finished successfully", name) + p.logger.Infof("Process finished") return &p.status, nil } From c7847451d08bc29c4a0eb3039f3f10c4d138990a Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 23 Apr 2019 20:27:37 +0200 Subject: [PATCH 64/94] Improve error handling on process state --- pkg/process/manager.go | 4 ++-- pkg/process/types.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/process/manager.go b/pkg/process/manager.go index 31f7fc6..e97e11e 100644 --- a/pkg/process/manager.go +++ b/pkg/process/manager.go @@ -83,7 +83,7 @@ func (m *manager) Start(name string) (*Status, error) { } info.status.Running = true - info.status.Error = nil + info.status.Error = "" info.status.StartedAt = &JSONTime{Time: time.Now()} info.status.StoppedAt = nil info.status.Logs = make([]Log, 0) @@ -92,7 +92,7 @@ func (m *manager) Start(name string) (*Status, error) { go func() { if err := info.process.Start(m.ctx); err != nil { - info.status.Error = err + info.status.Error = err.Error() info.status.Running = false m.logger.Errorf("failed to start process %s: %v", name, err) return diff --git a/pkg/process/types.go b/pkg/process/types.go index 4a8625e..091ff9e 100644 --- a/pkg/process/types.go +++ b/pkg/process/types.go @@ -12,7 +12,7 @@ type Status struct { Running bool `json:"running"` StartedAt *JSONTime `json:"startedAt"` StoppedAt *JSONTime `json:"stoppedAt"` - Error error `json:"error"` + Error string `json:"error"` Logs []Log `json:"logs"` Verbose bool `json:"verbose"` } From 35fd5479c51541f90c8a8b8f66f4a616bae1b9bb Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 23 Apr 2019 23:13:46 +0200 Subject: [PATCH 65/94] Reduce controller sender complexity --- pkg/artnet/controller.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index b86b84a..1dbb711 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -93,8 +93,6 @@ func (c *controller) Write(cmd cntl.Command) error { values[i].Value = dmxCmd.Value.Uint8() } - //c.logger.Warnf("%+v", values) - c.SetDMXChannelValues(values) return nil @@ -107,14 +105,9 @@ func (c *controller) triggerSend() { func (c *controller) sendBackground() { for data := range c.sendTrigger { - c.logger.Debug("Sending DMX Values") - c.send(data) - } -} - -func (c *controller) send(data *UniverseStateMap) { - for u, dmx := range *data { - c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u)) + for u, dmx := range *data { + go c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u)) + } } } From c945323995953cfb912b83fa3544d68ac68314a0 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 24 Apr 2019 00:43:37 +0200 Subject: [PATCH 66/94] Enable pprof in http server --- pkg/api/server/server.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 2e81135..72bdbed 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -4,6 +4,12 @@ import ( "context" "fmt" "net/http" + _ "net/http/pprof" + + "github.com/gorilla/handlers" + "github.com/gorilla/rpc" + "github.com/gorilla/rpc/json" + "github.com/sirupsen/logrus" "github.com/StageAutoControl/controller/pkg/api" "github.com/StageAutoControl/controller/pkg/api/datastore" @@ -11,10 +17,6 @@ import ( "github.com/StageAutoControl/controller/pkg/api/playground" "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/process" - "github.com/gorilla/handlers" - "github.com/gorilla/rpc" - "github.com/gorilla/rpc/json" - "github.com/sirupsen/logrus" ) // Server represents the controllers API server, aware of all the controllers @@ -75,7 +77,7 @@ func (s *Server) registerControllers() error { func (s *Server) Run(ctx context.Context, endpoint string) error { s.Server.RegisterCodec(json.NewCodec(), "application/json") - r := http.NewServeMux() + r := http.DefaultServeMux r.Handle(api.RPCPath, s.Server) r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(rw, "OK"); err != nil { From 5de87ea4374cc35a325ea882e7fe122b07d78d53 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 24 Apr 2019 09:13:54 +0200 Subject: [PATCH 67/94] =?UTF-8?q?goroutines=20are=20evil=20=F0=9F=91=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/artnet/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index 1dbb711..d92b7ce 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -106,7 +106,7 @@ func (c *controller) triggerSend() { func (c *controller) sendBackground() { for data := range c.sendTrigger { for u, dmx := range *data { - go c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u)) + c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u)) } } } From 5dcaaee4083cf87d727352b8714bbd552e9eaab5 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 24 Apr 2019 16:34:27 +0200 Subject: [PATCH 68/94] Update dependencies --- glide.lock | 6 +++--- glide.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glide.lock b/glide.lock index b0e5951..e393197 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 -updated: 2019-04-22T17:30:18.910675+02:00 +updated: 2019-04-24T16:34:20.269918+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 95b99e5cf00022b85be1abbb668b1a482e01bf8b + version: 748cf5fe8328f0e39478d3ab6415589887856a5f subpackages: - packet - packet/code @@ -44,7 +44,7 @@ imports: - name: github.com/spf13/viper version: 9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9 - name: golang.org/x/sys - version: e8e3143a4f4a00f1fafef0dd82ba78223281b01b + version: 953cdadca894cdc07be76fc99f95b40c28f06623 subpackages: - unix testImports: [] diff --git a/glide.yaml b/glide.yaml index 306b247..53ed799 100644 --- a/glide.yaml +++ b/glide.yaml @@ -11,7 +11,7 @@ import: - v2/json - package: github.com/jsimonetti/go-artnet # repo: https://github.com/StageAutoControl/go-artnet.git -# version: controller-self-logic +# version: one-conn - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From b5f90b789b6a8a8894170c985847aff2851be771 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Sat, 27 Apr 2019 21:04:57 +0200 Subject: [PATCH 69/94] Fix artnet state.Get, return a copy --- pkg/artnet/controller.go | 9 ++++----- pkg/artnet/state.go | 15 +++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index d92b7ce..b03fdc3 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -21,7 +21,7 @@ type controller struct { logger logging.Logger sender *artnet.Controller state *State - sendTrigger chan *UniverseStateMap + sendTrigger chan UniverseStateMap context context.Context } @@ -49,7 +49,7 @@ func NewController(logger logging.Logger) (Controller, error) { logger: logger, sender: artnet.NewController(host, ip, senderLogger), state: NewState(), - sendTrigger: make(chan *UniverseStateMap, 100), + sendTrigger: make(chan UniverseStateMap, 100), } return control, nil @@ -99,13 +99,12 @@ func (c *controller) Write(cmd cntl.Command) error { } func (c *controller) triggerSend() { - data := c.state.Get() - c.sendTrigger <- &data + c.sendTrigger <- c.state.Get() } func (c *controller) sendBackground() { for data := range c.sendTrigger { - for u, dmx := range *data { + for u, dmx := range data { c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u)) } } diff --git a/pkg/artnet/state.go b/pkg/artnet/state.go index c5354fe..65baa91 100644 --- a/pkg/artnet/state.go +++ b/pkg/artnet/state.go @@ -54,29 +54,32 @@ func (s *State) SetChannelValues(values []ChannelValue) { // SetUniverse sets a complete DMX universe data func (s *State) SetUniverse(u uint16, dmx Universe) { s.m.Lock() - defer s.m.Unlock() s.data[u] = dmx + s.m.Unlock() } // GetUniverse gets a complete DMX universe data func (s *State) GetUniverse(u uint16) Universe { s.m.RLock() - defer s.m.RUnlock() dmx, ok := s.data[u] if !ok { dmx = Universe{} } + s.m.RUnlock() return dmx } // Get returns all of the current state func (s *State) Get() UniverseStateMap { s.m.RLock() - defer s.m.RUnlock() - - return s.data + c := make(UniverseStateMap) + for k, v := range s.data { + c[k] = v + } + s.m.RUnlock() + return c } // GetUniverses returns a slice of all available universe indexes @@ -84,13 +87,13 @@ func (s *State) GetUniverses() []uint16 { universes := make([]uint16, 0) s.m.RLock() - defer s.m.RUnlock() for u := range s.data { universes = append(universes, u) } sort.Slice(universes, func(i, j int) bool { return universes[i] < universes[j] }) + s.m.RUnlock() return universes } From a002c4361b10e6707ece9f6e7dd9a46a424074a0 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 29 Apr 2019 10:48:04 +0200 Subject: [PATCH 70/94] Cleanup portaudio handling --- cmd/audio/dump_input.go | 5 ----- cmd/audio/list_devices.go | 6 ------ cmd/audio/sine.go | 3 --- cmd/root.go | 27 +++++++++++++++++++++++++-- cmd/server.go | 2 -- pkg/cntl/waiter/audio.go | 25 ++++++++----------------- 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/cmd/audio/dump_input.go b/cmd/audio/dump_input.go index f3908a2..93e83ec 100755 --- a/cmd/audio/dump_input.go +++ b/cmd/audio/dump_input.go @@ -24,11 +24,6 @@ var DumpInputCmd = &cobra.Command{ Short: "Dumps the audio input of a device to console", Long: ``, Run: func(cmd *cobra.Command, args []string) { - if err := portaudio.Initialize(); err != nil { - panic(err) - } - defer portaudio.Terminate() - buf := make([]float32, averageSamples) s, err := portaudio.OpenDefaultStream(1, 0, sampleRate, len(buf), buf) if err != nil { diff --git a/cmd/audio/list_devices.go b/cmd/audio/list_devices.go index 05820e9..a543799 100755 --- a/cmd/audio/list_devices.go +++ b/cmd/audio/list_devices.go @@ -17,12 +17,6 @@ var DeviceCmd = &cobra.Command{ Short: "Prints info about all devices", Long: ``, Run: func(cmd *cobra.Command, args []string) { - if err := portaudio.Initialize(); err != nil { - fmt.Println(err) - os.Exit(1) - } - defer portaudio.Terminate() - devices, err := portaudio.Devices() if err != nil { fmt.Println(err) diff --git a/cmd/audio/sine.go b/cmd/audio/sine.go index f6ae357..e2eeaaf 100755 --- a/cmd/audio/sine.go +++ b/cmd/audio/sine.go @@ -22,9 +22,6 @@ var SineCmd = &cobra.Command{ Short: "Creates a sin curved audio", Long: ``, Run: func(cmd *cobra.Command, args []string) { - portaudio.Initialize() - defer portaudio.Terminate() - s := newStereoSine(float64(frequency), sampleRate) defer s.Close() diff --git a/cmd/root.go b/cmd/root.go index 17effbf..1148dda 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,13 +3,17 @@ package cmd import ( + "context" "fmt" "os" - "github.com/StageAutoControl/controller/pkg/artnet" - "github.com/StageAutoControl/controller/pkg/disk" + "github.com/apinnecke/go-exitcontext" + "github.com/gordonklaus/portaudio" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + + "github.com/StageAutoControl/controller/pkg/artnet" + "github.com/StageAutoControl/controller/pkg/disk" ) var ( @@ -20,6 +24,7 @@ var ( loader *disk.Loader controller artnet.Controller disableController bool + ctx context.Context ) // RootCmd represents the base command when called without any subcommands @@ -28,13 +33,31 @@ var RootCmd = &cobra.Command{ Short: "Stage automatic controlling, triggering state changes.", Long: `Automatic stage controlling, including midi and DMX, by analyzing audio signals and pre defined light scenes`, PersistentPreRun: func(cmd *cobra.Command, args []string) { + ctx = exitcontext.New() logger = createLogger(logLevel) storage = createStorage(logger, storagePath) controller = createController(logger, disableController) loader = disk.NewLoader(storage) + + if err := portaudio.Initialize(); err != nil { + logger.Fatalf("failed to initialize portaudio: %v", err) + } + go func() { + <-ctx.Done() + terminateAudio() + }() + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + terminateAudio() }, } +func terminateAudio() { + if err := portaudio.Terminate(); err != nil { + logger.Errorf("failed to terminate portaudio: %v", err) + } +} + // Execute adds all child commands to the root command sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { diff --git a/cmd/server.go b/cmd/server.go index 613fa11..c670dcf 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" - "github.com/apinnecke/go-exitcontext" "github.com/spf13/cobra" apiServer "github.com/StageAutoControl/controller/pkg/api/server" @@ -17,7 +16,6 @@ var serverCmd = &cobra.Command{ Use: "server", Short: "Opens the RPC API to manage the data and control the processes", Run: func(cmd *cobra.Command, args []string) { - ctx := exitcontext.New() pm := process.NewManager(ctx, logger) server, err := apiServer.New(logger.WithField("module", "api"), storage, loader, controller, pm) if err != nil { diff --git a/pkg/cntl/waiter/audio.go b/pkg/cntl/waiter/audio.go index 3a82412..941fc40 100644 --- a/pkg/cntl/waiter/audio.go +++ b/pkg/cntl/waiter/audio.go @@ -32,30 +32,25 @@ func NewAudio(logger logging.Logger, threshold float32) *Audio { } func (a *Audio) start() (err error) { - if err := portaudio.Initialize(); err != nil { - return fmt.Errorf("failed to initialize portaudio: %v", err) - } - a.stream, err = portaudio.OpenDefaultStream(1, 0, sampleRate, len(a.buf), a.buf) if err != nil { return fmt.Errorf("failed to open default portaudio stream: %v", err) } + if err := a.stream.Start(); err != nil { + return fmt.Errorf("failed to start portaudio stream: %v", err) + } + go a.readStream() return nil } func (a *Audio) readStream() { - if err := a.stream.Start(); err != nil { - a.err <- err - return - } - for { err := a.stream.Read() if err != nil { - a.logger.Infof("Error reading audio stream: %s", err) + a.logger.Infof("Error reading portaudio stream: %s", err) return } @@ -98,7 +93,6 @@ loop: for { select { case <-waitForPeak: - a.logger.Info("Found peak. Starting playback!") done <- struct{}{} break loop case <-cancel: @@ -116,19 +110,16 @@ loop: func (a *Audio) stop() (err error) { a.cancel <- struct{}{} - if err := a.stream.Abort(); err != nil { + if err := a.stream.Stop(); err != nil { a.err <- err a.logger.Errorf("failed to stop portaudio stream: %v", err) + return err } if err := a.stream.Close(); err != nil { a.err <- err a.logger.Errorf("failed to close portaudio stream: %v", err) - } - - if err := portaudio.Terminate(); err != nil { - a.logger.Errorf("failed to terminate portaudio: %v", err) - a.err <- err + return err } return nil From 6565c91699d8345a48e70b00b470a0068bd2303d Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 29 Apr 2019 11:16:58 +0200 Subject: [PATCH 71/94] Make transition steps a uint16 --- pkg/cntl/dmx/transition.go | 2 +- pkg/cntl/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cntl/dmx/transition.go b/pkg/cntl/dmx/transition.go index 8b14077..2c2667c 100644 --- a/pkg/cntl/dmx/transition.go +++ b/pkg/cntl/dmx/transition.go @@ -160,7 +160,7 @@ func RenderTransitionParams(ds *cntl.DataStore, dd []*cntl.DMXDevice, t *cntl.DM return result, nil } -func calcTransitionSteps(from, to, steps uint8, easingFunc easingFunc) ([]uint8, error) { +func calcTransitionSteps(from, to uint8, steps uint16, easingFunc easingFunc) ([]uint8, error) { result := make([]uint8, steps) diff := float64(to) - float64(from) floatFrom := float64(from) diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 926e4a1..182ce28 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -157,7 +157,7 @@ type DMXTransition struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` Ease EaseFunc `json:"ease" yaml:"ease"` - Length uint8 `json:"length" yaml:"length"` + Length uint16 `json:"length" yaml:"length"` Params []DMXTransitionParams `json:"params" yaml:"params"` } From d126d375b93e5ed4229203e2b49e18be528afd93 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 30 Apr 2019 20:37:10 +0200 Subject: [PATCH 72/94] Try maxfpx branch --- glide.lock | 6 +++--- glide.yaml | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/glide.lock b/glide.lock index e393197..d0185db 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 -updated: 2019-04-24T16:34:20.269918+02:00 +hash: 74ae0eab7a8591885e68128033eabac0034c15b565b3c1451ee2cb272bb4d2b6 +updated: 2019-04-30T20:36:29.710648+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -22,7 +22,7 @@ imports: - name: github.com/jinzhu/copier version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd - name: github.com/jsimonetti/go-artnet - version: 748cf5fe8328f0e39478d3ab6415589887856a5f + version: cfa1cd18052b6d65c69efca50716b00a16a39f48 subpackages: - packet - packet/code diff --git a/glide.yaml b/glide.yaml index 53ed799..b856e2e 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,26 +1,26 @@ package: github.com/StageAutoControl/controller import: -- package: github.com/sirupsen/logrus -- package: github.com/gordonklaus/portaudio -- package: github.com/gorilla/handlers - version: ^1.2.1 -- package: github.com/gorilla/rpc - version: ^1.1.0 - subpackages: - - v2 - - v2/json -- package: github.com/jsimonetti/go-artnet -# repo: https://github.com/StageAutoControl/go-artnet.git -# version: one-conn -- package: github.com/rakyll/portmidi - version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 -- package: github.com/satori/go.uuid - version: ^1.1.0 -- package: github.com/spf13/cobra -- package: github.com/spf13/viper - version: ^1.0.0 -- package: github.com/creasty/go-easing -- package: github.com/peterbourgon/diskv - version: ^2.0.1 -- package: github.com/jinzhu/copier -- package: github.com/apinnecke/go-exitcontext + - package: github.com/sirupsen/logrus + - package: github.com/gordonklaus/portaudio + - package: github.com/gorilla/handlers + version: ^1.2.1 + - package: github.com/gorilla/rpc + version: ^1.1.0 + subpackages: + - v2 + - v2/json + - package: github.com/jsimonetti/go-artnet + # repo: https://github.com/StageAutoControl/go-artnet.git + version: maxfps + - package: github.com/rakyll/portmidi + version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 + - package: github.com/satori/go.uuid + version: ^1.1.0 + - package: github.com/spf13/cobra + - package: github.com/spf13/viper + version: ^1.0.0 + - package: github.com/creasty/go-easing + - package: github.com/peterbourgon/diskv + version: ^2.0.1 + - package: github.com/jinzhu/copier + - package: github.com/apinnecke/go-exitcontext From b03d892756d7b901f482901b1d73f80fb2a1b336 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 30 Apr 2019 21:09:26 +0200 Subject: [PATCH 73/94] Set maxFPS --- pkg/artnet/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index b03fdc3..77b1fb4 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -47,7 +47,7 @@ func NewController(logger logging.Logger) (Controller, error) { senderLogger := artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet")) control := &controller{ logger: logger, - sender: artnet.NewController(host, ip, senderLogger), + sender: artnet.NewController(host, ip, senderLogger, artnet.MaxFPS(40)), state: NewState(), sendTrigger: make(chan UniverseStateMap, 100), } From 387f24a5944f36e42ab51c2c5282b76cd527c0a5 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 1 May 2019 00:01:01 +0200 Subject: [PATCH 74/94] Remove the MaxFPS controller option --- pkg/artnet/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/artnet/controller.go b/pkg/artnet/controller.go index 77b1fb4..b3713b2 100644 --- a/pkg/artnet/controller.go +++ b/pkg/artnet/controller.go @@ -47,7 +47,7 @@ func NewController(logger logging.Logger) (Controller, error) { senderLogger := artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet")) control := &controller{ logger: logger, - sender: artnet.NewController(host, ip, senderLogger, artnet.MaxFPS(40)), + sender: artnet.NewController(host, ip, senderLogger), // , artnet.MaxFPS(26)), state: NewState(), sendTrigger: make(chan UniverseStateMap, 100), } From 6a3fdee22d28c673dfecb3f8bd621ed62d65d0ba Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 1 May 2019 09:59:53 +0200 Subject: [PATCH 75/94] Fix audio waiter --- pkg/cntl/waiter/audio.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/cntl/waiter/audio.go b/pkg/cntl/waiter/audio.go index 941fc40..c1a40cd 100644 --- a/pkg/cntl/waiter/audio.go +++ b/pkg/cntl/waiter/audio.go @@ -24,14 +24,16 @@ func NewAudio(logger logging.Logger, threshold float32) *Audio { return &Audio{ logger: logger, threshold: threshold, - fanOut: make([]chan struct{}, 0), - buf: make([]float32, 64), - cancel: make(chan struct{}, 1), - err: make(chan error, 5), } } func (a *Audio) start() (err error) { + + a.fanOut = make([]chan struct{}, 0) + a.buf = make([]float32, 64) + a.cancel = make(chan struct{}, 1) + a.err = make(chan error, 5) + a.stream, err = portaudio.OpenDefaultStream(1, 0, sampleRate, len(a.buf), a.buf) if err != nil { return fmt.Errorf("failed to open default portaudio stream: %v", err) @@ -113,7 +115,8 @@ func (a *Audio) stop() (err error) { if err := a.stream.Stop(); err != nil { a.err <- err a.logger.Errorf("failed to stop portaudio stream: %v", err) - return err + // don't return the error, stream.Close has to be called + // return err } if err := a.stream.Close(); err != nil { From 5c7dee03b1975862ee4302d905f17122530373f7 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Mon, 6 May 2019 15:24:36 +0200 Subject: [PATCH 76/94] Simplify audio waiter --- pkg/cntl/waiter/audio.go | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/pkg/cntl/waiter/audio.go b/pkg/cntl/waiter/audio.go index c1a40cd..186f95d 100644 --- a/pkg/cntl/waiter/audio.go +++ b/pkg/cntl/waiter/audio.go @@ -12,7 +12,7 @@ import ( type Audio struct { logger logging.Logger threshold float32 - fanOut []chan struct{} + notify chan struct{} buf []float32 stream *portaudio.Stream cancel chan struct{} @@ -28,8 +28,7 @@ func NewAudio(logger logging.Logger, threshold float32) *Audio { } func (a *Audio) start() (err error) { - - a.fanOut = make([]chan struct{}, 0) + a.notify = make(chan struct{}, 1) a.buf = make([]float32, 64) a.cancel = make(chan struct{}, 1) a.err = make(chan error, 5) @@ -52,34 +51,32 @@ func (a *Audio) readStream() { for { err := a.stream.Read() if err != nil { + a.err <- err a.logger.Infof("Error reading portaudio stream: %s", err) return } - a.checkForPeak() + if a.checkForPeak() { + return + } select { case <-a.cancel: return - default: } } } -func (a *Audio) checkForPeak() { +func (a *Audio) checkForPeak() bool { for _, i := range a.buf { if i >= a.threshold || i <= (a.threshold*-1) { - a.notifyWait() - return + a.notify <- struct{}{} + return true } } -} -func (a *Audio) notifyWait() { - for _, c := range a.fanOut { - c <- struct{}{} - } + return false } // Wait for a peak in the incoming audio stream @@ -88,13 +85,10 @@ func (a *Audio) Wait(done chan struct{}, cancel chan struct{}) error { return err } - waitForPeak := make(chan struct{}, 1) - a.fanOut = append(a.fanOut, waitForPeak) - loop: for { select { - case <-waitForPeak: + case <-a.notify: done <- struct{}{} break loop case <-cancel: @@ -104,7 +98,6 @@ loop: } } - a.fanOut = a.fanOut[:len(a.fanOut)-1] return a.stop() } @@ -125,5 +118,9 @@ func (a *Audio) stop() (err error) { return err } + close(a.notify) + close(a.cancel) + close(a.err) + return nil } From 851c08b818ecb37e7e30a76f6c4c4fde1275be16 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 10 May 2019 18:33:56 +0200 Subject: [PATCH 77/94] Use go-artnet master version --- glide.lock | 4 ++-- glide.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/glide.lock b/glide.lock index d0185db..cbbb501 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 74ae0eab7a8591885e68128033eabac0034c15b565b3c1451ee2cb272bb4d2b6 -updated: 2019-04-30T20:36:29.710648+02:00 +hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 +updated: 2019-05-10T18:33:46.434695+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 diff --git a/glide.yaml b/glide.yaml index b856e2e..afc7f2b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -11,7 +11,7 @@ import: - v2/json - package: github.com/jsimonetti/go-artnet # repo: https://github.com/StageAutoControl/go-artnet.git - version: maxfps + # version: maxfps - package: github.com/rakyll/portmidi version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - package: github.com/satori/go.uuid From 4dca84a3a2202da63659499add8c22300eabbf2a Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 17 May 2019 13:31:54 +0200 Subject: [PATCH 78/94] Add marker to dmx scene positions on songs --- pkg/cntl/types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cntl/types.go b/pkg/cntl/types.go index 182ce28..1e0ed0d 100644 --- a/pkg/cntl/types.go +++ b/pkg/cntl/types.go @@ -25,6 +25,7 @@ type DMXScenePosition struct { ID string `json:"id" yaml:"id"` At uint64 `json:"at" yaml:"at"` Repeat uint8 `json:"repeat" yaml:"repeat"` + Marker string `json:"marker" yaml:"marker"` } // Song is the whole container for everything that needs to be controlled during a song. From a7713a424faad950b94d045627b393e8f54603af Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 28 May 2019 11:39:04 +0200 Subject: [PATCH 79/94] Add gorilla websocket package --- glide.lock | 10 ++++++---- glide.yaml | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/glide.lock b/glide.lock index cbbb501..616535f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: a4fdebacc4ad7b269ce3564aabd9fad9e59e8887361f39abd24634bcc9dbf063 -updated: 2019-05-10T18:33:46.434695+02:00 +hash: 73ebe5508099716cf9b72fbca04a97e51fb1152ac07fbd495148e0a2b997a827 +updated: 2019-05-28T09:02:23.689353+02:00 imports: - name: github.com/apinnecke/go-exitcontext version: 06015046a58d57f896f5e2ea290e6540c3fba863 @@ -17,6 +17,8 @@ imports: - json - v2 - v2/json +- name: github.com/gorilla/websocket + version: 66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/jinzhu/copier @@ -36,13 +38,13 @@ imports: - name: github.com/satori/go.uuid version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 - name: github.com/sirupsen/logrus - version: 9b3cdde74fbe9443d704467498a7dcb61a79de9b + version: 839c75faf7f98a33d445d181f3018b5c3409a45e - name: github.com/spf13/cobra version: 67fc4837d267bc9bfd6e47f77783fcc3dffc68de - name: github.com/spf13/pflag version: 24fa6976df40757dce6aea913e7b81ade90530e1 - name: github.com/spf13/viper - version: 9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9 + version: b5bf975e5823809fb22c7644d008757f78a4259e - name: golang.org/x/sys version: 953cdadca894cdc07be76fc99f95b40c28f06623 subpackages: diff --git a/glide.yaml b/glide.yaml index afc7f2b..a251016 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,26 +1,26 @@ package: github.com/StageAutoControl/controller import: - - package: github.com/sirupsen/logrus - - package: github.com/gordonklaus/portaudio - - package: github.com/gorilla/handlers - version: ^1.2.1 - - package: github.com/gorilla/rpc - version: ^1.1.0 - subpackages: - - v2 - - v2/json - - package: github.com/jsimonetti/go-artnet - # repo: https://github.com/StageAutoControl/go-artnet.git - # version: maxfps - - package: github.com/rakyll/portmidi - version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 - - package: github.com/satori/go.uuid - version: ^1.1.0 - - package: github.com/spf13/cobra - - package: github.com/spf13/viper - version: ^1.0.0 - - package: github.com/creasty/go-easing - - package: github.com/peterbourgon/diskv - version: ^2.0.1 - - package: github.com/jinzhu/copier - - package: github.com/apinnecke/go-exitcontext +- package: github.com/sirupsen/logrus +- package: github.com/gordonklaus/portaudio +- package: github.com/gorilla/handlers + version: ^1.2.1 +- package: github.com/gorilla/rpc + version: ^1.1.0 + subpackages: + - v2 + - v2/json +- package: github.com/jsimonetti/go-artnet +- package: github.com/rakyll/portmidi + version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 +- package: github.com/satori/go.uuid + version: ^1.1.0 +- package: github.com/spf13/cobra +- package: github.com/spf13/viper + version: ^1.0.0 +- package: github.com/creasty/go-easing +- package: github.com/peterbourgon/diskv + version: ^2.0.1 +- package: github.com/jinzhu/copier +- package: github.com/apinnecke/go-exitcontext +- package: github.com/gorilla/websocket + version: ^1.4.0 From c9fa51ac5175605b635e325fd0990849acc8bfc6 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 28 May 2019 11:50:51 +0200 Subject: [PATCH 80/94] Add visualizer endpoint and transport usage --- cmd/playback.go | 7 +- cmd/server.go | 7 +- pkg/api/server/server.go | 26 +++++-- pkg/api/types.go | 3 +- pkg/cntl/playback/const.go | 5 +- pkg/cntl/playback/process.go | 12 +++- pkg/cntl/playback/types.go | 3 + pkg/cntl/transport/visualizer.go | 74 ------------------- pkg/visualizer/client.go | 65 +++++++++++++++++ pkg/visualizer/server.go | 117 +++++++++++++++++++++++++++++++ 10 files changed, 226 insertions(+), 93 deletions(-) delete mode 100644 pkg/cntl/transport/visualizer.go create mode 100644 pkg/visualizer/client.go create mode 100644 pkg/visualizer/server.go diff --git a/cmd/playback.go b/cmd/playback.go index 6b8a9fe..7998108 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -15,6 +15,7 @@ import ( "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/cntl/transport" "github.com/StageAutoControl/controller/pkg/cntl/waiter" + "github.com/StageAutoControl/controller/pkg/visualizer" ) const ( @@ -75,11 +76,7 @@ var playbackCmd = &cobra.Command{ break case transport.TypeVisualizer: - w, err := transport.NewVisualizer(logger.WithField(cntl.LoggerFieldTransport, transport.TypeVisualizer), viualizerEndpoint) - if err != nil { - logger.Fatalf("Unable to connect to the visualizer: %v", err) - } - + w := visualizer.NewServer(logger.WithField(cntl.LoggerFieldTransport, transport.TypeVisualizer)) writers = append(writers, w) break diff --git a/cmd/server.go b/cmd/server.go index c670dcf..81c4329 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -9,6 +9,7 @@ import ( "github.com/StageAutoControl/controller/pkg/cntl/playback" "github.com/StageAutoControl/controller/pkg/disk" "github.com/StageAutoControl/controller/pkg/process" + "github.com/StageAutoControl/controller/pkg/visualizer" ) // serverCmd represents the server command @@ -17,7 +18,9 @@ var serverCmd = &cobra.Command{ Short: "Opens the RPC API to manage the data and control the processes", Run: func(cmd *cobra.Command, args []string) { pm := process.NewManager(ctx, logger) - server, err := apiServer.New(logger.WithField("module", "api"), storage, loader, controller, pm) + visualizer := visualizer.NewServer(logger.WithField("module", "visualizer")) + + server, err := apiServer.New(logger.WithField("module", "api"), storage, loader, controller, pm, visualizer) if err != nil { logger.Fatal(err) } @@ -34,7 +37,7 @@ var serverCmd = &cobra.Command{ if err := playback.EnsureDefaultConfig(storage); err != nil { logger.Fatal(err) } - if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller), true); err != nil { + if err := pm.AddProcess(playback.ProcessName, playback.NewProcess(loader, storage, controller, visualizer), true); err != nil { logger.Fatal(err) } if err := controller.Start(ctx); err != nil { diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 72bdbed..ae6adbd 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -17,6 +17,7 @@ import ( "github.com/StageAutoControl/controller/pkg/api/playground" "github.com/StageAutoControl/controller/pkg/artnet" "github.com/StageAutoControl/controller/pkg/process" + "github.com/StageAutoControl/controller/pkg/visualizer" ) // Server represents the controllers API server, aware of all the controllers @@ -28,17 +29,27 @@ type Server struct { apiController map[string]interface{} cntl artnet.Controller pm process.Manager + visualizer *visualizer.Server } // New returns a new Server instance -func New(logger *logrus.Entry, storage api.Storage, loader api.Loader, cntl artnet.Controller, pm process.Manager) (*Server, error) { +func New( + logger *logrus.Entry, + storage api.Storage, + loader api.Loader, + cntl artnet.Controller, + pm process.Manager, + visualizer *visualizer.Server, +) (*Server, error) { + server := &Server{ - Server: rpc.NewServer(), - logger: logger, - storage: storage, - loader: loader, - cntl: cntl, - pm: pm, + Server: rpc.NewServer(), + logger: logger, + storage: storage, + loader: loader, + cntl: cntl, + pm: pm, + visualizer: visualizer, } if err := server.registerControllers(); err != nil { @@ -79,6 +90,7 @@ func (s *Server) Run(ctx context.Context, endpoint string) error { r := http.DefaultServeMux r.Handle(api.RPCPath, s.Server) + r.HandleFunc(api.VisualizerPath, s.visualizer.ServeRequest) r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(rw, "OK"); err != nil { s.logger.Errorf("failed to write content to response: %v", err) diff --git a/pkg/api/types.go b/pkg/api/types.go index 0ae2205..3891416 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -8,7 +8,8 @@ import ( var ( // RPCPath to where the RPC server should listen on - RPCPath = "/api" + RPCPath = "/api" + VisualizerPath = "/visualizer" // ErrNoIDGiven is returned when the request did not contain a valid ID ErrNoIDGiven = errors.New("no ID was given with request") diff --git a/pkg/cntl/playback/const.go b/pkg/cntl/playback/const.go index b4871bb..49c3932 100644 --- a/pkg/cntl/playback/const.go +++ b/pkg/cntl/playback/const.go @@ -19,13 +19,16 @@ var defaultConfig = ` "waiters": { "audio": { "enabled": true, - "threshold": 0.8 + "threshold": 0.7 } }, "transportWriters": { "artNet": { "enabled": true }, + "visualizer": { + "enabled": true + }, "midi": { "enabled": false, "outputDeviceId": 0 diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index 93caa3d..a0a0d65 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -8,6 +8,7 @@ import ( "github.com/StageAutoControl/controller/pkg/cntl/transport" "github.com/StageAutoControl/controller/pkg/cntl/waiter" "github.com/StageAutoControl/controller/pkg/internal/logging" + "github.com/StageAutoControl/controller/pkg/visualizer" ) // Process handles the playback of a single song @@ -19,14 +20,16 @@ type Process struct { controller artnet.Controller player *Player cancel context.CancelFunc + visualizer *visualizer.Server } // NewProcess returns a new playback process instance -func NewProcess(loader loader, storage storage, controller artnet.Controller) *Process { +func NewProcess(loader loader, storage storage, controller artnet.Controller, visualizer *visualizer.Server) *Process { return &Process{ loader: loader, storage: storage, controller: controller, + visualizer: visualizer, } } @@ -99,8 +102,6 @@ func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { } if config.TransportWriters.MIDI.Enabled { - writers++ - mw, err := transport.NewMIDI(p.logger, config.TransportWriters.MIDI.OutputDeviceID) if err != nil { return nil, fmt.Errorf("failed to create midi transport writer: %v", err) @@ -110,6 +111,11 @@ func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { cfg.writers = append(cfg.writers, mw) } + if config.TransportWriters.Visualizer.Enabled { + writers++ + cfg.writers = append(cfg.writers, p.visualizer) + } + if config.Waiters.Audio.Enabled { cfg.waiters = append(cfg.waiters, waiter.NewAudio(p.logger, config.Waiters.Audio.Threshold)) } diff --git a/pkg/cntl/playback/types.go b/pkg/cntl/playback/types.go index 6eb6e4a..8668660 100644 --- a/pkg/cntl/playback/types.go +++ b/pkg/cntl/playback/types.go @@ -51,6 +51,9 @@ type Config struct { ArtNet struct { Enabled bool `json:"enabled"` } `json:"artNet"` + Visualizer struct { + Enabled bool `json:"enabled"` + } `json:"visualizer"` MIDI struct { Enabled bool `json:"enabled"` OutputDeviceID int8 `json:"outputDeviceId"` diff --git a/pkg/cntl/transport/visualizer.go b/pkg/cntl/transport/visualizer.go deleted file mode 100644 index e3dd9b6..0000000 --- a/pkg/cntl/transport/visualizer.go +++ /dev/null @@ -1,74 +0,0 @@ -package transport - -import ( - "encoding/json" - "fmt" - "net" - "strings" - - "github.com/StageAutoControl/controller/pkg/cntl" - "github.com/StageAutoControl/controller/pkg/internal/logging" -) - -// Visualizer is a writer to the visualizer tool -type Visualizer struct { - logger logging.Logger - endpoint string - socket net.Conn -} - -// NewVisualizer creates a new Visualizer -func NewVisualizer(logger logging.Logger, endpoint string) (*Visualizer, error) { - socket, err := net.Dial("tcp", endpoint) - if err != nil { - return nil, err - } - - return &Visualizer{ - logger: logger, - socket: socket, - endpoint: endpoint, - }, nil -} - -// Write writes to the visualizer stream -func (t *Visualizer) Write(cmd cntl.Command) error { - b, err := json.Marshal(&cmd) - if err != nil { - return err - } - - // append delimiter byte - b = append(b, 0x0) - if n, err := t.socket.Write(b); err != nil { - return err - } else if n == 0 { - return fmt.Errorf("did not sent anything, sent %d bytes", n) - } - - go t.debug(cmd, b) - - return nil -} - -func (t *Visualizer) debug(cs cntl.Command, b []byte) { - t.logger.Debugf("Sent %d commands to visualizer: %v", len(cs.DMXCommands), renderDMXCommands(cs)) -} - -func renderDMXCommands(cmds cntl.Command) string { - s := make([]string, len(cmds.DMXCommands)) - - for i, c := range cmds.DMXCommands { - s[i] = fmt.Sprintf("%d:%d -> %d", c.Universe, c.Channel, c.Value) - } - - return fmt.Sprintf("%s --> %s", renderBarChange(cmds.BarChange), strings.Join(s, " | ")) -} - -func renderBarChange(bc *cntl.BarChange) string { - if bc == nil { - return strings.Repeat(" ", 20) - } - - return fmt.Sprintf("%19s", fmt.Sprintf("#%d %d/%d @%d bpm", bc.At, bc.NoteCount, bc.NoteValue, bc.Speed)) -} diff --git a/pkg/visualizer/client.go b/pkg/visualizer/client.go new file mode 100644 index 0000000..d05ac0c --- /dev/null +++ b/pkg/visualizer/client.go @@ -0,0 +1,65 @@ +package visualizer + +import ( + "github.com/gorilla/websocket" + + "github.com/StageAutoControl/controller/pkg/internal/logging" +) + +var ( + newline = []byte{'\n'} +) + +type client struct { + logger logging.Logger + server *Server + conn *websocket.Conn + send chan []byte +} + +func (c *client) read() { + defer func() { + c.server.unregister <- c + if err := c.conn.Close(); err != nil { + c.logger.Error(err) + } + }() + for { + if _, _, err := c.conn.NextReader(); err != nil { + if err := c.conn.Close(); err != nil { + c.logger.Error(err) + } + break + } + } +} + +func (c *client) stop() { + close(c.send) +} + +func (c *client) write() { + for message := range c.send { + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + if _, err := w.Write(message); err != nil { + c.logger.Error(err) + } + + n := len(c.send) + for i := 0; i < n; i++ { + if _, err := w.Write(newline); err != nil { + c.logger.Error(err) + } + if _, err := w.Write(<-c.send); err != nil { + c.logger.Error(err) + } + } + + if err := w.Close(); err != nil { + return + } + } +} diff --git a/pkg/visualizer/server.go b/pkg/visualizer/server.go new file mode 100644 index 0000000..008678e --- /dev/null +++ b/pkg/visualizer/server.go @@ -0,0 +1,117 @@ +package visualizer + +import ( + "context" + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/websocket" + + "github.com/StageAutoControl/controller/pkg/cntl" + "github.com/StageAutoControl/controller/pkg/internal/logging" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(_ *http.Request) bool { + return true + }, +} + +type Server struct { + logger logging.Logger + clients map[*client]bool + broadcast chan []byte + register chan *client + unregister chan *client +} + +func NewServer(logger logging.Logger) *Server { + return &Server{ + logger: logger, + broadcast: make(chan []byte), + register: make(chan *client), + unregister: make(chan *client), + clients: make(map[*client]bool), + } +} + +// Write the given command to the sockets +func (s *Server) Write(cmd cntl.Command) error { + b, err := json.Marshal(&cmd) + if err != nil { + return err + } + + s.send(b) + return nil +} + +// send the given byte slide to all sockets +func (s *Server) send(msg []byte) { + s.broadcast <- msg +} + +// Run the server +func (s *Server) Run(ctx context.Context) { + go func() { + for message := range s.broadcast { + for client := range s.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(s.clients, client) + } + } + } + }() + + for { + select { + case <-ctx.Done(): + s.stop() + return + case client := <-s.register: + s.clients[client] = true + case client := <-s.unregister: + if _, ok := s.clients[client]; ok { + delete(s.clients, client) + close(client.send) + } + + } + } +} + +func (s *Server) stop() { + close(s.broadcast) + close(s.register) + close(s.unregister) + + for c := range s.clients { + c.stop() + } +} + +// ServeRequest handles incoming http connections and upgrades them +func (s *Server) ServeRequest(rw http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(rw, r, nil) + if err != nil { + log.Println(err) + return + } + client := &client{ + logger: s.logger, + server: s, + conn: conn, + send: make(chan []byte, 1024), + } + s.register <- client + + // we don't care for what the client tells us + go client.read() + client.write() +} From 50f861176e10a0b860e2999d01b21bc82d2f3daf Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Tue, 28 May 2019 12:06:13 +0200 Subject: [PATCH 81/94] Fix visualizer endpoint --- pkg/api/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 3891416..42b91d1 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -9,7 +9,7 @@ import ( var ( // RPCPath to where the RPC server should listen on RPCPath = "/api" - VisualizerPath = "/visualizer" + VisualizerPath = "/visualizer-socket" // ErrNoIDGiven is returned when the request did not contain a valid ID ErrNoIDGiven = errors.New("no ID was given with request") From 2c071b65df5aa035d0cf4920173dd6b8a4411163 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:16:11 +0200 Subject: [PATCH 82/94] Add circle config --- .circleci/config.yml | 88 ++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 6 +-- 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..1eadab5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,88 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/golang:1.12 + working_directory: /go/src/github.com/StageAutoControl/controller + + steps: + - checkout + - setup_remote_docker: + docker_layer_caching: true + + - run: + name: install dependencies + command: | + curl https://glide.sh/get | sh + glide install --strip-vendor + + - run: + name: run tests + command: go test -v ./... + + - run: + name: gometalinter + command: | + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0 + golangci-lint run + + + publish: + docker: + - image: circleci/golang:1.12 + working_directory: /go/src/github.com/StageAutoControl/controller + + steps: + - checkout + - setup_remote_docker: + docker_layer_caching: true + + - run: + name: install dependencies + command: | + + - push-docker: + name: push docker images + command: | + [ "${CIRCLE_BRANCH}" != "master" ] && [ -z "${CIRCLE_TAG}" ] && exit 0 + + export DOCKER_REPO=$(echo "${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" | awk '{print tolower($0)}') + + docker login -u ${DOCKER_USER} -p "${DOCKER_PASS}" https://index.docker.io/v1/ + docker build -t ${DOCKER_REPO} . + + if [ "${CIRCLE_BRANCH}" == "master" ]; then + docker push ${DOCKER_REPO} + fi + + if [ -n "${CIRCLE_TAG}" ]; then + docker tag ${DOCKER_REPO} ${DOCKER_REPO}:${CIRCLE_TAG} + docker push ${DOCKER_REPO}:${CIRCLE_TAG} + fi + - push-artifacts: + name: push artifacts + command: | + [ -z "${CIRCLE_TAG}" ] && exit 0 + + curl https://glide.sh/get | sh + glide install --strip-vendor + go get github.com/tcnksm/ghr + + CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o bin/controller_linux_amd64 . + ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./bin + +workflows: + version: 2 + build: + jobs: + - build: + filters: + tags: + only: /.*/ + branches: + only: /.*/ + - publish: + context: docker + filters: + tags: + only: /.*/ diff --git a/Dockerfile b/Dockerfile index 32d55e5..006c9d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,11 +14,9 @@ RUN apt-get update \ WORKDIR /go/src/github.com/StageAutoControl/controller/ COPY --from=dependencies /go/src/github.com/StageAutoControl/controller/vendor vendor -COPY . ./ - -RUN go test ./... -RUN go build -o bin/controller_amd64 . +COPY . ./ +RUN CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o bin/controller . FROM ubuntu From af15bbfe8465dcbc2a42b54901bef3221cf640b8 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:20:01 +0200 Subject: [PATCH 83/94] Fix publish step format --- .circleci/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1eadab5..5e684cb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,23 +42,23 @@ jobs: command: | - push-docker: - name: push docker images - command: | - [ "${CIRCLE_BRANCH}" != "master" ] && [ -z "${CIRCLE_TAG}" ] && exit 0 + name: push docker images + command: | + [ "${CIRCLE_BRANCH}" != "master" ] && [ -z "${CIRCLE_TAG}" ] && exit 0 - export DOCKER_REPO=$(echo "${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" | awk '{print tolower($0)}') + export DOCKER_REPO=$(echo "${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" | awk '{print tolower($0)}') - docker login -u ${DOCKER_USER} -p "${DOCKER_PASS}" https://index.docker.io/v1/ - docker build -t ${DOCKER_REPO} . + docker login -u ${DOCKER_USER} -p "${DOCKER_PASS}" https://index.docker.io/v1/ + docker build -t ${DOCKER_REPO} . - if [ "${CIRCLE_BRANCH}" == "master" ]; then - docker push ${DOCKER_REPO} - fi + if [ "${CIRCLE_BRANCH}" == "master" ]; then + docker push ${DOCKER_REPO} + fi - if [ -n "${CIRCLE_TAG}" ]; then - docker tag ${DOCKER_REPO} ${DOCKER_REPO}:${CIRCLE_TAG} - docker push ${DOCKER_REPO}:${CIRCLE_TAG} - fi + if [ -n "${CIRCLE_TAG}" ]; then + docker tag ${DOCKER_REPO} ${DOCKER_REPO}:${CIRCLE_TAG} + docker push ${DOCKER_REPO}:${CIRCLE_TAG} + fi - push-artifacts: name: push artifacts command: | From 0630a7afa2c522e6ad0946d5636101deb54cd046 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:24:29 +0200 Subject: [PATCH 84/94] Fix circle config --- .circleci/config.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e684cb..3cbea34 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,8 @@ jobs: curl https://glide.sh/get | sh glide install --strip-vendor + apt-get install -y libportmidi-dev portaudio19-dev + - run: name: run tests command: go test -v ./... @@ -40,8 +42,12 @@ jobs: - run: name: install dependencies command: | + curl https://glide.sh/get | sh + glide install --strip-vendor + + apt-get install -y libportmidi-dev portaudio19-dev - - push-docker: + - deploy: name: push docker images command: | [ "${CIRCLE_BRANCH}" != "master" ] && [ -z "${CIRCLE_TAG}" ] && exit 0 @@ -59,15 +65,13 @@ jobs: docker tag ${DOCKER_REPO} ${DOCKER_REPO}:${CIRCLE_TAG} docker push ${DOCKER_REPO}:${CIRCLE_TAG} fi - - push-artifacts: + + - deploy: name: push artifacts command: | [ -z "${CIRCLE_TAG}" ] && exit 0 - curl https://glide.sh/get | sh - glide install --strip-vendor go get github.com/tcnksm/ghr - CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o bin/controller_linux_amd64 . ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./bin From 9cf089beaef01110c44dbc2276c1748d14b280d4 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:25:38 +0200 Subject: [PATCH 85/94] Add sudo for apt install --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3cbea34..632f26a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,7 +45,7 @@ jobs: curl https://glide.sh/get | sh glide install --strip-vendor - apt-get install -y libportmidi-dev portaudio19-dev + sudo apt-get install -y libportmidi-dev portaudio19-dev - deploy: name: push docker images From daf54fbb4665d87705e677cdda9c3ab6ce50e2c1 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:26:44 +0200 Subject: [PATCH 86/94] Add job dependencies --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 632f26a..3cfd5af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -87,6 +87,8 @@ workflows: only: /.*/ - publish: context: docker + requires: + - build filters: tags: only: /.*/ From 3e9b437328b25972a7d219a66cce7740b69c9b96 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:28:03 +0200 Subject: [PATCH 87/94] Add sudo for apt install --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3cfd5af..877a698 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ jobs: curl https://glide.sh/get | sh glide install --strip-vendor - apt-get install -y libportmidi-dev portaudio19-dev + sudo apt-get install -y libportmidi-dev portaudio19-dev - run: name: run tests From 44612451a2de38ee130c138c514ba225676315f7 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 14:41:12 +0200 Subject: [PATCH 88/94] Fix linter issues --- cmd/audio/dump_input.go | 15 ++++++++++++--- cmd/audio/sine.go | 13 +++++++++++-- cmd/midi/devices.go | 7 ++++++- cmd/midi/dump.go | 7 ++++++- cmd/playback.go | 9 --------- pkg/api/playback/playback.go | 4 ++-- pkg/api/playground/dmx_playground_controller.go | 4 ++-- pkg/cntl/playback/player.go | 8 ++++---- pkg/cntl/playback/process.go | 4 ---- pkg/cntl/types_equals.go | 10 +++++----- pkg/cntl/waiter/const.go | 1 - 11 files changed, 48 insertions(+), 34 deletions(-) diff --git a/cmd/audio/dump_input.go b/cmd/audio/dump_input.go index 93e83ec..6dd6628 100755 --- a/cmd/audio/dump_input.go +++ b/cmd/audio/dump_input.go @@ -5,6 +5,7 @@ package audio import ( "fmt" + "log" "os" "os/signal" @@ -29,16 +30,24 @@ var DumpInputCmd = &cobra.Command{ if err != nil { panic(err) } - defer s.Close() + defer func() { + if err := s.Close(); err != nil { + log.Fatal(err) + } + }() if err := s.Start(); err != nil { panic(err) } - defer s.Stop() + defer func() { + if err := s.Stop(); err != nil { + log.Fatal(err) + } + }() var frame int64 c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, os.Kill) + signal.Notify(c, os.Interrupt, os.Interrupt) fmt.Println("Started listener, dumping input.") diff --git a/cmd/audio/sine.go b/cmd/audio/sine.go index e2eeaaf..225c404 100755 --- a/cmd/audio/sine.go +++ b/cmd/audio/sine.go @@ -4,6 +4,7 @@ package audio import ( + "log" "math" "time" @@ -23,12 +24,20 @@ var SineCmd = &cobra.Command{ Long: ``, Run: func(cmd *cobra.Command, args []string) { s := newStereoSine(float64(frequency), sampleRate) - defer s.Close() + defer func() { + if err := s.Close(); err != nil { + log.Fatal(err) + } + }() if err := s.Start(); err != nil { panic(err) } - defer s.Stop() + defer func() { + if err := s.Stop(); err != nil { + log.Fatal(err) + } + }() time.Sleep(time.Duration(length) * time.Millisecond) }, diff --git a/cmd/midi/devices.go b/cmd/midi/devices.go index 5b6b6dc..477c720 100644 --- a/cmd/midi/devices.go +++ b/cmd/midi/devices.go @@ -5,6 +5,7 @@ package midi import ( "fmt" + "log" "os" "github.com/rakyll/portmidi" @@ -21,7 +22,11 @@ var MidiDeviceCmd = &cobra.Command{ fmt.Println(err) os.Exit(1) } - defer portmidi.Terminate() + defer func() { + if err := portmidi.Terminate(); err != nil { + log.Fatal(err) + } + }() num := portmidi.CountDevices() fmt.Printf("Found %d devices. \n", num) diff --git a/cmd/midi/dump.go b/cmd/midi/dump.go index 5bad297..4674ee6 100644 --- a/cmd/midi/dump.go +++ b/cmd/midi/dump.go @@ -5,6 +5,7 @@ package midi import ( "fmt" + "log" "os" "strconv" @@ -26,7 +27,11 @@ var MidiDumpCmd = &cobra.Command{ fmt.Println(err) os.Exit(1) } - defer portmidi.Terminate() + defer func() { + if err := portmidi.Terminate(); err != nil { + log.Fatal(err) + } + }() var d portmidi.DeviceID if deviceID == "" { diff --git a/cmd/playback.go b/cmd/playback.go index 7998108..b938274 100644 --- a/cmd/playback.go +++ b/cmd/playback.go @@ -69,16 +69,13 @@ var playbackCmd = &cobra.Command{ case transport.TypeStream: writers = append(writers, transport.NewStream(logger.WithField(cntl.LoggerFieldTransport, transport.TypeStream), os.Stdout)) - break case transport.TypeBarLogger: writers = append(writers, transport.NewBarLogger(logger.WithField(cntl.LoggerFieldTransport, transport.TypeBarLogger))) - break case transport.TypeVisualizer: w := visualizer.NewServer(logger.WithField(cntl.LoggerFieldTransport, transport.TypeVisualizer)) writers = append(writers, w) - break case transport.TypeArtNet: controller, err := artnet.NewController(logger.WithField(cntl.LoggerFieldTransport, transport.TypeArtNet)) @@ -92,7 +89,6 @@ var playbackCmd = &cobra.Command{ } writers = append(writers, w) - break case transport.TypeMidi: w, err := transport.NewMIDI(logger.WithField(cntl.LoggerFieldTransport, transport.TypeMidi), midiDeviceID) @@ -101,7 +97,6 @@ var playbackCmd = &cobra.Command{ } writers = append(writers, w) - break default: logger.Fatalf("Transport %q is not supported", transportType) @@ -114,12 +109,9 @@ var playbackCmd = &cobra.Command{ case waiter.TypeNone: waiters = append(waiters, waiter.NewNone(logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeNone))) - break - case waiter.TypeAudio: waiters = append(waiters, waiter.NewAudio(logger.WithField(cntl.LoggerFieldWaiter, waiter.TypeAudio), audioWaiterThreshold)) - break } } @@ -133,7 +125,6 @@ var playbackCmd = &cobra.Command{ logger.Fatal(err) } - break case playbackTypeSetList: setListID := args[1] if err = player.PlaySetList(ctx, setListID); err != nil { diff --git a/pkg/api/playback/playback.go b/pkg/api/playback/playback.go index def7262..830f0a4 100644 --- a/pkg/api/playback/playback.go +++ b/pkg/api/playback/playback.go @@ -57,12 +57,12 @@ func (c *Controller) Start(r *http.Request, req *playback.Params, res *Status) e // Stop a playback func (c *Controller) Stop(r *http.Request, req *api.IDBody, res *Status) error { - p, s, err := c.pm.GetProcess(playback.ProcessName) + p, _, err := c.pm.GetProcess(playback.ProcessName) if err != nil { return fmt.Errorf("failed to get playback status: %v", err) } - s, err = c.pm.Stop(playback.ProcessName) + s, err := c.pm.Stop(playback.ProcessName) if err != nil { return fmt.Errorf("failed to stop playback: %v", err) } diff --git a/pkg/api/playground/dmx_playground_controller.go b/pkg/api/playground/dmx_playground_controller.go index 3c97076..f948509 100644 --- a/pkg/api/playground/dmx_playground_controller.go +++ b/pkg/api/playground/dmx_playground_controller.go @@ -94,7 +94,7 @@ func (c *DMXPlaygroundController) PlayScene(r *http.Request, req *PlayOnceReques c.defaultBarParams(&req.BarParams) commands := playback.ToPlayable(req.BarParams, dmxCommands) if err := playback.Play(context.Background(), c.logger, []playback.TransportWriter{c.controller}, commands); err != nil { - + return fmt.Errorf("failed to start playback: %v", err) } return nil @@ -120,7 +120,7 @@ func (c *DMXPlaygroundController) PlayPreset(r *http.Request, req *PlayOnceReque c.defaultBarParams(&req.BarParams) commands := playback.ToPlayable(req.BarParams, dmxCommands) if err := playback.Play(context.Background(), c.logger, []playback.TransportWriter{c.controller}, commands); err != nil { - + return fmt.Errorf("failed to start playback: %v", err) } return nil diff --git a/pkg/cntl/playback/player.go b/pkg/cntl/playback/player.go index 0834ffb..ed7d8b4 100644 --- a/pkg/cntl/playback/player.go +++ b/pkg/cntl/playback/player.go @@ -79,11 +79,11 @@ func (p *Player) wait(ctx context.Context) error { }() for _, w := range p.waiters { - go func() { + go func(w Waiter) { if err := w.Wait(done, cancel); err != nil { p.logger.Error(err) } - }() + }(w) } select { @@ -148,11 +148,11 @@ func Play(ctx context.Context, logger logging.Logger, writers []TransportWriter, } for _, w := range writers { - go func() { + go func(w TransportWriter) { if err := w.Write(cmd); err != nil { logger.Error(err) } - }() + }(w) } i++ diff --git a/pkg/cntl/playback/process.go b/pkg/cntl/playback/process.go index a0a0d65..4873e4e 100644 --- a/pkg/cntl/playback/process.go +++ b/pkg/cntl/playback/process.go @@ -90,14 +90,12 @@ func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { writers: []TransportWriter{}, } - writers := 0 if config.TransportWriters.ArtNet.Enabled { aw, err := transport.NewArtNet(p.controller) if err != nil { return nil, fmt.Errorf("failed to create artnet transport writer: %v", err) } - writers++ cfg.writers = append(cfg.writers, aw) } @@ -107,12 +105,10 @@ func (p *Process) parseConfig(config *Config) (*parsedConfig, error) { return nil, fmt.Errorf("failed to create midi transport writer: %v", err) } - writers++ cfg.writers = append(cfg.writers, mw) } if config.TransportWriters.Visualizer.Enabled { - writers++ cfg.writers = append(cfg.writers, p.visualizer) } diff --git a/pkg/cntl/types_equals.go b/pkg/cntl/types_equals.go index 0884006..4ff65b2 100644 --- a/pkg/cntl/types_equals.go +++ b/pkg/cntl/types_equals.go @@ -128,7 +128,7 @@ func (v1 DMXParams) Equals(v2 DMXParams) bool { // Equals returns whether the two given objects are equal func (v1 DMXAnimation) Equals(v2 DMXAnimation) bool { - return v2.ID == v2.ID && + return v1.ID == v2.ID && dmxAnimationFrameList(v1.Frames).Equals(dmxAnimationFrameList(v2.Frames)) } @@ -139,10 +139,10 @@ func (v1 DMXAnimationFrame) Equals(v2 DMXAnimationFrame) bool { } // Equals returns whether the two given objects are equal -func (d DMXPreset) Equals(v2 DMXPreset) bool { - return v2.ID == v2.ID && - d.Name == v2.Name && - dmxDeviceParamsList(d.DeviceParams).Equals(dmxDeviceParamsList(v2.DeviceParams)) +func (v1 DMXPreset) Equals(v2 DMXPreset) bool { + return v1.ID == v2.ID && + v1.Name == v2.Name && + dmxDeviceParamsList(v1.DeviceParams).Equals(dmxDeviceParamsList(v2.DeviceParams)) } // Contains returns whether given DMXCommand is in the called collection diff --git a/pkg/cntl/waiter/const.go b/pkg/cntl/waiter/const.go index a9422a0..52c7c4f 100644 --- a/pkg/cntl/waiter/const.go +++ b/pkg/cntl/waiter/const.go @@ -6,5 +6,4 @@ const ( TypeAudio = "audio" sampleRate = 44100 - precision = 0.9 ) From 18b22b51c93359292d53685164cef7e3172037db Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 15:09:33 +0200 Subject: [PATCH 89/94] Fix build, we need to use cgo for portaudio --- .circleci/config.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 877a698..a0c2df4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,7 +72,7 @@ jobs: [ -z "${CIRCLE_TAG}" ] && exit 0 go get github.com/tcnksm/ghr - CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o bin/controller_linux_amd64 . + go build -o bin/controller . ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./bin workflows: diff --git a/Dockerfile b/Dockerfile index 006c9d9..bbf1590 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ WORKDIR /go/src/github.com/StageAutoControl/controller/ COPY --from=dependencies /go/src/github.com/StageAutoControl/controller/vendor vendor COPY . ./ -RUN CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o bin/controller . +RUN go build -o bin/controller . FROM ubuntu From e59ef7b57037cd406fb9b5f8bf084a80bef2f949 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Thu, 20 Jun 2019 15:15:11 +0200 Subject: [PATCH 90/94] Fix docker build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bbf1590..8ee8022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,6 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* WORKDIR /root/ -COPY --from=builder /go/src/github.com/StageAutoControl/controller/bin/controller_amd64 ./controller +COPY --from=builder /go/src/github.com/StageAutoControl/controller/bin/controller ./controller RUN chmod +x ./controller ENTRYPOINT ["./controller"] From 5b6f269ed510d13916858db843a2ddc7dcc016d7 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 17 Jan 2020 10:42:20 +0100 Subject: [PATCH 91/94] Replace glide with mod --- .circleci/config.yml | 10 ----- Dockerfile | 10 ++--- glide.lock | 52 ------------------------- glide.yaml | 26 ------------- go.mod | 25 ++++++++++++ go.sum | 90 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 95 deletions(-) delete mode 100644 glide.lock delete mode 100644 glide.yaml create mode 100644 go.mod create mode 100644 go.sum diff --git a/.circleci/config.yml b/.circleci/config.yml index a0c2df4..7a4886b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,21 +3,11 @@ jobs: build: docker: - image: circleci/golang:1.12 - working_directory: /go/src/github.com/StageAutoControl/controller - steps: - checkout - setup_remote_docker: docker_layer_caching: true - - run: - name: install dependencies - command: | - curl https://glide.sh/get | sh - glide install --strip-vendor - - sudo apt-get install -y libportmidi-dev portaudio19-dev - - run: name: run tests command: go test -v ./... diff --git a/Dockerfile b/Dockerfile index 8ee8022..f9486e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,3 @@ -FROM scalify/glide:0.13.0 as dependencies -WORKDIR /go/src/github.com/StageAutoControl/controller/ - -COPY glide.yaml glide.lock ./ -RUN glide install --strip-vendor - FROM golang:1.10 as builder RUN apt-get update \ @@ -13,7 +7,9 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* WORKDIR /go/src/github.com/StageAutoControl/controller/ -COPY --from=dependencies /go/src/github.com/StageAutoControl/controller/vendor vendor + +COPY go.mod go.sum ./ +RUN go mod download COPY . ./ RUN go build -o bin/controller . diff --git a/glide.lock b/glide.lock deleted file mode 100644 index 616535f..0000000 --- a/glide.lock +++ /dev/null @@ -1,52 +0,0 @@ -hash: 73ebe5508099716cf9b72fbca04a97e51fb1152ac07fbd495148e0a2b997a827 -updated: 2019-05-28T09:02:23.689353+02:00 -imports: -- name: github.com/apinnecke/go-exitcontext - version: 06015046a58d57f896f5e2ea290e6540c3fba863 -- name: github.com/creasty/go-easing - version: 0cfd96d3a544aad2e643739e4a4f6c081b12cda0 -- name: github.com/google/btree - version: 20236160a414454a9c64b6c8829381c6f4bddcaa -- name: github.com/gordonklaus/portaudio - version: 00e7307ccd93051979a933c6fd5ead641eba5686 -- name: github.com/gorilla/handlers - version: 7e0847f9db758cdebd26c149d0ae9d5d0b9c98ce -- name: github.com/gorilla/rpc - version: bffcfa752ad4e523cc8f720afeb5b985ed41ae16 - subpackages: - - json - - v2 - - v2/json -- name: github.com/gorilla/websocket - version: 66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d -- name: github.com/inconshreveable/mousetrap - version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -- name: github.com/jinzhu/copier - version: 7e38e58719c33e0d44d585c4ab477a30f8cb82dd -- name: github.com/jsimonetti/go-artnet - version: cfa1cd18052b6d65c69efca50716b00a16a39f48 - subpackages: - - packet - - packet/code - - version -- name: github.com/konsorten/go-windows-terminal-sequences - version: f55edac94c9bbba5d6182a4be46d86a2c9b5b50e -- name: github.com/peterbourgon/diskv - version: 5f041e8faa004a95c88a202771f4cc3e991971e6 -- name: github.com/rakyll/portmidi - version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 -- name: github.com/satori/go.uuid - version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 -- name: github.com/sirupsen/logrus - version: 839c75faf7f98a33d445d181f3018b5c3409a45e -- name: github.com/spf13/cobra - version: 67fc4837d267bc9bfd6e47f77783fcc3dffc68de -- name: github.com/spf13/pflag - version: 24fa6976df40757dce6aea913e7b81ade90530e1 -- name: github.com/spf13/viper - version: b5bf975e5823809fb22c7644d008757f78a4259e -- name: golang.org/x/sys - version: 953cdadca894cdc07be76fc99f95b40c28f06623 - subpackages: - - unix -testImports: [] diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index a251016..0000000 --- a/glide.yaml +++ /dev/null @@ -1,26 +0,0 @@ -package: github.com/StageAutoControl/controller -import: -- package: github.com/sirupsen/logrus -- package: github.com/gordonklaus/portaudio -- package: github.com/gorilla/handlers - version: ^1.2.1 -- package: github.com/gorilla/rpc - version: ^1.1.0 - subpackages: - - v2 - - v2/json -- package: github.com/jsimonetti/go-artnet -- package: github.com/rakyll/portmidi - version: 1246dd47c56089ea2ae791266edb7f4c6b90c045 -- package: github.com/satori/go.uuid - version: ^1.1.0 -- package: github.com/spf13/cobra -- package: github.com/spf13/viper - version: ^1.0.0 -- package: github.com/creasty/go-easing -- package: github.com/peterbourgon/diskv - version: ^2.0.1 -- package: github.com/jinzhu/copier -- package: github.com/apinnecke/go-exitcontext -- package: github.com/gorilla/websocket - version: ^1.4.0 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..caa53e2 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module github.com/StageAutoControl/controller + +go 1.13 + +require ( + github.com/apinnecke/go-exitcontext v0.0.0-20190131152654-06015046a58d + github.com/creasty/go-easing v0.0.0-20161107103139-0cfd96d3a544 + github.com/google/btree v1.0.1-0.20190326150332-20236160a414 // indirect + github.com/gordonklaus/portaudio v0.0.0-20180817120803-00e7307ccd93 + github.com/gorilla/handlers v1.4.2 + github.com/gorilla/rpc v1.2.0 + github.com/gorilla/websocket v1.4.1 + github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a + github.com/jsimonetti/go-artnet v0.0.0-20191008094654-e099a3930c68 + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible + github.com/rakyll/portmidi v0.0.0-20191102002215-74e95e8bc9b1 + github.com/satori/go.uuid v1.2.0 + github.com/sirupsen/logrus v1.4.2 + github.com/spf13/cobra v0.0.5 + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f314317 --- /dev/null +++ b/go.sum @@ -0,0 +1,90 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/apinnecke/go-exitcontext v0.0.0-20190131152654-06015046a58d h1:IasnhCMwdWF2hSn/wzAfDStDrCLl4vvTA96w4VSM9Hc= +github.com/apinnecke/go-exitcontext v0.0.0-20190131152654-06015046a58d/go.mod h1:AJk9IN8JW08Lq45W1qsMjrwBu1lqqo5sOW+gE8Iuab0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creasty/go-easing v0.0.0-20161107103139-0cfd96d3a544 h1:dSAtlq3+gqQHnEe3ZJdRU9Ur9VnpAqHViAGAH8oWX24= +github.com/creasty/go-easing v0.0.0-20161107103139-0cfd96d3a544/go.mod h1:jK8jBccxnmSEDHLVmvYdZ2RNusC5wW9/iWCNNvvnGsA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/google/btree v1.0.1-0.20190326150332-20236160a414 h1:BciqlM+jKfylRZv6hNtP39myqwtoz6JhgpxurLd8dTY= +github.com/google/btree v1.0.1-0.20190326150332-20236160a414/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/gordonklaus/portaudio v0.0.0-20180817120803-00e7307ccd93 h1:TSG+DyZBnazM22ZHyHLeUkzM34ClkJRjIWHTq4btvek= +github.com/gordonklaus/portaudio v0.0.0-20180817120803-00e7307ccd93/go.mod h1:HfYnZi/ARQKG0dwH5HNDmPCHdLiFiBf+SI7DbhW7et4= +github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= +github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= +github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 h1:sHsPfNMAG70QAvKbddQ0uScZCHQoZsT5NykGRCeeeIs= +github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jsimonetti/go-artnet v0.0.0-20191008094654-e099a3930c68 h1:F+kPtehe174cTiBrkF6P5d31/kWUUaIEb+ueFJ+XxEE= +github.com/jsimonetti/go-artnet v0.0.0-20191008094654-e099a3930c68/go.mod h1:N8sFzz7wHYGJd0Hu30NVGINiKthg2EVQW9E2xBVp9VM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rakyll/portmidi v0.0.0-20170716032345-1246dd47c560 h1:LxQoLCD5lQzG48j4BYI6CnKNSbnXAL5KL1oZfempXcE= +github.com/rakyll/portmidi v0.0.0-20170716032345-1246dd47c560/go.mod h1:tO1ylmFo6+hnYFvj/fd92q30wkNQwgWC/8mcHq0KkQU= +github.com/rakyll/portmidi v0.0.0-20191102002215-74e95e8bc9b1 h1:ayxM9WkC6QRX0QnfrbZa98pfFqrNq1jo+58rWL1qjZs= +github.com/rakyll/portmidi v0.0.0-20191102002215-74e95e8bc9b1/go.mod h1:xKffaBd7e1YUoLpR2azvJqkxEdUDNGNDMlsUNdv6Bcs= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE= +github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.4-0.20181223182923-24fa6976df40 h1:2gwxRRQ5I+FcDbxGtkIC9kWD7EFBewHjQqD8rDQAVQA= +github.com/spf13/pflag v1.0.4-0.20181223182923-24fa6976df40/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From b7a94ea24437c91c0e5c9ecd84ccb1c339dd584d Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Fri, 17 Jan 2020 14:46:45 +0100 Subject: [PATCH 92/94] Update dependencies --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index caa53e2..33acad9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.1 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a - github.com/jsimonetti/go-artnet v0.0.0-20191008094654-e099a3930c68 + github.com/jsimonetti/go-artnet v0.0.0-20200117113556-db828ac108e3 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible diff --git a/go.sum b/go.sum index f314317..6e333e1 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt7 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jsimonetti/go-artnet v0.0.0-20191008094654-e099a3930c68 h1:F+kPtehe174cTiBrkF6P5d31/kWUUaIEb+ueFJ+XxEE= github.com/jsimonetti/go-artnet v0.0.0-20191008094654-e099a3930c68/go.mod h1:N8sFzz7wHYGJd0Hu30NVGINiKthg2EVQW9E2xBVp9VM= +github.com/jsimonetti/go-artnet v0.0.0-20200117113556-db828ac108e3 h1:gsImleLF/P0sgU8zyXPW6GXPfr/u4ul3VD041tKmzoA= +github.com/jsimonetti/go-artnet v0.0.0-20200117113556-db828ac108e3/go.mod h1:N8sFzz7wHYGJd0Hu30NVGINiKthg2EVQW9E2xBVp9VM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From beb24bfee50cfff757a59e630fe417d28b82c501 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 22 Jan 2020 12:08:41 +0100 Subject: [PATCH 93/94] Detect absolute storage paths --- cmd/init.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/init.go b/cmd/init.go index f8c14c0..fefff8a 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -24,6 +24,10 @@ func createLogger(logLevel string) *logrus.Entry { } func createStorage(logger *logrus.Entry, storagePath string) *disk.Storage { + if filepath.IsAbs(storagePath) { + return disk.New(storagePath) + } + cwd, err := os.Getwd() if err != nil { logger.Fatal(err) From c66ceb2e6181718bf67abf2b3c8629a28a8c8842 Mon Sep 17 00:00:00 2001 From: Alexander Pinnecke Date: Wed, 22 Jan 2020 12:53:09 +0100 Subject: [PATCH 94/94] Fix nil pointer of controller not running and playback --- pkg/api/playground/dmx_playground_controller.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/api/playground/dmx_playground_controller.go b/pkg/api/playground/dmx_playground_controller.go index f948509..4cbdb6e 100644 --- a/pkg/api/playground/dmx_playground_controller.go +++ b/pkg/api/playground/dmx_playground_controller.go @@ -76,6 +76,10 @@ func (c *DMXPlaygroundController) defaultBarParams(bp *cntl.BarParams) { // PlayScene plays the given Scene once func (c *DMXPlaygroundController) PlayScene(r *http.Request, req *PlayOnceRequest, response *api.Empty) error { + if c.controller == nil { + return nil + } + ds, err := c.loader.Load() if err != nil { return err @@ -102,6 +106,10 @@ func (c *DMXPlaygroundController) PlayScene(r *http.Request, req *PlayOnceReques // PlayPreset plays the given Preset once func (c *DMXPlaygroundController) PlayPreset(r *http.Request, req *PlayOnceRequest, response *api.Empty) error { + if c.controller == nil { + return nil + } + ds, err := c.loader.Load() if err != nil { return err