Merge pull request #77 from JakenVeina/server-save-on-stop-windows

Stop & Save functionality on Windows, and additional Kill functionality.
This commit is contained in:
Mitch Roote 2017-06-02 08:26:47 -04:00 committed by GitHub
commit f33e540393
7 changed files with 178 additions and 17 deletions

View File

@ -4,16 +4,18 @@
NODE_ENV:=production
build:
# make sure this project is located within GOPATH, I.E. $GOPATH/src/factorio-server-manager
# Build Linux release
mkdir build
GOOS=linux GOARCH=amd64 go build -o factorio-server-manager/factorio-server-manager src/*
GOOS=linux GOARCH=amd64 go build -o factorio-server-manager/factorio-server-manager factorio-server-manager/src
# ui/node_modules/webpack/bin/webpack.js ui/webpack.config.js app/bundle.js --progress --profile --colors
cp -r app/ factorio-server-manager/
cp conf.json.example factorio-server-manager/conf.json
zip -r build/factorio-server-manager-linux-x64.zip factorio-server-manager
rm -rf factorio-server-manager
# Build Windows release
GOOS=windows GOARCH=386 go build -o factorio-server-manager/factorio-server-manager.exe src/*
GOOS=windows GOARCH=386 go build -o factorio-server-manager/factorio-server-manager.exe factorio-server-manager/src
cp -r app/ factorio-server-manager/
cp conf.json.example factorio-server-manager/conf.json
zip -r build/factorio-server-manager-windows.zip factorio-server-manager
@ -21,7 +23,7 @@ build:
dev:
mkdir dev
GOOS=linux GOARCH=amd64 go build -o factorio-server-linux/factorio-server-manager src/*
GOOS=linux GOARCH=amd64 go build -o factorio-server-linux/factorio-server-manager factorio-server-manager/src
cp -r app/ dev/
cp conf.json.example dev/conf.json
mv factorio-server-linux/factorio-server-manager dev/factorio-server-manager

View File

@ -14,6 +14,7 @@ import (
"runtime"
"strconv"
"strings"
"time"
"github.com/majormjr/rcon"
)
@ -225,21 +226,27 @@ func (f *FactorioServer) checkLogError(logline []string) error {
}
func (f *FactorioServer) Stop() error {
// TODO: Find an alternative to os.Kill on Windows. os.Interupt
// is not implemented. Maps will not be saved.
if runtime.GOOS == "windows" {
err := f.Cmd.Process.Signal(os.Kill)
if err != nil {
if err.Error() == "os: process already finished" {
f.Running = false
return err
}
log.Printf("Error sending SIGKILLL to Factorio process: %s", err)
return err
}
f.Running = false
log.Println("Sent SIGKILL to Factorio process. Factorio forced to exit.")
// Disable our own handling of CTRL+C, so we don't close when we send it to the console.
setCtrlHandlingIsDisabledForThisProcess(true)
// Send CTRL+C to all processes attached to the console (ourself, and the factorio server instance)
sendCtrlCToPid(0)
log.Println("Sent SIGINT to Factorio process. Factorio shutting down...")
// Somehow, the Factorio devs managed to code the game to react appropriately to CTRL+C, including
// saving the game, but not actually exit. So, we still have to manually kill the process, and
// for extra fun, there's no way to know when the server save has actually completed (unless we want
// to inject filesystem logic into what should be a process-level Stop() routine), so our best option
// is to just wait an arbitrary amount of time and hope that the save is successful in that time.
time.Sleep(2 * time.Second)
f.Cmd.Process.Signal(os.Kill)
// Re-enable handling of CTRL+C after we're sure that the factrio server is shut down.
setCtrlHandlingIsDisabledForThisProcess(false)
f.Running = false
return nil
}
@ -262,3 +269,43 @@ func (f *FactorioServer) Stop() error {
return nil
}
func (f *FactorioServer) Kill() error {
if runtime.GOOS == "windows" {
err := f.Cmd.Process.Signal(os.Kill)
if err != nil {
if err.Error() == "os: process already finished" {
f.Running = false
return err
}
log.Printf("Error sending SIGKILL to Factorio process: %s", err)
return err
}
f.Running = false
log.Println("Sent SIGKILL to Factorio process. Factorio forced to exit.")
return nil
}
err := f.Cmd.Process.Signal(os.Kill)
if err != nil {
if err.Error() == "os: process already finished" {
f.Running = false
return err
}
log.Printf("Error sending SIGKILL to Factorio process: %s", err)
return err
}
f.Running = false
log.Printf("Sent SIGKILL to Factorio process. Factorio forced to exit.")
err = f.Rcon.Close()
if err != nil {
log.Printf("Error close rcon connection: %s", err)
}
return nil
}

View File

@ -0,0 +1,10 @@
package main
// Stubs for windows-only functions
func sendCtrlCToPid(pid int) {
}
func setCtrlHandlingIsDisabledForThisProcess(disabled bool) {
}

View File

@ -0,0 +1,41 @@
package main
import (
"log"
"syscall"
)
func sendCtrlCToPid(pid int) {
d, e := syscall.LoadDLL("kernel32.dll")
if e != nil {
log.Fatalf("LoadDLL: %v\n", e)
}
p, e := d.FindProc("GenerateConsoleCtrlEvent")
if e != nil {
log.Fatalf("FindProc: %v\n", e)
}
r, _, e := p.Call(uintptr(syscall.CTRL_C_EVENT), uintptr(pid))
if r == 0 {
log.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
}
}
func setCtrlHandlingIsDisabledForThisProcess(disabled bool) {
disabledInt := 0
if(disabled){
disabledInt = 1
}
d, e := syscall.LoadDLL("kernel32.dll")
if e != nil {
log.Fatalf("LoadDLL: %v\n", e)
}
p, e := d.FindProc("SetConsoleCtrlHandler")
if e != nil {
log.Fatalf("FindProc: %v\n", e)
}
r, _, e := p.Call(uintptr(0), uintptr(disabledInt))
if r == 0 {
log.Fatalf("SetConsoleCtrlHandler: %v\n", e)
}
}

View File

@ -667,6 +667,41 @@ func StopServer(w http.ResponseWriter, r *http.Request) {
}
}
func KillServer(w http.ResponseWriter, r *http.Request) {
resp := JSONResponse{
Success: false,
}
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
if FactorioServ.Running {
err := FactorioServ.Kill()
if err != nil {
log.Printf("Error in kill server handler: %s", err)
resp.Data = fmt.Sprintf("Error in kill server handler: %s", err)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
return
}
log.Printf("Killed Factorio server.")
resp.Success = true
resp.Data = fmt.Sprintf("Factorio server killed")
} else {
resp.Data = "Factorio server is not running"
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
return
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
}
func CheckServer(w http.ResponseWriter, r *http.Request) {
resp := JSONResponse{
Success: false,

View File

@ -228,6 +228,11 @@ var apiRoutes = Routes{
"GET",
"/server/stop",
StopServer,
}, {
"KillServer",
"GET",
"/server/kill",
KillServer,
}, {
"RunningServer",
"GET",

View File

@ -5,6 +5,7 @@ class ServerCtl extends React.Component {
super(props);
this.startServer = this.startServer.bind(this);
this.stopServer = this.stopServer.bind(this);
this.killServer = this.killServer.bind(this);
this.incrementPort = this.incrementPort.bind(this);
this.decrementPort = this.decrementPort.bind(this);
@ -58,6 +59,21 @@ class ServerCtl extends React.Component {
e.preventDefault();
}
killServer(e) {
$.ajax({
type: "GET",
url: "/api/server/kill",
dataType: "json",
success: (resp) => {
this.props.facServStatus();
this.props.getStatus();
console.log(resp)
swal(resp.data)
}
});
e.preventDefault();
}
incrementPort() {
let port = this.state.port + 1;
this.setState({port: port})
@ -85,7 +101,11 @@ class ServerCtl extends React.Component {
</div>
<div className="col-md-4">
<button className="btn btn-block btn-danger" type="button" onClick={this.stopServer}><i className="fa fa-stop fa-fw"></i>Stop Factorio Server</button>
<button className="btn btn-block btn-warning" type="button" onClick={this.stopServer}><i className="fa fa-stop fa-fw"></i>Stop &amp; Save Factorio Server</button>
</div>
<div className="col-md-4">
<button className="btn btn-block btn-danger" type="button" onClick={this.killServer}><i className="fa fa-close fa-fw"></i>Stop Factorio Server without Saving</button>
</div>
</div>
@ -132,3 +152,4 @@ ServerCtl.propTypes = {
}
export default ServerCtl