0001-gui-add-auto-scummer.patch
#612
- Author
- Anonymous
- Created
- Dec. 14, 2022, 12:35 p.m.
- Expires
- Never
- Size
- 19.2ย KB
- Hits
- 56
- Syntax
- Diff
- Private
- โ No
From b20a2678585b94aeea093085f92d8c3c6fb28233 Mon Sep 17 00:00:00 2001
From: Winston Weinert <git@winny.tech>
Date: Wed, 14 Dec 2022 06:32:25 -0600
Subject: [PATCH] gui: add auto-scummer
---
.gitignore | 1 +
cmd/savecmd/save.go | 9 +--
go.mod | 13 ++++-
go.sum | 24 ++++++++
gui/main.go | 84 +++++++++++++++++++++++++++-
gui/models.go | 23 ++++++++
gui/newsavewatcher.go | 126 ++++++++++++++++++++++++++++++++++++++++++
gui/paths.go | 25 +++++++++
savefile/savefile.go | 16 +++++-
9 files changed, 308 insertions(+), 13 deletions(-)
create mode 100644 gui/models.go
create mode 100644 gui/newsavewatcher.go
create mode 100644 gui/paths.go
diff --git a/.gitignore b/.gitignore
index 5ef1910..6d08386 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/jhmod
+/jhmod.exe
# Discourage accidental staging of scratch files used by the tool.
core.*
diff --git a/cmd/savecmd/save.go b/cmd/savecmd/save.go
index 59160ed..d083691 100644
--- a/cmd/savecmd/save.go
+++ b/cmd/savecmd/save.go
@@ -29,14 +29,7 @@ func saveInfoCmd() *cobra.Command {
if debug {
fmt.Fprintf(os.Stderr, "Reading file %s\n", p)
}
- f, openErr := os.Open(p)
- if openErr != nil {
- fmt.Fprintf(os.Stderr, "Failed to open '%s': %v\n", p, openErr)
- errors++
- continue
- }
- defer f.Close()
- save, parseErr := savefile.Parse(f)
+ save, parseErr := savefile.ParseFile(p)
if parseErr != nil {
fmt.Fprintf(os.Stderr, "Failed to parse '%s: %v\n", p, parseErr)
errors++
diff --git a/go.mod b/go.mod
index 23b554d..20cbce1 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,9 @@ module github.com/sector-f/jhmod
go 1.17
require (
+ github.com/adrg/xdg v0.4.0
github.com/dsnet/golib/memfile v1.0.0
+ github.com/fsnotify/fsnotify v1.6.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
)
@@ -12,7 +14,6 @@ require (
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
- github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
@@ -21,19 +22,25 @@ require (
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
+ github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
github.com/stretchr/testify v1.7.2 // indirect
github.com/tevino/abool v1.2.0 // indirect
+ github.com/u-root/u-root v0.10.0 // indirect
github.com/yuin/goldmark v1.4.0 // indirect
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
- golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
- golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
+ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
+ golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+ gorm.io/driver/sqlite v1.4.3 // indirect
+ gorm.io/gorm v1.24.2 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
)
diff --git a/go.sum b/go.sum
index 3841d6e..6262fea 100644
--- a/go.sum
+++ b/go.sum
@@ -44,6 +44,8 @@ fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNq
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
+github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -82,6 +84,8 @@ github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUv
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU=
@@ -198,6 +202,11 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -216,6 +225,9 @@ github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -281,6 +293,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
+github.com/u-root/u-root v0.10.0 h1:nz3jSORXAxTl6bNXhRh5s9HT5oRo5hmCJXUJ0q/BK7s=
+github.com/u-root/u-root v0.10.0/go.mod h1:lqAiThZZ0/yg0rj49gxpSCK8hfv86NSc+wPhGp5idb4=
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -388,6 +402,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -458,8 +474,11 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -647,6 +666,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
+gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
+gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
+gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0=
+gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/gui/main.go b/gui/main.go
index 09fd2eb..cc20102 100644
--- a/gui/main.go
+++ b/gui/main.go
@@ -1,14 +1,96 @@
package gui
import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "os"
+ path "path/filepath"
+ "time"
+
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
+ "github.com/sector-f/jhmod/savefile"
+ "github.com/u-root/u-root/pkg/cp"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm" // Base ORM
)
+func sha256File(path string) ([]byte, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return make([]byte, 0), err
+ }
+ defer f.Close()
+ h := sha256.New()
+ if _, err := io.Copy(h, f); err != nil {
+ return make([]byte, 0), err
+ }
+ return h.Sum(nil), nil
+}
+
func Run() {
a := app.New()
w := a.NewWindow("Hello World")
+ lbl := widget.NewLabel("Hello World!")
+
+ mkdirErr := os.Mkdir(getSaveScumDir(), 0750)
+ if mkdirErr != nil && !os.IsExist(mkdirErr) {
+ fmt.Fprintf(os.Stderr, "Cannot make savescumdir %v\n", mkdirErr)
+ return
+ }
+
+ dbPath := path.Join(
+ getSaveScumDir(),
+ "db.sqlite3",
+ )
+ db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
+ if err != nil {
+ panic(fmt.Sprintf("Unable to open db \"%s\" %v", dbPath, err))
+ }
+ if err = db.AutoMigrate(&StoredSaveFile{}); err != nil {
+ panic(fmt.Sprintf("Could not migrate db.\n"))
+ }
+
+ go watchForNewSave(func(p string) {
+ lbl.SetText(fmt.Sprintf("%s %s", time.Now().String(), p))
+
+ sd, err := savefile.ParseFile(p)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Could not parse save \"%s\". Aborting.\n", p)
+ return
+ }
+ digest, shaErr := sha256File(p)
+ if shaErr != nil {
+ fmt.Fprintf(os.Stderr, "Could not SHA256 \"%s\". Aborting.\n", p)
+ return
+ }
+
+ digestHex := hex.EncodeToString(digest)
+ relPath := digestHex
+
+ storedSaveFile := &StoredSaveFile{
+ OriginalBase: path.Base(p), // this don't work on the winders
+ StoredRelPath: relPath,
+ Sha256Hex: digestHex,
+
+ PlayerName: sd.PlayerName,
+ GameMode: sd.GameMode,
+ CurrentLevel: sd.CurrentLevel,
+ Seed: sd.Seed,
+ }
+
+ destAbs := path.Join(getSaveScumDir(), relPath)
+ if cpErr := cp.Copy(p, destAbs); cpErr != nil {
+ fmt.Fprintf(os.Stderr, "Failed to copy file \"%s\" to \"%s\" (%v)\n", p, destAbs, cpErr)
+ return
+ } else {
+ fmt.Printf("Copied \"%s\" to \"%s\" n saved to db.\n", p, destAbs)
+ }
+ db.Create(storedSaveFile)
+ })
- w.SetContent(widget.NewLabel("Hello World!"))
+ w.SetContent(lbl)
w.ShowAndRun()
}
diff --git a/gui/models.go b/gui/models.go
new file mode 100644
index 0000000..46a911d
--- /dev/null
+++ b/gui/models.go
@@ -0,0 +1,23 @@
+package gui
+
+import (
+ "gorm.io/gorm"
+)
+
+type StoredSaveFile struct {
+ gorm.Model
+ Id int `gorm:"primary_key"`
+ OriginalBase string
+ StoredRelPath string `gorm:"unique"`
+ Sha256Hex string `gorm:"unique"`
+
+ // Copied verbatim from savefile.go
+ // Player name
+ PlayerName string
+ // Game mode. This can be "jh", and various others.
+ GameMode string
+ // The current level's name.
+ CurrentLevel string
+ // The seed used to generate the game.
+ Seed uint32
+}
diff --git a/gui/newsavewatcher.go b/gui/newsavewatcher.go
new file mode 100644
index 0000000..a69a7e9
--- /dev/null
+++ b/gui/newsavewatcher.go
@@ -0,0 +1,126 @@
+package gui
+
+import (
+ "fmt"
+ "math"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/fsnotify/fsnotify"
+)
+
+type CreationCallback = func(*fsnotify.Event)
+
+// Depending on the system, a single "write" can generate many Write events; for
+// example compiling a large Go program can generate hundreds of Write events on
+// the binary.
+//
+// The general strategy to deal with this is to wait a short time for more write
+// events, resetting the wait period for every new event.
+func dedup(callback CreationCallback, paths ...string) {
+ if len(paths) < 1 {
+ panic("must specify at least one path to watch")
+ }
+
+ // Create a new watcher.
+ w, err := fsnotify.NewWatcher()
+ if err != nil {
+ panic(fmt.Sprintf("creating a new watcher: %s", err))
+ }
+ defer w.Close()
+
+ // Start listening for events.
+ go watchForCreated(w, callback)
+
+ // Add all paths from the commandline.
+ for _, p := range paths {
+ err = w.Add(p)
+ if err != nil {
+ panic(fmt.Sprintf("%q: %s", p, err))
+ }
+ }
+}
+
+func watchForCreated(w *fsnotify.Watcher, creatcb CreationCallback) {
+ var (
+ // Wait 100ms for new events; each new event resets the timer.
+ waitFor = 100 * time.Millisecond
+ // why is this not a const?
+
+ // Keep track of the timers, as path รขโ โ timer.
+ mu sync.Mutex
+ timers = make(map[string]*time.Timer)
+
+ // Callback we run.
+ cb = func(e fsnotify.Event) {
+ creatcb(&e)
+
+ // Don't need to remove the timer if you don't have a lot of files.
+ mu.Lock()
+ delete(timers, e.Name)
+ mu.Unlock()
+ }
+ )
+
+ for {
+ select {
+ // Read from Errors.
+ case _, ok := <-w.Errors:
+ if !ok { // Channel was closed (i.e. Watcher.Close() was called).
+ return
+ }
+ // Read from Events.
+ case e, ok := <-w.Events:
+ if !ok { // Channel was closed (i.e. Watcher.Close() was called).
+ return
+ }
+
+ // We just want to watch for file creation, so ignore everything
+ // outside of Create and Write.
+ if !e.Has(fsnotify.Create) && !e.Has(fsnotify.Write) {
+ continue
+ }
+
+ // Get timer.
+ mu.Lock()
+ t, ok := timers[e.Name]
+ mu.Unlock()
+
+ // No timer yet, so create one.
+ if !ok {
+ t = time.AfterFunc(math.MaxInt64, func() { cb(e) })
+ t.Stop()
+
+ mu.Lock()
+ timers[e.Name] = t
+ mu.Unlock()
+ }
+
+ // Reset the timer for this path, so it will start from 100ms again.
+ t.Reset(waitFor)
+ }
+ }
+}
+
+type NewSaveCallback = func(string)
+
+func watchForNewSave(cb NewSaveCallback) {
+ watcher, err := fsnotify.NewWatcher()
+ w := watcher
+ if err != nil {
+ panic("Could not create watcher")
+ }
+ defer watcher.Close()
+ err = watcher.Add(guessGameDir())
+ if err != nil {
+ panic(fmt.Sprintf("Could not create add dir to watcher %v", err))
+ }
+ watchForCreated(w, func(e *fsnotify.Event) {
+ base := filepath.Base(e.Name)
+ if base != "save_loading" && strings.HasPrefix(base, "save") {
+ cb(e.Name)
+ }
+ })
+}
diff --git a/gui/paths.go b/gui/paths.go
new file mode 100644
index 0000000..c341e6a
--- /dev/null
+++ b/gui/paths.go
@@ -0,0 +1,25 @@
+package gui
+
+import (
+ "runtime"
+
+ "github.com/adrg/xdg"
+)
+
+func guessGameDir() string {
+ os := runtime.GOOS
+ switch os {
+ case "windows":
+ return "C:/Program Files (x86)/Steam/steamapps/common/Jupiter Hell"
+ default:
+ panic("Don't know about OS default game dir")
+ }
+}
+
+func getSaveScumDir() string {
+ dir, err := xdg.DataFile("jhmod/savescum")
+ if err != nil {
+ panic(err)
+ }
+ return dir
+}
diff --git a/savefile/savefile.go b/savefile/savefile.go
index 224db88..c13f501 100644
--- a/savefile/savefile.go
+++ b/savefile/savefile.go
@@ -7,6 +7,7 @@ import (
"errors"
"io"
"io/ioutil"
+ "os"
"unicode"
)
@@ -15,7 +16,7 @@ const (
magic = "\xde\xc0\xad\xde"
)
-type savedata struct {
+type Savedata struct {
// Player name
PlayerName string
// Game mode. This can be "jh", and various others.
@@ -26,6 +27,9 @@ type savedata struct {
Seed uint32
}
+// TODO port legacy type `savedata` to `Savedata`
+type savedata = Savedata
+
// No magic found on save file.
var ErrNoMagicFound error = errors.New("no magic found")
@@ -135,3 +139,13 @@ func Parse(r io.Reader) (savedata, error) {
Seed: seed,
}, nil
}
+
+func ParseFile(path string) (savedata, error) {
+ f, openErr := os.Open(path)
+ if openErr != nil {
+ return savedata{}, openErr
+ }
+
+ defer f.Close()
+ return Parse(f)
+}
--
2.34.1.windows.1