status updates

This commit is contained in:
Fran Jurmanović
2025-05-06 23:46:02 +02:00
parent d5140783ca
commit a1bcc080f1
9 changed files with 175 additions and 61 deletions

View File

@@ -69,7 +69,7 @@ func (ac *ApiController) getStatus(c *fiber.Ctx) error {
if err != nil {
return c.Status(400).SendString(strings.ReplaceAll(err.Error(), "\x00", ""))
}
return c.SendString(apiModel)
return c.SendString(string(apiModel))
}
// startServer starts service

View File

@@ -58,11 +58,7 @@ func (ac *ConfigController) updateConfig(c *fiber.Ctx) error {
return c.Status(400).SendString(err.Error())
}
if restart {
serviceName, err := ac.apiService.GetServiceName(c)
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Unable to restart service"})
}
ac.apiService.RestartServer(serviceName)
ac.apiService.ApiRestartServer(c)
}
return c.JSON(ConfigModel)

View File

@@ -1,11 +1,13 @@
package model
type ServiceStatus string
const (
StatusRunning ServiceStatus = "SERVICE_RUNNING\r\n"
StatusStopped ServiceStatus = "SERVICE_STOPPED\r\n"
StatusRestarting ServiceStatus = "SERVICE_RESTARTING\r\n"
)
type ApiModel struct {
Api string `json:"api"`
}
// ServiceStatus represents a Windows service state
type ServiceStatus struct {
Name string `json:"name"`
Status string `json:"status"` // "running", "stopped", "pending"
}

View File

@@ -13,11 +13,11 @@ type Config struct {
}
type Configurations struct {
Configuration map[string]interface{} `json:"configuration"`
Entrylist map[string]interface{} `json:"entrylist"`
Event map[string]interface{} `json:"event"`
EventRules map[string]interface{} `json:"eventRules"`
Settings map[string]interface{} `json:"settings"`
Configuration Configuration `json:"configuration"`
AssistRules AssistRules `json:"assistRules"`
Event EventConfig `json:"event"`
EventRules EventRules `json:"eventRules"`
Settings ServerSettings `json:"settings"`
}
type ServerSettings struct {
@@ -64,6 +64,18 @@ type Session struct {
SessionDurationMinutes int `json:"sessionDurationMinutes"`
}
type AssistRules struct {
StabilityControlLevelMax int `json:"stabilityControlLevelMax"`
DisableAutosteer int `json:"disableAutosteer"`
DisableAutoLights int `json:"disableAutoLights"`
DisableAutoWiper int `json:"disableAutoWiper"`
DisableAutoEngineStart int `json:"disableAutoEngineStart"`
DisableAutoPitLimiter int `json:"disableAutoPitLimiter"`
DisableAutoGear int `json:"disableAutoGear"`
DisableAutoClutch int `json:"disableAutoClutch"`
DisableIdealLine int `json:"disableIdealLine"`
}
type EventRules struct {
QualifyStandingType int `json:"qualifyStandingType"`
PitWindowLengthSec int `json:"pitWindowLengthSec"`
@@ -77,3 +89,12 @@ type EventRules struct {
IsMandatoryPitstopSwapDriverRequired bool `json:"isMandatoryPitstopSwapDriverRequired"`
TyreSetCount int `json:"tyreSetCount"`
}
type Configuration struct {
UdpPort int `json:"udpPort"`
TcpPort int `json:"tcpPort"`
MaxConnections int `json:"maxConnections"`
LanDiscovery int `json:"lanDiscovery"`
RegisterToLobby int `json:"registerToLobby"`
ConfigVersion int `json:"configVersion"`
}

View File

@@ -1,12 +1,42 @@
package model
import (
"sync"
"time"
)
// Server represents an ACC server instance
type Server struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null" json:"name"`
Status string `json:"status"`
Status ServiceStatus `json:"status"`
IP string `gorm:"not null" json:"-"`
Port int `gorm:"not null" json:"-"`
ConfigPath string `gorm:"not null" json:"-"` // e.g. "/acc/servers/server1/"
ServiceName string `gorm:"not null" json:"-"` // Windows service name
BroadcastIP string `json:"-"`
BroadcastPort int `json:"-"`
BroadcastPassword string `json:"-"`
}
type PlayerState struct {
CarID int // Car ID in broadcast packets
DriverName string // Optional: pulled from registration packet
TeamName string
CarModel string
CurrentLap int
LastLapTime int // in milliseconds
BestLapTime int // in milliseconds
Position int
ConnectedAt time.Time
DisconnectedAt *time.Time
IsConnected bool
}
type ServerState struct {
sync.RWMutex
Session string
PlayerCount int
Players map[int]*PlayerState
// etc.
}

View File

@@ -62,3 +62,22 @@ func (as ServerRepository) GetAll(ctx context.Context) *[]model.Server {
db.Find(&ServerModel)
return ServerModel
}
// UpdateServer
// Updates Server row from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// model.Server: Server object from database.
func (as ServerRepository) UpdateServer(ctx context.Context, body *model.Server) *model.Server {
db := as.db.WithContext(ctx)
existingServer := new(model.Server)
result := db.Where("id=?", body.ID).First(existingServer)
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
body.ID = existingServer.ID
}
db.Save(body)
return body
}

View File

@@ -17,6 +17,47 @@ import (
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
const (
ConfigurationJson = "configuration.json"
AssistRulesJson = "assistRules.json"
EventJson = "event.json"
EventRulesJson = "eventRules.json"
SettingsJson = "settings.json"
)
var decodeMap = map[string]func(string) (interface{}, error){
ConfigurationJson: func(f string) (interface{}, error) {
return readAndDecode[model.Configuration](f, ConfigurationJson)
},
AssistRulesJson: func(f string) (interface{}, error) {
return readAndDecode[model.AssistRules](f, AssistRulesJson)
},
EventJson: func(f string) (interface{}, error) {
return readAndDecode[model.EventConfig](f, EventJson)
},
EventRulesJson: func(f string) (interface{}, error) {
return readAndDecode[model.EventRules](f, EventRulesJson)
},
SettingsJson: func(f string) (interface{}, error) {
return readAndDecode[model.ServerSettings](f, SettingsJson)
},
}
func DecodeFileName(fileName string) func(path string) (interface{}, error) {
if decoder, ok := decodeMap[fileName]; ok {
return decoder
}
return nil
}
func mustDecode[T any](fileName, path string) (T, error) {
result, err := DecodeFileName(fileName)(path)
if err != nil {
var zero T
return zero, err
}
return result.(T), nil
}
type ConfigService struct {
repository *repository.ConfigRepository
@@ -77,17 +118,32 @@ func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{
return nil, err
}
newDataUTF16, err := EncodeUTF16LEBOM(newData)
if err != nil {
return nil, err
}
context := ctx.UserContext()
if (configFile == ConfigurationJson) {
config, err := DecodeToMap[model.Configuration](newDataUTF16)
if err != nil {
return nil, err
}
if (server.BroadcastPort != config.UdpPort || server.Port != config.TcpPort) {
server.BroadcastPort = config.UdpPort
server.Port = config.TcpPort
as.serverRepository.UpdateServer(context, server)
}
}
if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil {
return nil, err
}
// Log change
return as.repository.UpdateConfig(ctx.UserContext(), &model.Config{
return as.repository.UpdateConfig(context, &model.Config{
ServerID: uint(serverID),
ConfigFile: configFile,
OldConfig: string(oldDataUTF8),
@@ -103,7 +159,7 @@ func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{
// context.Context: Application context
// Returns:
// string: Application version
func (as ConfigService) GetConfig(ctx *fiber.Ctx) (map[string]interface{}, error) {
func (as ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
serverID, _ := ctx.ParamsInt("id")
configFile := ctx.Params("file")
@@ -113,13 +169,7 @@ func (as ConfigService) GetConfig(ctx *fiber.Ctx) (map[string]interface{}, error
return nil, fiber.NewError(404, "Server not found")
}
config, err := readFile(server.ConfigPath, configFile)
if err != nil {
return nil, err
}
decoded, err := DecodeToMap(config)
decoded, err := DecodeFileName(configFile)(server.ConfigPath)
if err != nil {
return nil, err
}
@@ -143,47 +193,27 @@ func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error
return nil, fiber.NewError(404, "Server not found")
}
configuration, err := readFile(server.ConfigPath, "configuration.json")
if err != nil {
return nil, err
}
decodedconfiguration, err := DecodeToMap(configuration)
decodedconfiguration, err := mustDecode[model.Configuration](ConfigurationJson, server.ConfigPath)
if err != nil {
return nil, err
}
entrylist, err := readFile(server.ConfigPath, "entrylist.json")
if err != nil {
return nil, err
}
decodedentrylist, err := DecodeToMap(entrylist)
decodedAssistRules, err := mustDecode[model.AssistRules](AssistRulesJson, server.ConfigPath)
if err != nil {
return nil, err
}
event, err := readFile(server.ConfigPath, "event.json")
if err != nil {
return nil, err
}
decodedevent, err := DecodeToMap(event)
decodedevent, err := mustDecode[model.EventConfig](EventJson, server.ConfigPath)
if err != nil {
return nil, err
}
eventRules, err := readFile(server.ConfigPath, "eventRules.json")
if err != nil {
return nil, err
}
decodedeventRules, err := DecodeToMap(eventRules)
decodedeventRules, err := mustDecode[model.EventRules](EventRulesJson, server.ConfigPath)
if err != nil {
return nil, err
}
settings, err := readFile(server.ConfigPath, "settings.json")
if err != nil {
return nil, err
}
decodedsettings, err := DecodeToMap(settings)
decodedsettings, err := mustDecode[model.ServerSettings](SettingsJson, server.ConfigPath)
if err != nil {
return nil, err
}
@@ -193,10 +223,24 @@ func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error
Event: decodedevent,
EventRules: decodedeventRules,
Settings: decodedsettings,
Entrylist: decodedentrylist,
AssistRules: decodedAssistRules,
}, nil
}
func readAndDecode[T interface{}](path string, configFile string) (T, error) {
settings, err := readFile(path, configFile)
var zero T
if err != nil {
return zero, err
}
decodedsettings, err := DecodeToMap[T](settings)
if err != nil {
return zero, err
}
return decodedsettings, nil
}
func readFile(path string, configFile string) ([]byte, error) {
configPath := filepath.Join(path, "\\server\\cfg", configFile)
oldData, err := os.ReadFile(configPath)
@@ -219,19 +263,20 @@ func DecodeUTF16LEBOM(input []byte) ([]byte, error) {
return transformBytes(decoder.NewDecoder(), input)
}
func DecodeToMap(input []byte) (map[string]interface{}, error) {
func DecodeToMap[T interface{}](input []byte) (T, error) {
var zero T
if input == nil {
return nil, nil
return zero, nil
}
configUTF8 := new(map[string]interface{})
configUTF8 := new(T)
decoded, err := DecodeUTF16LEBOM(input)
if err != nil {
return nil, err
return zero, err
}
err = json.Unmarshal(decoded, configUTF8)
if err != nil {
return nil, err
return zero, err
}
return *configUTF8, nil
}

View File

@@ -31,7 +31,7 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
for i, server := range *servers {
status, _ := as.apiService.StatusServer(server.ServiceName)
(*servers)[i].Status = status
(*servers)[i].Status = model.ServiceStatus(status)
}
return servers
@@ -46,7 +46,8 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
// string: Application version
func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) *model.Server {
server := as.repository.GetFirst(ctx.UserContext(), serverID)
server.Status, _ = as.apiService.StatusServer(server.ServiceName);
status, _ := as.apiService.StatusServer(server.ServiceName)
server.Status = model.ServiceStatus(status)
return server
}

View File

@@ -14,8 +14,8 @@ import (
func InitializeServices(c *dig.Container) {
repository.InitializeRepositories(c)
c.Provide(NewServerService)
c.Provide(NewApiService)
c.Provide(NewConfigService)
c.Provide(NewServerService)
c.Provide(NewLookupService)
}