150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"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, nil, 0o644); err != nil {
|
|
panic(fmt.Errorf("failed to init data file: %w", err))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (chs *ClientHandlerService) unload(conf map[uuid.UUID]*bootoption.Client) error {
|
|
dat, err := json.Marshal(conf)
|
|
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 {
|
|
return nil, fmt.Errorf("failed to read data file: %w", err)
|
|
}
|
|
if err := json.Unmarshal(dat, &conf); err != nil {
|
|
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) 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
|
|
}
|