Refactoring #34
					 17 changed files with 197 additions and 150 deletions
				
			
		
							
								
								
									
										6
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -11,6 +11,6 @@ spaceapid | ||||||
| spaceapid-state.json | spaceapid-state.json | ||||||
| 
 | 
 | ||||||
| # Config shards | # Config shards | ||||||
| credentials.json | config-credentials.json | ||||||
| dynamic.json | config-dynamic.json | ||||||
| response.json | config-response.json | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
										
									
									
									
								
							|  | @ -3,7 +3,7 @@ | ||||||
| `spaceapid` serves a [SpaceAPI](https://spaceapi.io)-compatible JSON on port 8080: | `spaceapid` serves a [SpaceAPI](https://spaceapi.io)-compatible JSON on port 8080: | ||||||
| 
 | 
 | ||||||
| ```shell | ```shell | ||||||
| $ curl -X GET http://[::1]:8080 | jq | $ curl http://[::1]:8080 | jq | ||||||
| { | { | ||||||
|   "api_compatibility": [ |   "api_compatibility": [ | ||||||
|     "14" |     "14" | ||||||
|  | @ -26,7 +26,7 @@ The config consists of three parts: | ||||||
| - `"response"` | - `"response"` | ||||||
| 	- The static (pre-filled) parts of the response | 	- The static (pre-filled) parts of the response | ||||||
| 
 | 
 | ||||||
| See [Running](#running) for details. | See [Running](#Running) for details. | ||||||
| 
 | 
 | ||||||
| ## Updating values | ## Updating values | ||||||
| 
 | 
 | ||||||
|  | @ -37,12 +37,18 @@ curl -X PUT -u user:password -d true http://[::1]:8080/state/open | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The same is true for the endpoints for sensors configured under `"dynamic"`. | The same is true for the endpoints for sensors configured under `"dynamic"`. | ||||||
| Currently only `temperature` and `humidity` are implemented. | Currently only the sensors with the `value/unit/location/name/description` schema are implemented. | ||||||
|  | At the time of writing this includes `temperature`, `barometer`, `humidity`, `beverage_supply`, `power_consumption`, | ||||||
|  | and `account_balance`. | ||||||
|  | Out-of-spec sensors may also be used as long as they share the same schema. | ||||||
| 
 | 
 | ||||||
| ```shell | ```shell | ||||||
| curl -X PUT -u user:password -d 23.42 http://[::1]:8080/sensors/{temperature,humidity}/location[/name] | curl -X PUT -u user:password -d 23.42 http://[::1]:8080/sensors/{temperature,humidity,...}[/location[/name]] | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | As can be seen in the example, the http urls are generated from sensor type and optionally `location` and `name`. | ||||||
|  | Depending on sensor type `location` might be required for your sensors, see the schema for details. | ||||||
|  | 
 | ||||||
| ## Building | ## Building | ||||||
| 
 | 
 | ||||||
| See the `go.mod` file for minimum required Go version. | See the `go.mod` file for minimum required Go version. | ||||||
|  | @ -55,5 +61,5 @@ Set the environment variable to a comma-separated list of config files or pass t | ||||||
| ```shell | ```shell | ||||||
| env CONFIG_PATH=config-template.json go run . | env CONFIG_PATH=config-template.json go run . | ||||||
| # OR | # OR | ||||||
| go run . -c credentials.json,dynamic.json,response.json | go run . -c config-credentials.json,config-dynamic.json,config-response.json | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -46,6 +46,16 @@ | ||||||
|             "club-assistant" |             "club-assistant" | ||||||
|           ] |           ] | ||||||
|         } |         } | ||||||
|  |       ], | ||||||
|  |       "beverage_supply": [ | ||||||
|  |         { | ||||||
|  |           "sensor_data": { | ||||||
|  |             "unit": "btl" | ||||||
|  |           }, | ||||||
|  |           "allowed_credentials": [ | ||||||
|  |             "club-assistant" | ||||||
|  |           ] | ||||||
|  |         } | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     "state": { |     "state": { | ||||||
|  | @ -66,8 +76,8 @@ | ||||||
|     "url": "https://hamburg.ccc.de/", |     "url": "https://hamburg.ccc.de/", | ||||||
|     "location": { |     "location": { | ||||||
|       "address": "Zeiseweg 9, 22765 Hamburg, Germany", |       "address": "Zeiseweg 9, 22765 Hamburg, Germany", | ||||||
|       "lon": 9.9445899999999998, |       "lon": 9.94444, | ||||||
|       "lat": 53.55836 |       "lat": 53.5584 | ||||||
|     }, |     }, | ||||||
|     "contact": { |     "contact": { | ||||||
|       "phone": "+49 40 23830150", |       "phone": "+49 40 23830150", | ||||||
|  | @ -79,7 +89,7 @@ | ||||||
|     }, |     }, | ||||||
|     "feeds": { |     "feeds": { | ||||||
|       "blog": { |       "blog": { | ||||||
|         "type": "application/atom+xml", |         "type": "application/rss+xml", | ||||||
|         "url": "https://hamburg.ccc.de/feed.xml" |         "url": "https://hamburg.ccc.de/feed.xml" | ||||||
|       }, |       }, | ||||||
|       "calendar": { |       "calendar": { | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ import ( | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" | 	"git.hamburg.ccc.de/ccchh/spaceapid/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| package config | package config | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" | 	"git.hamburg.ccc.de/ccchh/spaceapid/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type HTTPBACredentialID string | type HTTPBACredentialID string | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -1,3 +1,3 @@ | ||||||
| module gitlab.hamburg.ccc.de/ccchh/spaceapid | module git.hamburg.ccc.de/ccchh/spaceapid | ||||||
| 
 | 
 | ||||||
| go 1.21 | go 1.21 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import ( | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" | 	"git.hamburg.ccc.de/ccchh/spaceapid/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) { | func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) { | ||||||
|  |  | ||||||
|  | @ -7,8 +7,8 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/config" | 	"git.hamburg.ccc.de/ccchh/spaceapid/config" | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" | 	"git.hamburg.ccc.de/ccchh/spaceapid/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func EnvironmentSensor( | func EnvironmentSensor( | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/config" | 	"git.hamburg.ccc.de/ccchh/spaceapid/config" | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" | 	"git.hamburg.ccc.de/ccchh/spaceapid/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func StateOpen( | func StateOpen( | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/config" | 	"git.hamburg.ccc.de/ccchh/spaceapid/config" | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/util" | 	"git.hamburg.ccc.de/ccchh/spaceapid/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // updateEndpointValidator checks BasicAuth credentials, | // updateEndpointValidator checks BasicAuth credentials, | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										29
									
								
								main.go
									
										
									
									
									
								
							|  | @ -2,18 +2,17 @@ package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"strings" |  | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 
 | 
 | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/config" | 	"git.hamburg.ccc.de/ccchh/spaceapid/config" | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/handlers" | 	"git.hamburg.ccc.de/ccchh/spaceapid/handlers" | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" | 	"git.hamburg.ccc.de/ccchh/spaceapid/persistence" | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/util" | 	"git.hamburg.ccc.de/ccchh/spaceapid/types" | ||||||
|  | 	"git.hamburg.ccc.de/ccchh/spaceapid/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -39,7 +38,7 @@ func main() { | ||||||
| 	conf := config.ParseConfiguration() | 	conf := config.ParseConfiguration() | ||||||
| 
 | 
 | ||||||
| 	// Merge old state if present | 	// Merge old state if present | ||||||
| 	util.MergeOldState(&conf.Response) | 	persistence.MergeOldState(&conf.Response) | ||||||
| 
 | 
 | ||||||
| 	// Register signal handler | 	// Register signal handler | ||||||
| 	sc := make(chan os.Signal, 1) | 	sc := make(chan os.Signal, 1) | ||||||
|  | @ -47,7 +46,7 @@ func main() { | ||||||
| 	go func(resp *types.SpaceAPIResponseV14) { | 	go func(resp *types.SpaceAPIResponseV14) { | ||||||
| 		<-sc | 		<-sc | ||||||
| 		log.Println("Saving state and shutting down...") | 		log.Println("Saving state and shutting down...") | ||||||
| 		util.SaveCurrentState(*resp) | 		persistence.SaveCurrentState(*resp) | ||||||
| 		os.Exit(0) | 		os.Exit(0) | ||||||
| 	}(&conf.Response) | 	}(&conf.Response) | ||||||
| 
 | 
 | ||||||
|  | @ -59,15 +58,15 @@ func main() { | ||||||
| 		handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State), | 		handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State), | ||||||
| 	) | 	) | ||||||
| 	// Register handlers for Environmental Sensors | 	// Register handlers for Environmental Sensors | ||||||
| 	for key, envSensorConfigs := range conf.Dynamic.Sensors { | 	for sensorType, envSensorConfigs := range conf.Dynamic.Sensors { | ||||||
| 		for i, envSensorConfig := range envSensorConfigs { | 		for i, envSensorConfig := range envSensorConfigs { | ||||||
| 			pattern := fmt.Sprintf("/sensors/%s/%s", key, envSensorConfig.SensorData.Location) | 			urlPath := util.GetSensorURLPath( | ||||||
| 			if envSensorConfig.SensorData.Name != "" { | 				sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name, | ||||||
| 				pattern += "/" + envSensorConfig.SensorData.Name | 			) | ||||||
| 			} | 			http.HandleFunc( | ||||||
| 			http.HandleFunc(strings.ToLower(pattern), | 				urlPath, | ||||||
| 				handlers.EnvironmentSensor( | 				handlers.EnvironmentSensor( | ||||||
| 					conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[key][i], | 					conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i], | ||||||
| 				), | 				), | ||||||
| 			) | 			) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
							
								
								
									
										119
									
								
								persistence/persistentState.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								persistence/persistentState.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,119 @@ | ||||||
|  | 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.SpaceAPIResponseV14) { | ||||||
|  | 	var ( | ||||||
|  | 		err            error | ||||||
|  | 		oldState       []byte | ||||||
|  | 		persistedState types.PersistentStateV14 | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	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 = persistedState.State | ||||||
|  | 
 | ||||||
|  | 	// 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.SpaceAPIResponseV14) { | ||||||
|  | 	// 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.PersistentStateV14{ | ||||||
|  | 		State: struct { | ||||||
|  | 			Open       bool  `json:"open"` | ||||||
|  | 			LastChange int64 `json:"lastchange"` | ||||||
|  | 		}{Open: response.State.Open, LastChange: response.State.LastChange}, | ||||||
|  | 	} | ||||||
|  | 	// 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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								persistence/persistentStateDir.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								persistence/persistentStateDir.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | //go:build !linux | ||||||
|  | 
 | ||||||
|  | package persistence | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	persistentStateDir = "./" | ||||||
|  | ) | ||||||
							
								
								
									
										5
									
								
								persistence/persistentStateDir_linux.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								persistence/persistentStateDir_linux.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | package persistence | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	persistentStateDir = "/var/lib/spaceapid/" | ||||||
|  | ) | ||||||
|  | @ -45,7 +45,7 @@ type SpaceState struct { | ||||||
| type EnvironmentSensor struct { | type EnvironmentSensor struct { | ||||||
| 	Value       float64 `json:"value"` | 	Value       float64 `json:"value"` | ||||||
| 	Unit        string  `json:"unit"` | 	Unit        string  `json:"unit"` | ||||||
| 	Location    string  `json:"location"` | 	Location    string  `json:"location,omitempty"` | ||||||
| 	Name        string  `json:"name,omitempty"` | 	Name        string  `json:"name,omitempty"` | ||||||
| 	Description string  `json:"description,omitempty"` | 	Description string  `json:"description,omitempty"` | ||||||
| 	LastChange  int64   `json:"lastchange,omitempty"` | 	LastChange  int64   `json:"lastchange,omitempty"` | ||||||
|  | @ -61,7 +61,7 @@ type PersistentStateV14 struct { | ||||||
| 
 | 
 | ||||||
| type PersistentEnvironmentSensor struct { | type PersistentEnvironmentSensor struct { | ||||||
| 	Value      float64 `json:"value"` | 	Value      float64 `json:"value"` | ||||||
| 	Location   string  `json:"location"` | 	Location   string  `json:"location,omitempty"` | ||||||
| 	Name       string  `json:"name,omitempty"` | 	Name       string  `json:"name,omitempty"` | ||||||
| 	LastChange int64   `json:"lastchange"` | 	LastChange int64   `json:"lastchange"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| package util | package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/config" | 	"git.hamburg.ccc.de/ccchh/spaceapid/config" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // CheckCredentials validates whether a given username/password pair matches an | // CheckCredentials validates whether a given username/password pair matches an | ||||||
|  |  | ||||||
							
								
								
									
										123
									
								
								util/util.go
									
										
									
									
									
								
							
							
						
						
									
										123
									
								
								util/util.go
									
										
									
									
									
								
							|  | @ -1,119 +1,20 @@ | ||||||
| package util | package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"fmt" | ||||||
| 	"errors" | 	"strings" | ||||||
| 	"log" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 
 |  | ||||||
| 	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | // GetSensorURLPath generates the URL path for the given sensor details. | ||||||
| 	savedStateJSONPath = "/var/lib/spaceapid/spaceapid-state.json" | // location and name may be optional depending on sensorType, see the schema definition for details. | ||||||
| ) | // The path is always all-lowercase. | ||||||
| 
 | func GetSensorURLPath(sensorType, location, name string) string { | ||||||
| // MergeOldState merges a given SpaceAPIResponse with the state saved at the time of last program exit and then deletes | 	path := fmt.Sprintf("/sensors/%s", sensorType) | ||||||
| // the file containing the old state. | 	if location != "" { | ||||||
| func MergeOldState(response *types.SpaceAPIResponseV14) { | 		path += "/" + location | ||||||
| 	var ( |  | ||||||
| 		err            error |  | ||||||
| 		oldState       []byte |  | ||||||
| 		persistedState types.PersistentStateV14 |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	log.Println("Merging old state from", savedStateJSONPath, "...") |  | ||||||
| 
 |  | ||||||
| 	// 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 | 	if name != "" { | ||||||
| 	} | 		path += "/" + name | ||||||
| 
 |  | ||||||
| 	// Read file and load persisted state |  | ||||||
| 	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, &persistedState) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Println(savedStateJSONPath, "doesn't seem to contain valid data... error:", err) |  | ||||||
| 		goto removeOld |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Merge state |  | ||||||
| 	response.State = persistedState.State |  | ||||||
| 
 |  | ||||||
| 	// 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(savedStateJSONPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Println("Failed to remove", savedStateJSONPath, ", continuing... error:", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func SaveCurrentState(response types.SpaceAPIResponseV14) { |  | ||||||
| 	// Create state directory if not present |  | ||||||
| 	err := os.MkdirAll(path.Dir(savedStateJSONPath), 0750) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatalln("Failed creating", savedStateJSONPath, ", aborting... error:", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Open persistent state file |  | ||||||
| 	file, err := os.OpenFile(savedStateJSONPath, os.O_WRONLY|os.O_CREATE, 0644) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatalln("Failed opening", savedStateJSONPath, "while trying to save current state... error:", err) |  | ||||||
| 	} |  | ||||||
| 	defer file.Close() |  | ||||||
| 
 |  | ||||||
| 	// Create persistent state |  | ||||||
| 	persistentStateV14 := types.PersistentStateV14{ |  | ||||||
| 		State: struct { |  | ||||||
| 			Open       bool  `json:"open"` |  | ||||||
| 			LastChange int64 `json:"lastchange"` |  | ||||||
| 		}{Open: response.State.Open, LastChange: response.State.LastChange}, |  | ||||||
| 	} |  | ||||||
| 	// 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) |  | ||||||
| 	} | 	} | ||||||
|  | 	return strings.ToLower(path) | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue