spaceapid/persistence/persistentState.go
Bennett Wetters fd3cae1866 feat: SpaceAPI v15 support
Delete types/v14.go, replace with v15.go.

Rename internal types to not include message version.
I don't currently see the need to support multiple message
versions that have incompatible layouts.
v15 is backwards compatible with v14, so the config example now
specifies both.

Rename the "response" field in the config file to "static" as
dynamic/static makes more sense from the user's perspective.
(Internally it's still SpaceapidConfig.{Dynamic,Response}
to align with type names and usage.)
2026-07-25 22:51:26 +02:00

120 lines
3.7 KiB
Go

package persistence
import (
"encoding/json"
"errors"
"log"
"os"
"git.hamburg.ccc.de/ccchh/spaceapid/types"
)
const (
persistentStateFile = "spaceapid-state.json"
persistentStatePath = persistentStateDir + persistentStateFile
)
// 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.SpaceAPIResponse) {
var (
err error
oldState []byte
persistedState types.PersistentSpaceState
)
log.Println("Merging old state from", persistentStatePath, "...")
// Check if state.json is present
_, err = os.Stat(persistentStatePath)
if err != nil {
log.Println("Old state json not accessible at", persistentStatePath, ", skipping merge... error:", err)
if errors.Is(err, os.ErrNotExist) {
return
}
goto removeOld
}
// Read file and load persisted state
oldState, err = os.ReadFile(persistentStatePath)
if err != nil {
log.Println("Error reading old state from", persistentStatePath, ", skipping merge... error:", err)
goto removeOld
}
err = json.Unmarshal(oldState, &persistedState)
if err != nil {
log.Println(persistentStatePath, "doesn't seem to contain valid data... error:", err)
goto removeOld
}
// Merge state
response.State.FromPersistentSpaceState(persistedState)
// Merge sensors
for key, environmentSensors := range persistedState.Sensors {
for _, sensor := range environmentSensors {
// Order or amount of sensors might have changed, so check sensors already present in response
// and then look for matching values in persisted state
for i := range response.Sensors[key] {
if rs := &response.Sensors[key][i]; rs.Location == sensor.Location && rs.Name == sensor.Name {
rs.Value = sensor.Value
rs.LastChange = sensor.LastChange
}
}
}
}
// Delete old state json
removeOld:
err = os.RemoveAll(persistentStatePath)
if err != nil {
log.Println("Failed to remove", persistentStatePath, ", continuing... error:", err)
}
}
func SaveCurrentState(response types.SpaceAPIResponse) {
// Create state directory if not present
err := os.MkdirAll(persistentStateDir, 0750)
if err != nil {
log.Fatalln("Failed creating", persistentStateDir, ", aborting... error:", err)
}
// Open persistent state file
file, err := os.OpenFile(persistentStatePath, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatalln("Failed opening", persistentStatePath, "while trying to save current state... error:", err)
}
defer file.Close()
// Create persistent state
persistentStateV14 := types.PersistentSpaceState{
State: struct {
Open bool `json:"open"`
LastChange int64 `json:"lastchange"`
Message string `json:"message,omitempty"`
}{Open: response.State.Open, LastChange: response.State.LastChange, Message: response.State.Message},
}
// Save sensor state
persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
for key, environmentSensors := range response.Sensors {
persistentStateV14.Sensors[key] = make([]types.PersistentEnvironmentSensor, len(environmentSensors))
for i, sensor := range environmentSensors {
persistentStateV14.Sensors[key][i].Value = sensor.Value
persistentStateV14.Sensors[key][i].Location = sensor.Location
persistentStateV14.Sensors[key][i].Name = sensor.Name
persistentStateV14.Sensors[key][i].LastChange = sensor.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)
}
}