From 8a1cf0456a8fc18dcac7a8d97439719f8790123a Mon Sep 17 00:00:00 2001 From: Bennett Wetters Date: Fri, 10 Nov 2023 03:24:12 +0100 Subject: [PATCH] First working? version with persistent state --- main.go | 18 +++++++++++- types/v14.go | 7 +++++ util/util.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 6d8ebe0..32739c2 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,9 @@ package main import ( "log" "net/http" + "os" + "os/signal" + "syscall" "gitlab.hamburg.ccc.de/ccchh/spaceapid/handlers" "gitlab.hamburg.ccc.de/ccchh/spaceapid/util" @@ -15,10 +18,23 @@ func main() { log.Println("Reading initial SpaceAPI response from", config.TemplatePath) spaceApiResponse := util.ParseTemplate(config.TemplatePath) + // Merge old state if present + util.MergeOldState(&spaceApiResponse) + // Register HTTP handlers http.HandleFunc("/", handlers.Root(&spaceApiResponse)) http.HandleFunc("/state/open", handlers.StateOpen(config.BAUsername, config.BAPassword, &spaceApiResponse)) + // Start webserver log.Println("Starting HTTP server...") - log.Fatalln(http.ListenAndServe(":8080", nil)) + go log.Fatalln(http.ListenAndServe(":8080", nil)) + + // Wait for exit signal + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM) + <-sc + + // Save state for next run + log.Println("Saving state and shutting down...") + util.SaveCurrentState(spaceApiResponse) } diff --git a/types/v14.go b/types/v14.go index e3e2a6f..5f014ba 100644 --- a/types/v14.go +++ b/types/v14.go @@ -38,3 +38,10 @@ type SpaceAPIResponseV14 struct { URL string `json:"url"` } `json:"links"` } + +type PersistentStateV14 struct { + State struct { + Open bool `json:"open"` + LastChange int64 `json:"lastchange"` + } `json:"state"` +} diff --git a/util/util.go b/util/util.go index cca50dd..18447be 100644 --- a/util/util.go +++ b/util/util.go @@ -3,13 +3,17 @@ package util import ( "bytes" "encoding/json" + "errors" "log" "os" + "path" "slices" "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" ) +const savedStateJSONPath = "/var/lib/spaceapid/spaceapid-state.json" + // ParseTemplate parses the given file and func ParseTemplate(file string) (resp types.SpaceAPIResponseV14) { @@ -34,3 +38,77 @@ func ParseTemplate(file string) (resp types.SpaceAPIResponseV14) { return } + +// MergeOldState merges a given SpaceAPIResponse with the state saved at the time of last program exit and then deletes +// the file containing the old state. +func MergeOldState(response *types.SpaceAPIResponseV14) { + var ( + err error + oldState []byte + ) + + // Create state directory if not present + err = os.MkdirAll(path.Dir(savedStateJSONPath), 0750) + if err != nil { + log.Fatalln("Failed creating", savedStateJSONPath, ", aborting... error:", err) + } + + // Check if state.json is present + _, err = os.Stat(savedStateJSONPath) + if err != nil { + log.Println("Old state json not accessible at", savedStateJSONPath, ", skipping merge... error:", err) + if errors.Is(err, os.ErrNotExist) { + return + } + goto removeOld + } + + // Read file and merge + oldState, err = os.ReadFile(savedStateJSONPath) + if err != nil { + log.Println("Error reading old state from", savedStateJSONPath, ", skipping merge... error:", err) + goto removeOld + } + err = json.Unmarshal(oldState, response) + if err != nil { + log.Println(savedStateJSONPath, "doesn't seem to contain valid data... error:", err) + goto removeOld + } + + // Delete old state json +removeOld: + err = os.RemoveAll(savedStateJSONPath) + if err != nil { + log.Println("Failed to remove", savedStateJSONPath, ", continuing... error:", err) + } +} + +func SaveCurrentState(response types.SpaceAPIResponseV14) { + file, err := os.OpenFile(savedStateJSONPath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + log.Fatalln("Failed opening", savedStateJSONPath, "while trying to save current state... error:", err) + } + defer func(file *os.File) { + _ = file.Close() + }(file) + + // Create persistent state + persistentStateV14 := types.PersistentStateV14{ + State: struct { + Open bool `json:"open"` + LastChange int64 `json:"lastchange"` + }{Open: response.State.Open, LastChange: response.State.LastChange}, + } + + // Serialize persistent state + marshal, err := json.MarshalIndent(persistentStateV14, "", "\t") + if err != nil { + log.Fatalln("Failed serializing persistent state... error:", err) + } + + // Write to file + _, err = file.Write(marshal) + if err != nil { + log.Fatalln("Failed writing persistent state to file", file.Name(), "... error:", err) + } +}