puerta/internal/door/door.go

112 lines
2.5 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
Active Door
)
type newDoorFunc func(map[string]any) (Door, error)
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(username string) error {
statusMu.Lock()
if isOpening {
defer statusMu.Unlock()
return &ErrorCommunication{"checking status", fmt.Errorf("Door is busy processing another request")}
}
isOpen, err := Active.IsOpen()
if err != nil {
statusMu.Unlock()
return &ErrorCommunication{"checking status", err}
} else if isOpen {
statusMu.Unlock()
return &ErrorAlreadyOpen{}
}
// 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 Active.Open(errors, done)
if err = <-errors; err != nil {
setStatus(false)
return &ErrorCommunication{"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 Connect(config map[string]any) (err error) {
adapterName, hasAdapter := config["kind"]
if !hasAdapter {
return fmt.Errorf("missing DOOR_ADAPTER")
}
factory, exists := adapters.factories[adapterName.(string)]
if !exists {
return fmt.Errorf("unknown DOOR_ADAPTER \"%s\", not one of [%s]", adapterName, strings.Join(adapters.names, ","))
}
Active, err = factory(config)
return err
}