Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Commit

Permalink
added better server state control
Browse files Browse the repository at this point in the history
* added support for spaces in server path

wouldn't install to correct path because i had spaces in my username e.g.: C://Users/jens van de wiel
the above would result in a error

* initial version

* added event when server exits outside of app

* added confirmation modal for force stop

* Update Server.tsx
  • Loading branch information
JensvandeWiel authored Oct 31, 2023
1 parent 08093d6 commit f6ac3b6
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 29 deletions.
81 changes: 67 additions & 14 deletions frontend/src/pages/Server.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import {Button, ButtonGroup, Card, Input, Tab, TabList, Tabs} from "@mui/joy";
import {
Button,
ButtonGroup,
Card, DialogActions,
DialogContent,
DialogTitle,
Divider,
Input,
Modal,
ModalDialog,
Tab,
TabList,
Tabs
} from "@mui/joy";
import {Settings} from "./server/Settings";
import {General} from "./server/General";
import {useEffect, useState} from "react";
import {server} from "../../wailsjs/go/models";
import {
CheckServerInstalled,
ForceStopServer,
GetServer,
GetServer, GetServerStatus,
SaveServer,
StartServer
} from "../../wailsjs/go/server/ServerController";
import {InstallUpdater} from "./InstallUpdater";
import {useAlert} from "../components/AlertProvider";
import {EventsOff, EventsOn} from "../../wailsjs/runtime";
import {IconAlertCircleFilled} from "@tabler/icons-react";


type Props = {
Expand All @@ -30,8 +45,12 @@ export const Server = ({id, className}: Props) => {

const [serv, setServ] = useState<server.Server>(defaultServer)
const [isInstalled, setIsInstalled] = useState(false)
const [serverStatus, setServerStatus] = useState(false)
const [forceStopModalOpen, setForceStopModalOpen] = useState(false)
const {addAlert} = useAlert()

//region useEffect land :)

useEffect(() => {
if (serv.id >= 0) {
CheckServerInstalled(serv.id).then((val) => setIsInstalled(val)).catch((reason) => console.error(reason))
Expand All @@ -40,28 +59,41 @@ export const Server = ({id, className}: Props) => {

useEffect(() => {
if (id !== undefined) {
GetServer(id).then((s) => setServ(s)).catch((reason) => console.error(reason))
GetServer(id).then((s) => {setServ(s)}).catch((reason) => console.error(reason))
}
}, [id]);

useEffect(() => {
if (serv.id == -1) {

} else {
if (serv.id >= 0) {
SaveServer(serv).catch((reason) => console.error(reason))
}


}, [serv]);

useEffect(() => {
EventsOn("onServerExit", () => setServerStatus(false))
return () => EventsOff("onServerExit")
}, []);
//endregion

function onServerStartButtonClicked() {
StartServer(serv.id).catch((err) => {addAlert(err, "danger"); console.error(err)})
StartServer(serv.id).catch((err) => {addAlert(err, "danger"); console.error(err)}).then(() => setTimeout(function () {
refreshServerStatus()
}, 200))

}

function onServerStopButtonClicked() {
ForceStopServer(serv.id).catch((err) => {addAlert(err, "danger"); console.error(err)})
function onServerForceStopButtonClicked() {
ForceStopServer(serv.id).catch((err) => {addAlert(err, "danger"); console.error(err)}).then(() => setServerStatus(false))
}

function refreshServerStatus() {
GetServerStatus(serv.id).catch((reason) => {console.error("serverstatus: " + reason); addAlert(reason, "danger")}).then((s) => {
if (typeof s === "boolean") {
setServerStatus(s)
}
})

}

if (id !== undefined) {
return (
Expand All @@ -73,9 +105,30 @@ export const Server = ({id, className}: Props) => {

<div className={'ml-auto my-auto mr-8'}>
<ButtonGroup aria-label="outlined primary button group">
<Button color={'success'} variant="solid" onClick={onServerStartButtonClicked}>Start</Button>
<Button color={'danger'} variant="solid" onClick={onServerStopButtonClicked}>Stop</Button>
<Button color={'success'} variant="solid" disabled={serverStatus} onClick={onServerStartButtonClicked}>Start</Button>
<Button color={'danger'} variant="solid" disabled={/*!serverStatus*/ true} onClick={onServerStartButtonClicked}>Stop</Button>
<Button color={'danger'} variant="solid" disabled={!serverStatus} onClick={() => setForceStopModalOpen(true)}>Force stop</Button>
</ButtonGroup>
<Modal open={forceStopModalOpen} onClose={() => setForceStopModalOpen(false)}>
<ModalDialog variant="outlined" role="alertdialog">
<DialogTitle>
<IconAlertCircleFilled/>
Confirmation
</DialogTitle>
<Divider />
<DialogContent>
Are you sure you want to forcefully stop the server? No save action will be performed!
</DialogContent>
<DialogActions>
<Button variant="solid" color="danger" onClick={() => {setForceStopModalOpen(false); onServerForceStopButtonClicked()}}>
Force stop
</Button>
<Button variant="plain" color="neutral" onClick={() => setForceStopModalOpen(false)}>
Cancel
</Button>
</DialogActions>
</ModalDialog>
</Modal>
</div>
</div>
<TabList className={'w-full'}>
Expand All @@ -101,4 +154,4 @@ export const Server = ({id, className}: Props) => {
</Card>
);
}
};
};
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.18
require (
github.com/adrg/xdg v0.4.0
github.com/jensvandewiel/gosteamcmd v0.1.2
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19
github.com/sethvargo/go-password v0.2.0
github.com/wailsapp/wails/v2 v2.6.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4P
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jensvandewiel/gosteamcmd v0.1.2 h1:NHichoj0v3GvSVN2Fn36dSOLosHAytpaKnLnDHTMQPI=
github.com/jensvandewiel/gosteamcmd v0.1.2/go.mod h1:Y7hP+iXFIOs8II68VrbdR+W1wwpB8LXYe9lfgjyTLIw=
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68=
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE=
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
Expand Down
3 changes: 1 addition & 2 deletions installer/installer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ func (c *InstallerController) Install(installPath string) error {
steamCMDPath := c.config.Config.SteamCMDPath

prompts := []*gosteamcmd.Prompt{
gosteamcmd.ForceInstallDir(installPath),
gosteamcmd.ForceInstallDir("\"" + installPath + "\""),
gosteamcmd.Login("", "", ""),
gosteamcmd.AppUpdate(2430930, "", true),
}

cmd := gosteamcmd.New(io.Discard, prompts, steamCMDPath)

cmd.Console.Parser.OnInformationReceived = func(action console.Action, progress float64, currentWritten, total uint64) {
actionString := ""

Expand Down
72 changes: 61 additions & 11 deletions server/server.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package server

import (
"context"
"fmt"
"github.com/keybase/go-ps"
"github.com/wailsapp/wails/v2/pkg/runtime"
"os/exec"

"path"
"strconv"
)
Expand All @@ -14,6 +18,7 @@ type GameUserSettings struct {

type Server struct {
Command *exec.Cmd `json:"-"`
ctx context.Context

//CONFIGURATION VARIABLES

Expand Down Expand Up @@ -61,14 +66,19 @@ func (s *Server) Start() error {
return fmt.Errorf("error starting server: failed updating server configuration: %v", err)
}

if s.Command != nil {
return fmt.Errorf("error starting server: server is already started")
}

s.Command = exec.Command(path.Join(s.ServerPath, "ShooterGame\\Binaries\\Win64\\ArkAscendedServer.exe"), s.CreateArguments())
err = s.Command.Start()
if err != nil {
return fmt.Errorf("error starting server: %v", err)
if s.IsServerRunning() {
return fmt.Errorf("error starting server: server is already running")
} else {
s.Command = exec.Command(path.Join(s.ServerPath, "ShooterGame\\Binaries\\Win64\\ArkAscendedServer.exe"), s.CreateArguments())
err = s.Command.Start()
if err != nil {
return fmt.Errorf("error starting server: %v", err)
}
go func() {
_ = s.Command.Wait()

runtime.EventsEmit(s.ctx, "onServerExit", s.Id)
}()
}

return nil
Expand All @@ -78,21 +88,61 @@ func (s *Server) Start() error {
func (s *Server) ForceStop() error {

if s.Command == nil {
return fmt.Errorf("error stopping server: s.Command is nil")
return fmt.Errorf("error stopping server: server is not running")
}
if s.Command.Process == nil {
return fmt.Errorf("error stopping server: server is not running")
}

if !s.IsServerRunning() {
return fmt.Errorf("error stopping server: server is not running")
}

err := s.Command.Process.Kill()
if err != nil {
return fmt.Errorf("error stopping server: %v", err)
}

s.Command = nil

return nil
}

// GetServerStatus returns the status of the server.
func (s *Server) IsServerRunning() bool {

if s.Command == nil {
return false
}

if s.Command.Process == nil {
return false
}

// Retrieve a list of all processes.
processList, err := ps.Processes()
if err != nil {
return false
}

// Iterate through the list of processes and check if the specified PID exists.
processFound := false
for _, process := range processList {
if process.Pid() == s.Command.Process.Pid {
processFound = true
break
}
}

if processFound {
return true
} else {
return false
}
}

func (s *Server) CreateArguments() string {
basePrompt := s.ServerMap + "?listen"
basePrompt += "?MultiHome=" + s.IpAddress
basePrompt += "?SessionName=" + s.ServerName
basePrompt += "?Port=" + strconv.Itoa(s.ServerPort)
basePrompt += "?QueryPort=" + strconv.Itoa(s.QueryPort)
basePrompt += "?RCONEnabled=true?RCONServerGameLogBuffer=600?RCONPort=" + strconv.Itoa(s.RCONPort)
Expand Down
20 changes: 18 additions & 2 deletions server/server_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ func (c *ServerController) GetServerWithError(id int, addToMap bool) (*Server, e
}

runtime.EventsEmit(c.ctx, "gotServer", s.Id)
s.ctx = c.ctx
return &s, nil
} else {
runtime.EventsEmit(c.ctx, "gotServer", server.Id)
server.ctx = c.ctx
return server, nil
}
}
Expand Down Expand Up @@ -107,6 +109,7 @@ func (c *ServerController) CreateServerWithError(saveToConfig bool) (int, *Serve
}

runtime.EventsEmit(c.ctx, "serverCreated", NewServer.Id)
NewServer.ctx = c.ctx

return id, &NewServer, nil
}
Expand Down Expand Up @@ -269,6 +272,7 @@ func (c *ServerController) SaveServer(server Server) error {
}

server.Command = oldServ.Command
server.ctx = c.ctx

err = c.SaveServerWithError(&server)
if err != nil {
Expand Down Expand Up @@ -386,21 +390,33 @@ func (c *ServerController) ForceStopServer(id int) error {

server, exists := c.Servers[id]
if !exists {
err := fmt.Errorf("error starting server " + strconv.Itoa(id) + ": server does not exist in map")
err := fmt.Errorf("error stopping server " + strconv.Itoa(id) + ": server does not exist in map")
runtime.LogError(c.ctx, err.Error())
return err
}

err := server.ForceStop()
if err != nil {
err := fmt.Errorf("error starting server " + strconv.Itoa(id) + ": " + err.Error())
err := fmt.Errorf("error stopping server " + strconv.Itoa(id) + ": " + err.Error())
runtime.LogError(c.ctx, err.Error())
return err
}

return nil
}

func (c *ServerController) GetServerStatus(id int) (bool, error) {
server, exists := c.Servers[id]
if !exists {
err := fmt.Errorf("error getting server status " + strconv.Itoa(id) + ": server does not exist in map")
runtime.LogError(c.ctx, err.Error())
return false, err
}

status := server.IsServerRunning()
return status, nil
}

//endregion

//region ServerController helper functions
Expand Down

0 comments on commit f6ac3b6

Please sign in to comment.