Skip to content

Commit f78a9a4

Browse files
committed
- remove Load Latest from save overviews (except under Controls)
- add actual latest save to Load Latest - disable Controls and Load Mods from Save when no save is present
1 parent 47dcfc9 commit f78a9a4

File tree

10 files changed

+110
-41
lines changed

10 files changed

+110
-41
lines changed

src/api/handlers.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"os"
1212
"path/filepath"
13+
"strconv"
1314
"sync"
1415
"time"
1516

@@ -84,23 +85,48 @@ func SaveSession(w http.ResponseWriter, r *http.Request, session *sessions.Sessi
8485
// Lists all save files in the factorio/saves directory
8586
func ListSaves(w http.ResponseWriter, r *http.Request) {
8687
var resp interface{}
87-
config := bootstrap.GetConfig()
8888
defer func() {
8989
WriteResponse(w, resp)
9090
}()
9191

9292
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
9393

94-
savesList, err := factorio.ListSaves(config.FactorioSavesDir)
94+
latestParam := r.URL.Query().Get("latest")
95+
96+
var withLatest bool
97+
98+
if latestParam != "" {
99+
var err error
100+
withLatest, err = strconv.ParseBool(latestParam)
101+
if err != nil {
102+
resp = fmt.Sprintf("Error parsing latestParam: %s", err)
103+
log.Println(resp)
104+
w.WriteHeader(http.StatusBadRequest)
105+
return
106+
}
107+
}
108+
109+
savesList, err := factorio.ListSaves()
95110
if err != nil {
96111
resp = fmt.Sprintf("Error listing save files: %s", err)
97112
log.Println(resp)
98113
w.WriteHeader(http.StatusInternalServerError)
99114
return
100115
}
101116

102-
loadLatest := factorio.Save{Name: "Load Latest"}
103-
savesList = append(savesList, loadLatest)
117+
// get actual latest and add name
118+
// but only if requested
119+
if withLatest && len(savesList) != 0 {
120+
latestSave, err := factorio.GetLatestSave()
121+
if err != nil {
122+
resp = fmt.Sprintf("Error getting latest save: %s", err)
123+
log.Println(resp)
124+
w.WriteHeader(http.StatusInternalServerError)
125+
return
126+
}
127+
latestSave.Name = fmt.Sprintf("Load Latest (%s)", latestSave.Name)
128+
savesList = append(savesList, latestSave)
129+
}
104130

105131
resp = savesList
106132
}

src/bootstrap/user.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414

1515
type User struct {
1616
gorm.Model
17-
Username string `json:"username",gorm:"uniqueIndex,not null"`
18-
Password string `json:"password",gorm:"not null"`
19-
Role string `json:"role",gorm:"not null"`
17+
Username string `json:"username" gorm:"uniqueIndex,not null"`
18+
Password string `json:"password" gorm:"not null"`
19+
Role string `json:"role" gorm:"not null"`
2020
Email string `json:"email"`
2121
}
2222

src/factorio/save.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func readString(r io.Reader, game Version, forceOptimized bool) (s string, err e
293293
return string(d), nil
294294
}
295295

296-
func (h SaveHeader) readStats(r io.Reader) (stats map[byte][]map[uint16]uint32, err error) {
296+
func (h *SaveHeader) readStats(r io.Reader) (stats map[byte][]map[uint16]uint32, err error) {
297297
var scratch [4]byte
298298
stats = make(map[byte][]map[uint16]uint32)
299299

src/factorio/saves.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ type Save struct {
1818
Size int64 `json:"size"`
1919
}
2020

21-
func (s Save) String() string {
21+
func (s *Save) String() string {
2222
return s.Name
2323
}
2424

2525
// Lists save files in factorio/saves
26-
func ListSaves(saveDir string) (saves []Save, err error) {
26+
func ListSaves() (saves []Save, err error) {
27+
config := bootstrap.GetConfig()
2728
saves = []Save{}
28-
err = filepath.Walk(saveDir, func(path string, info os.FileInfo, err error) error {
29+
err = filepath.Walk(config.FactorioSavesDir, func(path string, info os.FileInfo, err error) error {
2930
if info == nil || (info.IsDir() && info.Name() == "saves") {
3031
return nil
3132
}
@@ -40,8 +41,7 @@ func ListSaves(saveDir string) (saves []Save, err error) {
4041
}
4142

4243
func FindSave(name string) (*Save, error) {
43-
config := bootstrap.GetConfig()
44-
saves, err := ListSaves(config.FactorioSavesDir)
44+
saves, err := ListSaves()
4545
if err != nil {
4646
return nil, fmt.Errorf("error listing saves: %v", err)
4747
}
@@ -84,3 +84,24 @@ func CreateSave(filePath string) (string, error) {
8484

8585
return result, nil
8686
}
87+
88+
func GetLatestSave() (save Save, err error) {
89+
config := bootstrap.GetConfig()
90+
91+
err = filepath.Walk(config.FactorioSavesDir, func(path string, info os.FileInfo, err error) error {
92+
if info == nil || (info.IsDir() && info.Name() == "saves") {
93+
return nil
94+
}
95+
96+
if save.LastMod.Before(info.ModTime()) {
97+
save = Save{
98+
Name: info.Name(),
99+
LastMod: info.ModTime(),
100+
Size: info.Size(),
101+
}
102+
}
103+
return nil
104+
})
105+
106+
return
107+
}

src/factorio/server.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func (server *Server) Run() error {
231231
ioutil.WriteFile(config.SettingsFile, data, 0644)
232232
}
233233

234-
saves, err := ListSaves(config.FactorioSavesDir)
234+
saves, err := ListSaves()
235235
if err != nil {
236236
log.Println("Failed to get saves list: ", err)
237237
}
@@ -260,12 +260,12 @@ func (server *Server) Run() error {
260260
args = append(args, "--server-adminlist", config.FactorioAdminFile)
261261
}
262262

263-
if server.Savefile == "Load Latest" {
263+
if strings.HasPrefix(server.Savefile, "Load Latest") {
264264
args = append(args, "--start-server-load-latest")
265265
} else {
266266
args = append(args, "--start-server", filepath.Join(config.FactorioSavesDir, server.Savefile))
267267
}
268-
268+
269269
// Write chat log to a different file if requested (if not it will be mixed-in with the default logfile)
270270
if config.ChatLogFile != "" {
271271
args = append(args, "--console-log", config.ChatLogFile)
@@ -278,7 +278,7 @@ func (server *Server) Run() error {
278278
log.Println("Starting server with command: ", config.FactorioBinary, args)
279279
server.Cmd = exec.Command(config.FactorioBinary, args...)
280280
}
281-
281+
282282
server.StdOut, err = server.Cmd.StdoutPipe()
283283
if err != nil {
284284
log.Printf("Error opening stdout pipe: %s", err)

ui/App/components/Select.jsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
import React, {useState} from "react";
1+
import React, {useEffect, useState} from "react";
22

3-
const Select = ({register, options, className = "", defaultValue = ""}) => {
3+
const Select = ({register, options, className = "", defaultValue = "", disabled = undefined}) => {
44

55
const [value, setValue] = useState(defaultValue);
66

7+
useEffect(() => {
8+
if (value === "") {
9+
setValue(defaultValue)
10+
}
11+
});
12+
713
return (
814
<div className={`${className} relative`}>
915
<select
1016
className="shadow appearance-none border w-full py-2 px-3 text-black"
1117
{...register}
1218
value={value}
19+
disabled={disabled}
1320
onChange={optionElement => setValue(optionElement.target.value)}
1421
>
1522
{options.map(option => <option value={option.value} key={option.value}>{option.name}</option>)}

ui/App/views/Controls.jsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,14 @@ const Controls = ({serverStatus}) => {
1212

1313
const factorioVersion = serverStatus.fac_version ? serverStatus.fac_version : 'Unknown';
1414
const [saves, setSaves] = useState([]);
15+
const [isDisabled, setIsDisabled] = useState(true);
1516
const [isStopping, setIsStopping] = useState(false);
1617
const [isStarting, setIsStarting] = useState(false);
1718
const [isKilling, setIsKilling] = useState(false);
1819

19-
const { handleSubmit, register, formState: {errors} } = useForm();
20+
const { handleSubmit, reset, register, formState: {errors} } = useForm();
2021

2122
const startServer = async (data) => {
22-
if(saves.length === 1 && saves[0].name === "Load Latest") {
23-
window.flash("Save must be created before starting server", "red");
24-
return;
25-
}
2623
setIsStarting(true);
2724
await server.start(data.ip, parseInt(data.port), data.save);
2825
}
@@ -38,8 +35,14 @@ const Controls = ({serverStatus}) => {
3835
}
3936

4037
useEffect(() => {
41-
savesResource.list()
42-
.then(res => setSaves(res));
38+
savesResource.list(true)
39+
.then(res => {
40+
setSaves(res);
41+
if (res.length > 0) {
42+
setIsDisabled(undefined);
43+
}
44+
reset();
45+
});
4346
}, [])
4447

4548
return (
@@ -80,6 +83,7 @@ const Controls = ({serverStatus}) => {
8083
<div className="font-bold">IP</div>
8184
<Input
8285
defaultValue={"0.0.0.0"}
86+
disabled={isDisabled}
8387
register={register('ip',{required: true, pattern: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'})}
8488
/>
8589
<Error error={errors.ip} message="IP is required and must be valid."/>
@@ -90,6 +94,7 @@ const Controls = ({serverStatus}) => {
9094
type="number"
9195
min={1}
9296
defaultValue={"34197"}
97+
disabled={isDisabled}
9398
register={register('port',{required: true})}
9499
/>
95100
<Error error={errors.port} message="Port is required"/>
@@ -103,12 +108,14 @@ const Controls = ({serverStatus}) => {
103108
<div className="relative">
104109
<Select
105110
register={register('save',{required: true})}
106-
defaultValue="Load Latest"
111+
defaultValue={saves.find((save) => save.name.startsWith('Load Latest'))?.name}
112+
disabled={isDisabled}
107113
options={saves.map(save => new Object({
108114
value: save.name,
109115
name: save.name
110116
}))}
111117
/>
118+
<Error error={errors.save} message="Save is required and must be valid."/>
112119
</div>
113120
</div>
114121
</>
@@ -122,7 +129,7 @@ const Controls = ({serverStatus}) => {
122129
<Button onClick={stopServer} isLoading={isStopping} isDisabled={isKilling} size="sm" className="w-full md:w-auto mb-2 md:mb-0 md:mr-2" type="default">Save & Stop Server</Button>
123130
<Button onClick={killServer} isLoading={isKilling} isDisabled={isStopping} size="sm" type="danger" className="w-full md:w-auto">Kill Server</Button>
124131
</>
125-
: <Button isSubmit={true} isLoading={isStarting} size="sm" type="success" className="w-full md:w-auto">Start Server</Button>
132+
: <Button isSubmit={true} isDisabled={isDisabled} isLoading={isStarting} size="sm" type="success" className="w-full md:w-auto">Start Server</Button>
126133
}
127134
</div>
128135
}

ui/App/views/Mods/components/LoadMods.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ const LoadMods = ({refreshMods}) => {
1111
const [saves, setSaves] = useState([]);
1212
const {register, reset, handleSubmit} = useForm();
1313
const [isLoading, setIsLoading] = useState(false);
14+
const [isDisabled, setIsDisabled] = useState(true);
1415

1516
useEffect(() => {
1617
(async () => {
17-
setSaves(await savesResource.list());
18+
const s = await savesResource.list()
19+
setSaves(s);
20+
if (s.length > 0) {
21+
setIsDisabled(false);
22+
}
1823
reset();
1924
})();
2025
}, []);
@@ -40,12 +45,13 @@ const LoadMods = ({refreshMods}) => {
4045
<Select
4146
register={register('save')}
4247
className="mb-4"
48+
disabled={isDisabled}
4349
options={saves?.map(save => new Object({
4450
name: save.name,
4551
value: save.name
4652
}))}
4753
/>
48-
<Button isSubmit={true} isLoading={isLoading}>Load</Button>
54+
<Button isSubmit={true} isDisabled={isDisabled} isLoading={isLoading}>Load</Button>
4955
</form>
5056
)
5157
}

ui/App/views/Saves/Saves.jsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,13 @@ const Saves = ({serverStatus}) => {
7474
<td className="pr-4">{(new Date(save.last_mod)).toLocaleString()}</td>
7575
<td className="pr-4">{parseFloat(save.size / 1024 / 1024).toFixed(3)} MB</td>
7676
<td>
77-
{ save.name !== 'Load Latest' && <>
78-
<a href={`/api/saves/dl/${save.name}`} className="mr-2">
79-
<FontAwesomeIcon
80-
className="text-gray-light cursor-pointer hover:text-orange"
81-
icon={faDownload}/>
82-
</a>
83-
<FontAwesomeIcon className="text-red cursor-pointer hover:text-red-light mr-2"
84-
onClick={() => deleteSave(save)} icon={faTrashAlt}/>
85-
</>}
77+
<a href={`/api/saves/dl/${save.name}`} className="mr-2">
78+
<FontAwesomeIcon
79+
className="text-gray-light cursor-pointer hover:text-orange"
80+
icon={faDownload}/>
81+
</a>
82+
<FontAwesomeIcon className="text-red cursor-pointer hover:text-red-light mr-2"
83+
onClick={() => deleteSave(save)} icon={faTrashAlt}/>
8684
</td>
8785
</tr>
8886
)}

ui/api/resources/saves.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import client from "../client";
22

33
export default {
4-
list: async () => {
5-
const response = await client.get('/api/saves/list');
4+
list: async (latest) => {
5+
const response = await client.get('/api/saves/list', {
6+
params: {
7+
latest
8+
}
9+
});
610
return response.data;
711
},
812
delete: async (save) => {

0 commit comments

Comments
 (0)