add tacking
This commit is contained in:
1
go.mod
1
go.mod
@@ -30,6 +30,7 @@ require (
|
|||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
|
github.com/skillian/getfiletime v0.0.0-20170819221534-37b64ac4de15 // indirect
|
||||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
github.com/swaggo/swag v1.16.3 // indirect
|
github.com/swaggo/swag v1.16.3 // indirect
|
||||||
github.com/urfave/cli/v2 v2.27.2 // indirect
|
github.com/urfave/cli/v2 v2.27.2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -79,6 +79,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/skillian/getfiletime v0.0.0-20170819221534-37b64ac4de15 h1:szp/LEXP+cCpLNKJ1NgC3Vnloo4TYmHv8lrzxng+cXI=
|
||||||
|
github.com/skillian/getfiletime v0.0.0-20170819221534-37b64ac4de15/go.mod h1:QHVxyK6RvbImkJZCSS48T72l5JVj/rA0FerqWGSSvlQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ func Init(di *dig.Container, app *fiber.App) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic("unable to bind routes")
|
panic("unable to bind routes")
|
||||||
}
|
}
|
||||||
|
err = di.Provide(func() *dig.Container {
|
||||||
|
return di
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic("unable to bind dig")
|
||||||
|
}
|
||||||
|
|
||||||
controller.InitializeControllers(di)
|
controller.InitializeControllers(di)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func (ac *ConfigController) updateConfig(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(400).SendString(err.Error())
|
return c.Status(400).SendString(err.Error())
|
||||||
}
|
}
|
||||||
|
log.Print("restart", restart)
|
||||||
if restart {
|
if restart {
|
||||||
_, err := ac.apiService.ApiRestartServer(c)
|
_, err := ac.apiService.ApiRestartServer(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -129,3 +129,11 @@ func (i *IntString) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
return fmt.Errorf("invalid postQualySeconds value")
|
return fmt.Errorf("invalid postQualySeconds value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i IntString) ToString() string {
|
||||||
|
return strconv.Itoa(int(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i IntString) ToInt() (int) {
|
||||||
|
return int(i)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ type Server struct {
|
|||||||
Port int `gorm:"not null" json:"-"`
|
Port int `gorm:"not null" json:"-"`
|
||||||
ConfigPath string `gorm:"not null" json:"-"` // e.g. "/acc/servers/server1/"
|
ConfigPath string `gorm:"not null" json:"-"` // e.g. "/acc/servers/server1/"
|
||||||
ServiceName string `gorm:"not null" json:"-"` // Windows service name
|
ServiceName string `gorm:"not null" json:"-"` // Windows service name
|
||||||
|
State ServerState `gorm:"-" json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerState struct {
|
type PlayerState struct {
|
||||||
@@ -30,10 +31,26 @@ type PlayerState struct {
|
|||||||
IsConnected bool
|
IsConnected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerState struct {
|
type AccServerInstance struct {
|
||||||
sync.RWMutex
|
Model *Server
|
||||||
Session string
|
State *ServerState
|
||||||
PlayerCount int
|
}
|
||||||
Players map[int]*PlayerState
|
|
||||||
|
type State struct {
|
||||||
|
Session string `json:"session"`
|
||||||
|
SessionStart time.Time `json:"sessionStart"`
|
||||||
|
PlayerCount int `json:"playerCount"`
|
||||||
|
// Players map[int]*PlayerState
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerState struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Session string `json:"session"`
|
||||||
|
SessionStart time.Time `json:"sessionStart"`
|
||||||
|
PlayerCount int `json:"playerCount"`
|
||||||
|
Track string `json:"track"`
|
||||||
|
MaxConnections int `json:"maxConnections"`
|
||||||
|
// Players map[int]*PlayerState
|
||||||
// etc.
|
// etc.
|
||||||
}
|
}
|
||||||
@@ -4,25 +4,32 @@ import (
|
|||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
"acc-server-manager/local/utl/common"
|
"acc-server-manager/local/utl/common"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type ApiService struct {
|
type ApiService struct {
|
||||||
repository *repository.ApiRepository
|
repository *repository.ApiRepository
|
||||||
serverRepository *repository.ServerRepository
|
serverRepository *repository.ServerRepository
|
||||||
|
serverService *ServerService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApiService(repository *repository.ApiRepository,
|
func NewApiService(repository *repository.ApiRepository,
|
||||||
serverRepository *repository.ServerRepository) *ApiService {
|
serverRepository *repository.ServerRepository,) *ApiService {
|
||||||
return &ApiService{
|
return &ApiService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
serverRepository: serverRepository,
|
serverRepository: serverRepository,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (as ApiService) SetServerService(serverService *ServerService) {
|
||||||
|
as.serverService = serverService
|
||||||
|
}
|
||||||
|
|
||||||
// GetFirst
|
// GetFirst
|
||||||
// Gets first row from API table.
|
// Gets first row from API table.
|
||||||
//
|
//
|
||||||
@@ -73,15 +80,28 @@ func (as ApiService) StatusServer(serviceName string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (as ApiService) StartServer(serviceName string) (string, error) {
|
func (as ApiService) StartServer(serviceName string) (string, error) {
|
||||||
return ManageService(serviceName, "start")
|
status, err := ManageService(serviceName, "start")
|
||||||
|
|
||||||
|
server := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
|
as.serverService.StartAccServerRuntime(server)
|
||||||
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as ApiService) StopServer(serviceName string) (string, error) {
|
func (as ApiService) StopServer(serviceName string) (string, error) {
|
||||||
return ManageService(serviceName, "stop")
|
status, err := ManageService(serviceName, "stop")
|
||||||
|
|
||||||
|
server := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
|
as.serverService.instances.Delete(server.ID)
|
||||||
|
|
||||||
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as ApiService) RestartServer(serviceName string) (string, error) {
|
func (as ApiService) RestartServer(serviceName string) (string, error) {
|
||||||
return ManageService(serviceName, "restart")
|
status, err := ManageService(serviceName, "restart")
|
||||||
|
|
||||||
|
server := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
|
as.serverService.StartAccServerRuntime(server)
|
||||||
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ManageService(serviceName string, action string) (string, error) {
|
func ManageService(serviceName string, action string) (string, error) {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ func mustDecode[T any](fileName, path string) (T, error) {
|
|||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
repository *repository.ConfigRepository
|
repository *repository.ConfigRepository
|
||||||
serverRepository *repository.ServerRepository
|
serverRepository *repository.ServerRepository
|
||||||
|
serverService *ServerService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigService(repository *repository.ConfigRepository, serverRepository *repository.ServerRepository) *ConfigService {
|
func NewConfigService(repository *repository.ConfigRepository, serverRepository *repository.ServerRepository) *ConfigService {
|
||||||
@@ -72,6 +73,10 @@ func NewConfigService(repository *repository.ConfigRepository, serverRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (as ConfigService) SetServerService(serverService *ServerService) {
|
||||||
|
as.serverService = serverService
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateConfig
|
// UpdateConfig
|
||||||
// Updates physical config file and caches it in database.
|
// Updates physical config file and caches it in database.
|
||||||
//
|
//
|
||||||
@@ -131,6 +136,8 @@ func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
as.serverService.StartAccServerRuntime(server)
|
||||||
|
|
||||||
// Log change
|
// Log change
|
||||||
return as.repository.UpdateConfig(context, &model.Config{
|
return as.repository.UpdateConfig(context, &model.Config{
|
||||||
ServerID: uint(serverID),
|
ServerID: uint(serverID),
|
||||||
|
|||||||
@@ -3,7 +3,15 @@ package service
|
|||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
|
"acc-server-manager/local/utl/tracking"
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -11,13 +19,90 @@ import (
|
|||||||
type ServerService struct {
|
type ServerService struct {
|
||||||
repository *repository.ServerRepository
|
repository *repository.ServerRepository
|
||||||
apiService *ApiService
|
apiService *ApiService
|
||||||
|
instances sync.Map
|
||||||
|
configService *ConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerService(repository *repository.ServerRepository, apiService *ApiService) *ServerService {
|
func NewServerService(repository *repository.ServerRepository, apiService *ApiService, configService *ConfigService) *ServerService {
|
||||||
return &ServerService{
|
service := &ServerService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
apiService: apiService,
|
apiService: apiService,
|
||||||
|
configService: configService,
|
||||||
}
|
}
|
||||||
|
servers := repository.GetAll(context.Background())
|
||||||
|
for _, server := range *servers {
|
||||||
|
status, err := service.apiService.StatusServer(server.ServiceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err.Error())
|
||||||
|
}
|
||||||
|
if (status == string(model.StatusRunning)) {
|
||||||
|
service.StartAccServerRuntime(&server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
var leaderboardUpdateRegex = regexp.MustCompile(`Updated leaderboard for (\d+) clients`)
|
||||||
|
var sessionChangeRegex = regexp.MustCompile(`Session changed: (\w+) -> (\w+)`)
|
||||||
|
|
||||||
|
func handleLogLine(instance *model.AccServerInstance) func(string) {
|
||||||
|
return func (line string) {
|
||||||
|
state := (*instance).State
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if strings.Contains(line, "client(s) online") {
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
countStr := parts[1]
|
||||||
|
if count, err := strconv.Atoi(countStr); err == nil {
|
||||||
|
state.Lock()
|
||||||
|
state.PlayerCount = count
|
||||||
|
state.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(line, "Session changed") {
|
||||||
|
match := sessionChangeRegex.FindStringSubmatch(line)
|
||||||
|
if len(match) == 3 {
|
||||||
|
newSession := match[2]
|
||||||
|
|
||||||
|
state.Lock()
|
||||||
|
state.Session = newSession
|
||||||
|
state.SessionStart = now
|
||||||
|
state.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(line, "Updated leaderboard for") {
|
||||||
|
match := leaderboardUpdateRegex.FindStringSubmatch(line)
|
||||||
|
if len(match) == 2 {
|
||||||
|
if count, err := strconv.Atoi(match[1]); err == nil {
|
||||||
|
state.Lock()
|
||||||
|
state.PlayerCount = count
|
||||||
|
state.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) StartAccServerRuntime(server *model.Server) {
|
||||||
|
s.instances.Delete(server.ID)
|
||||||
|
instance := &model.AccServerInstance{
|
||||||
|
Model: server,
|
||||||
|
State: &model.ServerState{PlayerCount: 0},
|
||||||
|
}
|
||||||
|
config, _ := DecodeFileName(ConfigurationJson)(server.ConfigPath)
|
||||||
|
cfg := config.(model.Configuration)
|
||||||
|
event, _ := DecodeFileName(EventJson)(server.ConfigPath)
|
||||||
|
evt := event.(model.EventConfig)
|
||||||
|
|
||||||
|
instance.State.MaxConnections = cfg.MaxConnections.ToInt()
|
||||||
|
instance.State.Track = evt.Track
|
||||||
|
|
||||||
|
go tracking.TailLogFile(filepath.Join(server.ConfigPath, "\\server\\log\\server.log"), handleLogLine(instance))
|
||||||
|
s.instances.Store(server.ID, instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll
|
// GetAll
|
||||||
@@ -36,6 +121,15 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
|
|||||||
log.Print(err.Error())
|
log.Print(err.Error())
|
||||||
}
|
}
|
||||||
(*servers)[i].Status = model.ServiceStatus(status)
|
(*servers)[i].Status = model.ServiceStatus(status)
|
||||||
|
instance, ok := as.instances.Load(server.ID)
|
||||||
|
if !ok {
|
||||||
|
log.Print("Unable to retrieve instance for server of ID: ", server.ID)
|
||||||
|
} else {
|
||||||
|
serverInstance := instance.(*model.AccServerInstance)
|
||||||
|
if (serverInstance.State != nil) {
|
||||||
|
(*servers)[i].State = *serverInstance.State
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers
|
return servers
|
||||||
@@ -55,7 +149,15 @@ func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) *model.Server {
|
|||||||
log.Print(err.Error())
|
log.Print(err.Error())
|
||||||
}
|
}
|
||||||
server.Status = model.ServiceStatus(status)
|
server.Status = model.ServiceStatus(status)
|
||||||
|
instance, ok := as.instances.Load(server.ID)
|
||||||
|
if !ok {
|
||||||
|
log.Print("Unable to retrieve instance for server of ID: ", server.ID)
|
||||||
|
} else {
|
||||||
|
serverInstance := instance.(*model.AccServerInstance)
|
||||||
|
if (serverInstance.State != nil) {
|
||||||
|
server.State = *serverInstance.State
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
|
"log"
|
||||||
|
|
||||||
"go.uber.org/dig"
|
"go.uber.org/dig"
|
||||||
)
|
)
|
||||||
@@ -18,4 +19,12 @@ func InitializeServices(c *dig.Container) {
|
|||||||
c.Provide(NewApiService)
|
c.Provide(NewApiService)
|
||||||
c.Provide(NewConfigService)
|
c.Provide(NewConfigService)
|
||||||
c.Provide(NewLookupService)
|
c.Provide(NewLookupService)
|
||||||
|
|
||||||
|
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService) {
|
||||||
|
api.SetServerService(server)
|
||||||
|
config.SetServerService(server)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("unable to initialize server service in api service")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
local/utl/tracking/tracking.go
Normal file
24
local/utl/tracking/tracking.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package tracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TailLogFile(path string, callback func(string)) {
|
||||||
|
file, _ := os.Open(path)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
file.Seek(0, os.SEEK_END) // Start at end of file
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err == nil {
|
||||||
|
callback(line)
|
||||||
|
} else {
|
||||||
|
time.Sleep(500 * time.Millisecond) // wait for new data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user