110 lines
2.4 KiB
Go
110 lines
2.4 KiB
Go
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
// Copyright © 2022 Roberto Hidalgo <nidito@un.rob.mx>
|
||
|
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
|
||
|
}
|