Skip to content

Commit f33e540

Browse files
authored
Merge pull request #77 from JakenVeina/server-save-on-stop-windows
Stop & Save functionality on Windows, and additional Kill functionality.
2 parents 63799ae + c4784f5 commit f33e540

File tree

7 files changed

+171
-10
lines changed

7 files changed

+171
-10
lines changed

Makefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,26 @@
44
NODE_ENV:=production
55

66
build:
7+
# make sure this project is located within GOPATH, I.E. $GOPATH/src/factorio-server-manager
8+
79
# Build Linux release
810
mkdir build
9-
GOOS=linux GOARCH=amd64 go build -o factorio-server-manager/factorio-server-manager src/*
11+
GOOS=linux GOARCH=amd64 go build -o factorio-server-manager/factorio-server-manager factorio-server-manager/src
1012
# ui/node_modules/webpack/bin/webpack.js ui/webpack.config.js app/bundle.js --progress --profile --colors
1113
cp -r app/ factorio-server-manager/
1214
cp conf.json.example factorio-server-manager/conf.json
1315
zip -r build/factorio-server-manager-linux-x64.zip factorio-server-manager
1416
rm -rf factorio-server-manager
1517
# Build Windows release
16-
GOOS=windows GOARCH=386 go build -o factorio-server-manager/factorio-server-manager.exe src/*
18+
GOOS=windows GOARCH=386 go build -o factorio-server-manager/factorio-server-manager.exe factorio-server-manager/src
1719
cp -r app/ factorio-server-manager/
1820
cp conf.json.example factorio-server-manager/conf.json
1921
zip -r build/factorio-server-manager-windows.zip factorio-server-manager
2022
rm -rf factorio-server-manager
2123

2224
dev:
2325
mkdir dev
24-
GOOS=linux GOARCH=amd64 go build -o factorio-server-linux/factorio-server-manager src/*
26+
GOOS=linux GOARCH=amd64 go build -o factorio-server-linux/factorio-server-manager factorio-server-manager/src
2527
cp -r app/ dev/
2628
cp conf.json.example dev/conf.json
2729
mv factorio-server-linux/factorio-server-manager dev/factorio-server-manager

src/factorio_server.go

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"runtime"
1515
"strconv"
1616
"strings"
17+
"time"
1718

1819
"github.com/majormjr/rcon"
1920
)
@@ -225,16 +226,60 @@ func (f *FactorioServer) checkLogError(logline []string) error {
225226
}
226227

227228
func (f *FactorioServer) Stop() error {
228-
// TODO: Find an alternative to os.Kill on Windows. os.Interupt
229-
// is not implemented. Maps will not be saved.
230229
if runtime.GOOS == "windows" {
230+
231+
// Disable our own handling of CTRL+C, so we don't close when we send it to the console.
232+
setCtrlHandlingIsDisabledForThisProcess(true)
233+
234+
// Send CTRL+C to all processes attached to the console (ourself, and the factorio server instance)
235+
sendCtrlCToPid(0)
236+
log.Println("Sent SIGINT to Factorio process. Factorio shutting down...")
237+
238+
// Somehow, the Factorio devs managed to code the game to react appropriately to CTRL+C, including
239+
// saving the game, but not actually exit. So, we still have to manually kill the process, and
240+
// for extra fun, there's no way to know when the server save has actually completed (unless we want
241+
// to inject filesystem logic into what should be a process-level Stop() routine), so our best option
242+
// is to just wait an arbitrary amount of time and hope that the save is successful in that time.
243+
time.Sleep(2 * time.Second)
244+
f.Cmd.Process.Signal(os.Kill)
245+
246+
// Re-enable handling of CTRL+C after we're sure that the factrio server is shut down.
247+
setCtrlHandlingIsDisabledForThisProcess(false)
248+
249+
f.Running = false
250+
return nil
251+
}
252+
253+
err := f.Cmd.Process.Signal(os.Interrupt)
254+
if err != nil {
255+
if err.Error() == "os: process already finished" {
256+
f.Running = false
257+
return err
258+
}
259+
log.Printf("Error sending SIGINT to Factorio process: %s", err)
260+
return err
261+
}
262+
f.Running = false
263+
log.Printf("Sent SIGINT to Factorio process. Factorio shutting down...")
264+
265+
err = f.Rcon.Close()
266+
if err != nil {
267+
log.Printf("Error close rcon connection: %s", err)
268+
}
269+
270+
return nil
271+
}
272+
273+
func (f *FactorioServer) Kill() error {
274+
if runtime.GOOS == "windows" {
275+
231276
err := f.Cmd.Process.Signal(os.Kill)
232277
if err != nil {
233278
if err.Error() == "os: process already finished" {
234279
f.Running = false
235280
return err
236281
}
237-
log.Printf("Error sending SIGKILLL to Factorio process: %s", err)
282+
log.Printf("Error sending SIGKILL to Factorio process: %s", err)
238283
return err
239284
}
240285
f.Running = false
@@ -243,17 +288,17 @@ func (f *FactorioServer) Stop() error {
243288
return nil
244289
}
245290

246-
err := f.Cmd.Process.Signal(os.Interrupt)
291+
err := f.Cmd.Process.Signal(os.Kill)
247292
if err != nil {
248293
if err.Error() == "os: process already finished" {
249294
f.Running = false
250295
return err
251296
}
252-
log.Printf("Error sending SIGINT to Factorio process: %s", err)
297+
log.Printf("Error sending SIGKILL to Factorio process: %s", err)
253298
return err
254299
}
255300
f.Running = false
256-
log.Printf("Sent SIGINT to Factorio process. Factorio shutting down...")
301+
log.Printf("Sent SIGKILL to Factorio process. Factorio forced to exit.")
257302

258303
err = f.Rcon.Close()
259304
if err != nil {
@@ -262,3 +307,5 @@ func (f *FactorioServer) Stop() error {
262307

263308
return nil
264309
}
310+
311+

src/factorio_server_linux.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
4+
// Stubs for windows-only functions
5+
6+
func sendCtrlCToPid(pid int) {
7+
}
8+
9+
func setCtrlHandlingIsDisabledForThisProcess(disabled bool) {
10+
}

src/factorio_server_windows.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"syscall"
6+
)
7+
8+
func sendCtrlCToPid(pid int) {
9+
d, e := syscall.LoadDLL("kernel32.dll")
10+
if e != nil {
11+
log.Fatalf("LoadDLL: %v\n", e)
12+
}
13+
p, e := d.FindProc("GenerateConsoleCtrlEvent")
14+
if e != nil {
15+
log.Fatalf("FindProc: %v\n", e)
16+
}
17+
r, _, e := p.Call(uintptr(syscall.CTRL_C_EVENT), uintptr(pid))
18+
if r == 0 {
19+
log.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
20+
}
21+
}
22+
23+
func setCtrlHandlingIsDisabledForThisProcess(disabled bool) {
24+
disabledInt := 0
25+
if(disabled){
26+
disabledInt = 1
27+
}
28+
29+
d, e := syscall.LoadDLL("kernel32.dll")
30+
if e != nil {
31+
log.Fatalf("LoadDLL: %v\n", e)
32+
}
33+
p, e := d.FindProc("SetConsoleCtrlHandler")
34+
if e != nil {
35+
log.Fatalf("FindProc: %v\n", e)
36+
}
37+
r, _, e := p.Call(uintptr(0), uintptr(disabledInt))
38+
if r == 0 {
39+
log.Fatalf("SetConsoleCtrlHandler: %v\n", e)
40+
}
41+
}

src/handlers.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,41 @@ func StopServer(w http.ResponseWriter, r *http.Request) {
667667
}
668668
}
669669

670+
671+
func KillServer(w http.ResponseWriter, r *http.Request) {
672+
resp := JSONResponse{
673+
Success: false,
674+
}
675+
676+
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
677+
678+
if FactorioServ.Running {
679+
err := FactorioServ.Kill()
680+
if err != nil {
681+
log.Printf("Error in kill server handler: %s", err)
682+
resp.Data = fmt.Sprintf("Error in kill server handler: %s", err)
683+
if err := json.NewEncoder(w).Encode(resp); err != nil {
684+
log.Printf("Error encoding config file JSON reponse: ", err)
685+
}
686+
return
687+
}
688+
689+
log.Printf("Killed Factorio server.")
690+
resp.Success = true
691+
resp.Data = fmt.Sprintf("Factorio server killed")
692+
} else {
693+
resp.Data = "Factorio server is not running"
694+
if err := json.NewEncoder(w).Encode(resp); err != nil {
695+
log.Printf("Error encoding config file JSON reponse: ", err)
696+
}
697+
return
698+
}
699+
700+
if err := json.NewEncoder(w).Encode(resp); err != nil {
701+
log.Printf("Error encoding config file JSON reponse: ", err)
702+
}
703+
}
704+
670705
func CheckServer(w http.ResponseWriter, r *http.Request) {
671706
resp := JSONResponse{
672707
Success: false,

src/routes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ var apiRoutes = Routes{
228228
"GET",
229229
"/server/stop",
230230
StopServer,
231+
}, {
232+
"KillServer",
233+
"GET",
234+
"/server/kill",
235+
KillServer,
231236
}, {
232237
"RunningServer",
233238
"GET",

ui/App/components/ServerCtl/ServerCtl.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class ServerCtl extends React.Component {
55
super(props);
66
this.startServer = this.startServer.bind(this);
77
this.stopServer = this.stopServer.bind(this);
8+
this.killServer = this.killServer.bind(this);
89

910
this.incrementPort = this.incrementPort.bind(this);
1011
this.decrementPort = this.decrementPort.bind(this);
@@ -58,6 +59,21 @@ class ServerCtl extends React.Component {
5859
e.preventDefault();
5960
}
6061

62+
killServer(e) {
63+
$.ajax({
64+
type: "GET",
65+
url: "/api/server/kill",
66+
dataType: "json",
67+
success: (resp) => {
68+
this.props.facServStatus();
69+
this.props.getStatus();
70+
console.log(resp)
71+
swal(resp.data)
72+
}
73+
});
74+
e.preventDefault();
75+
}
76+
6177
incrementPort() {
6278
let port = this.state.port + 1;
6379
this.setState({port: port})
@@ -85,7 +101,11 @@ class ServerCtl extends React.Component {
85101
</div>
86102

87103
<div className="col-md-4">
88-
<button className="btn btn-block btn-danger" type="button" onClick={this.stopServer}><i className="fa fa-stop fa-fw"></i>Stop Factorio Server</button>
104+
<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>
105+
</div>
106+
107+
<div className="col-md-4">
108+
<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>
89109
</div>
90110
</div>
91111

@@ -132,3 +152,4 @@ ServerCtl.propTypes = {
132152
}
133153

134154
export default ServerCtl
155+

0 commit comments

Comments
 (0)