diff --git a/local/controller/api.go b/local/controller/api.go index a8489fe..03444fe 100644 --- a/local/controller/api.go +++ b/local/controller/api.go @@ -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 diff --git a/local/controller/config.go b/local/controller/config.go index eb76c99..3dbc4bc 100644 --- a/local/controller/config.go +++ b/local/controller/config.go @@ -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) diff --git a/local/model/api.go b/local/model/api.go index 9ec7cb7..c95b626 100644 --- a/local/model/api.go +++ b/local/model/api.go @@ -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" -} diff --git a/local/model/config.go b/local/model/config.go index e60db2c..f0b9be8 100644 --- a/local/model/config.go +++ b/local/model/config.go @@ -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"` +} diff --git a/local/model/server.go b/local/model/server.go index d3a33a2..3225864 100644 --- a/local/model/server.go +++ b/local/model/server.go @@ -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. +} \ No newline at end of file diff --git a/local/repository/server.go b/local/repository/server.go index 365675f..9ab2089 100644 --- a/local/repository/server.go +++ b/local/repository/server.go @@ -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 +} diff --git a/local/service/config.go b/local/service/config.go index 869de21..5078975 100644 --- a/local/service/config.go +++ b/local/service/config.go @@ -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 } diff --git a/local/service/server.go b/local/service/server.go index bd67394..5fa2abb 100644 --- a/local/service/server.go +++ b/local/service/server.go @@ -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 } diff --git a/local/service/service.go b/local/service/service.go index 2c75aa8..42e3dc6 100644 --- a/local/service/service.go +++ b/local/service/service.go @@ -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) }