// SPDX-License-Identifier: Apache-2.0 // Copyright © 2022 Roberto Hidalgo package door import ( "fmt" "strings" "sync" "github.com/sirupsen/logrus" ) var ( isOpening bool statusMu sync.Mutex ) type newDoorFunc func(map[string]any) Door var adapters = &struct { factories map[string]newDoorFunc names []string }{ factories: map[string]newDoorFunc{}, names: []string{}, } func _register(name string, factory newDoorFunc) { adapters.factories[name] = factory adapters.names = append(adapters.names, name) } type Door interface { IsOpen() (bool, error) Open(errors chan<- error, done chan<- bool) } func setStatus(status bool) { statusMu.Lock() isOpening = status statusMu.Unlock() } // RequestToEnter opens the door unless it's already open or opening func RequestToEnter(door Door, username string) error { statusMu.Lock() if isOpening { defer statusMu.Unlock() return &DoorCommunicationError{"checking status", fmt.Errorf("Door is busy processing another request")} } isOpen, err := door.IsOpen() if err != nil { statusMu.Unlock() return &DoorCommunicationError{"checking status", err} } else if isOpen { statusMu.Unlock() return &DoorAlreadyOpen{} } // okay, we're triggering an open and preventing others // from doing the same until this function toggles this value again isOpening = true statusMu.Unlock() logrus.Infof("Opening door for %s\n", username) errors := make(chan error, 2) done := make(chan bool) go door.Open(errors, done) if err = <-errors; err != nil { setStatus(false) return &DoorCommunicationError{"opening", err} } logrus.Infof("Door opened for %s", username) go func() { // Door might continue working on stuff after we Open, // wait for done or another error select { case <-done: logrus.Info("REX complete") case err, ok := <-errors: if ok && err != nil { logrus.Errorf("Failed during power off: %s", err) } else if ok { logrus.Info("Door power shut off correctly") } } // now it's safe for others to open the door setStatus(false) }() return nil } func NewDoor(config map[string]any) (Door, error) { adapterName, hasAdapter := config["kind"] if !hasAdapter { return nil, fmt.Errorf("missing DOOR_ADAPTER") } factory, exists := adapters.factories[adapterName.(string)] if !exists { return nil, fmt.Errorf("unknown DOOR_ADAPTER \"%s\", not one of [%s]", adapterName, strings.Join(adapters.names, ",")) } return factory(config), nil }