http-boot-server/bootserver/services/services.go

187 lines
4.9 KiB
Go

package services
import (
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"time"
"git.faercol.me/faercol/http-boot-server/bootserver/bootoption"
"git.faercol.me/faercol/http-boot-server/bootserver/filelock"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
var ErrUnknownClient = errors.New("unknown client")
var ErrUnselectedBootOption = errors.New("unselected boot option")
var ErrUnknownBootOption = errors.New("unknown boot option")
const defaultLockTimeout = 1 * time.Second
type ClientHandlerService struct {
filepath string
fileLock *filelock.FileLock
lockTimeout time.Duration
logger *logrus.Logger
}
func NewClientHandlerService(filepath string, logger *logrus.Logger) *ClientHandlerService {
return &ClientHandlerService{
filepath: filepath,
fileLock: filelock.New(filepath),
lockTimeout: defaultLockTimeout,
logger: logger,
}
}
func (chs *ClientHandlerService) Init() {
if _, err := os.Open(chs.filepath); errors.Is(err, os.ErrNotExist) {
if err := os.WriteFile(chs.filepath, []byte("{}"), 0o644); err != nil {
panic(fmt.Errorf("failed to init data file: %w", err))
}
}
chs.fileLock.Unlock()
}
func (chs *ClientHandlerService) unload(conf map[uuid.UUID]*bootoption.Client) error {
dat, err := json.MarshalIndent(conf, "", "\t")
if err != nil {
return fmt.Errorf("failed to marshal data to JSON: %w", err)
}
if err := os.WriteFile(chs.filepath, dat, 0o644); err != nil {
return fmt.Errorf("failed to commit to the data file: %w", err)
}
if err := chs.fileLock.Unlock(); err != nil {
return fmt.Errorf("failed to release the lock to data file: %w", err)
}
return nil
}
func (chs *ClientHandlerService) load() (map[uuid.UUID]*bootoption.Client, error) {
conf := make(map[uuid.UUID]*bootoption.Client)
if err := chs.fileLock.Lock(chs.lockTimeout); err != nil {
return nil, fmt.Errorf("failed to obtain a lock to the data file: %w", err)
}
dat, err := os.ReadFile(chs.filepath)
if err != nil {
defer chs.fileLock.Unlock()
return nil, fmt.Errorf("failed to read data file: %w", err)
}
if err := json.Unmarshal(dat, &conf); err != nil {
defer chs.fileLock.Unlock()
return nil, fmt.Errorf("failed to parse data file: %w", err)
}
return conf, nil
}
func (chs *ClientHandlerService) unloadNoCommmit(conf map[uuid.UUID]*bootoption.Client) {
if err := chs.unload(conf); err != nil {
chs.logger.Errorf("Failed to unload config: %q", err.Error())
}
}
func (chs *ClientHandlerService) AddClient(client *bootoption.Client) (uuid.UUID, error) {
clients, err := chs.load()
if err != nil {
return uuid.Nil, fmt.Errorf("failed to load current config: %w", err)
}
client.ID = uuid.New()
clients[client.ID] = client
if err := chs.unload(clients); err != nil {
return uuid.Nil, fmt.Errorf("failed to save current config: %w", err)
}
return client.ID, nil
}
func (chs *ClientHandlerService) GetAllClientConfig() ([]*bootoption.Client, error) {
clients, err := chs.load()
if err != nil {
return nil, fmt.Errorf("failed to load current config %w", err)
}
defer chs.unloadNoCommmit(clients)
clientList := []*bootoption.Client{}
for id, clt := range clients {
clt.ID = id
clientList = append(clientList, clt)
}
sort.Slice(clientList, func(i, j int) bool {
return clientList[i].ID.String() < clientList[j].ID.String()
})
return clientList, nil
}
func (chs *ClientHandlerService) GetClientConfig(client uuid.UUID) (*bootoption.Client, error) {
clients, err := chs.load()
if err != nil {
return nil, fmt.Errorf("failed to load current config %w", err)
}
defer chs.unloadNoCommmit(clients)
clientDetails, ok := clients[client]
if !ok {
return nil, ErrUnknownClient
}
clientDetails.ID = client
return clientDetails, nil
}
func (chs *ClientHandlerService) GetClientSelectedBootOption(client uuid.UUID) (*bootoption.EFIApp, error) {
clients, err := chs.load()
if err != nil {
return nil, fmt.Errorf("failed to load current config: %w", err)
}
defer chs.unloadNoCommmit(clients)
clientDetails, ok := clients[client]
if !ok {
return nil, ErrUnknownClient
}
if clientDetails.SelectedOption == "" {
return nil, ErrUnselectedBootOption
}
if option, ok := clientDetails.Options[clientDetails.SelectedOption]; !ok {
return nil, ErrUnknownBootOption
} else {
return &option, nil
}
}
func (chs *ClientHandlerService) SetClientBootOption(client uuid.UUID, option string) error {
var err error
clients, err := chs.load()
if err != nil {
return fmt.Errorf("failed to load current config: %w", err)
}
clientDetails, ok := clients[client]
if !ok {
err = ErrUnknownClient
} else {
if _, ok := clientDetails.Options[option]; !ok {
err = ErrUnknownBootOption
} else {
clientDetails.SelectedOption = option
}
}
if err != nil {
defer chs.unloadNoCommmit(clients)
return err
}
if err := chs.unload(clients); err != nil {
return fmt.Errorf("failed to save current config: %w", err)
}
return nil
}