Skip to content

Commit

Permalink
Add persistent history to application
Browse files Browse the repository at this point in the history
The application saves the bins and errors history state every 10 minutes
and when disconecting from the device. The storage path is configurable
using the new command-line option "-state-dir", which also allows to
turn this feature off.
  • Loading branch information
janh committed Nov 28, 2024
1 parent 68a2260 commit 56dfe8c
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 10 deletions.
2 changes: 2 additions & 0 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

var (
DefaultConfigPath string
DefaultStateDir string
DefaultKnownHostsPath string
DefaultPrivateKeyPath string
)
Expand Down Expand Up @@ -153,4 +154,5 @@ func init() {
DefaultKnownHostsPath = filepath.Join(xdg.Home, ".ssh", "known_hosts")

DefaultConfigPath = filepath.Join(xdg.ConfigHome, "3e8.eu-go-dsl", "config.toml")
DefaultStateDir = filepath.Join(xdg.ConfigHome, "3e8.eu-go-dsl", "state")
}
7 changes: 5 additions & 2 deletions cmd/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ var (
lastMessage common.Message
mutex sync.Mutex
mutexClient sync.Mutex
stateDir string
)

func Run() {
func Run(newStateDir string) {
updateState(common.Message{State: stateConnect})

stateDir = newStateDir

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

Expand Down Expand Up @@ -90,7 +93,7 @@ func clientConnect(clientConfig dsl.Config) {
mutexClient.Lock()
defer mutexClient.Unlock()

c = common.NewClient(clientConfig)
c = common.NewClient(clientConfig, stateDir)

startReceive <- true
<-startDone
Expand Down
2 changes: 1 addition & 1 deletion cmd/gui/nogui.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ package gui

const Enabled = false

func Run() {}
func Run(stateDir string) {}
7 changes: 5 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ func main() {
var configPath string
flagSet.StringVar(&configPath, "config", config.DefaultConfigPath, "path to configuration file")

var stateDir string
flagSet.StringVar(&stateDir, "state-dir", config.DefaultStateDir, "path to state directory, set to empty string to disable persistent history")

var secretsPath string
flagSet.StringVar(&secretsPath, "secrets", "", "path to secrets file")

Expand Down Expand Up @@ -173,7 +176,7 @@ func main() {
}

if gui.Enabled && (startGUI || len(os.Args) == 1) {
gui.Run()
gui.Run(stateDir)
} else {
err = config.Validate()
if err != nil {
Expand All @@ -187,7 +190,7 @@ func main() {
}

if startWebServer {
web.Run(clientConfig, config.Config.Web)
web.Run(clientConfig, config.Config.Web, stateDir)
} else {
cli.LoadData(clientConfig)
}
Expand Down
18 changes: 17 additions & 1 deletion cmd/web/common/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
const (
intervalDefault time.Duration = 30 * time.Second
intervalShort time.Duration = 10 * time.Second
intervalSave time.Duration = 10 * time.Minute
)

type StateChange struct {
Expand Down Expand Up @@ -73,9 +74,11 @@ type Client struct {
password string
passphrase map[string]string
encryptionPassphrase string

stateDir string
}

func NewClient(config dsl.Config) *Client {
func NewClient(config dsl.Config, stateDir string) *Client {
c := &Client{
setPassword: make(chan string),
setPassphrase: make(chan string),
Expand All @@ -92,6 +95,7 @@ func NewClient(config dsl.Config) *Client {
done: make(chan bool),
config: config,
passphrase: make(map[string]string),
stateDir: stateDir,
}

go c.distribute()
Expand Down Expand Up @@ -328,6 +332,9 @@ func (c *Client) update() {
panic(err)
}

c.loadHistory(binsHistory, errorsHistory)
nextSave := time.Now().Truncate(intervalSave).Add(intervalSave)

mainloop:
for {
for i := 0; i < 2; i++ {
Expand Down Expand Up @@ -362,6 +369,11 @@ mainloop:

c.errCount = 0

if now.After(nextSave) {
c.saveHistory(binsHistory, errorsHistory)
nextSave = time.Now().Truncate(intervalSave).Add(intervalSave)
}

break

} else {
Expand Down Expand Up @@ -398,6 +410,10 @@ mainloop:
}
}

if c.lastData.HasData {
c.saveHistory(binsHistory, errorsHistory)
}

if c.client != nil {
c.client.Close()
}
Expand Down
103 changes: 103 additions & 0 deletions cmd/web/common/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package common

import (
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"

"3e8.eu/go/dsl/history"
)

func (c *Client) readStateFile(filename string, readFunc func(r io.Reader) error) (err error) {
file, err := os.Open(filepath.Join(c.stateDir, filename))
if err != nil {
return
}
defer func() {
closeErr := file.Close()
if err == nil {
err = closeErr
}
}()

reader, err := gzip.NewReader(file)
if err != nil {
return
}
defer func() {
closeErr := reader.Close()
if err == nil {
err = closeErr
}
}()

err = readFunc(reader)
return
}

func (c *Client) writeStateFile(filename string, writeFunc func(w io.Writer) error) (err error) {
err = os.MkdirAll(c.stateDir, os.ModePerm)
if err != nil {
return
}

file, err := os.Create(filepath.Join(c.stateDir, filename))
if err != nil {
return
}
defer func() {
closeErr := file.Close()
if err == nil {
err = closeErr
}
}()

writer := gzip.NewWriter(file)
defer func() {
closeErr := writer.Close()
if err == nil {
err = closeErr
}
}()

err = writeFunc(writer)
return
}

func (c *Client) loadHistory(bins *history.Bins, errors *history.Errors) {
if c.stateDir == "" {
return
}

err := c.readStateFile("bins.dat.gz", bins.Load)
if err != nil && !os.IsNotExist(err) {
fmt.Println("failed to load bins history:", err)
}

err = c.readStateFile("errors.dat.gz", errors.Load)
if err != nil && !os.IsNotExist(err) {
fmt.Println("failed to load errors history:", err)
}
}

func (c *Client) saveHistory(bins *history.Bins, errors *history.Errors) {
if c.stateDir == "" {
return
}

err := c.writeStateFile("bins.dat.gz", bins.Save)
if err != nil {
fmt.Println("failed to save bins history:", err)
}

err = c.writeStateFile("errors.dat.gz", errors.Save)
if err != nil {
fmt.Println("failed to save errors history:", err)
}
}
8 changes: 4 additions & 4 deletions cmd/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ var (
config Config
)

func Run(clientConfig dsl.Config, webConfig Config) {
func Run(clientConfig dsl.Config, webConfig Config, stateDir string) {
config = webConfig

if config.ListenAddress == "" {
config.ListenAddress = "[::1]:0"
}

addr, err := start(clientConfig)
addr, err := start(clientConfig, stateDir)
if err != nil {
fmt.Println("failed to start web server:", err)
os.Exit(1)
Expand Down Expand Up @@ -75,7 +75,7 @@ func Run(clientConfig dsl.Config, webConfig Config) {
}
}

func start(clientConfig dsl.Config) (addr string, err error) {
func start(clientConfig dsl.Config, stateDir string) (addr string, err error) {
http.HandleFunc("/", handleRoot)

static := &staticHandler{}
Expand All @@ -101,7 +101,7 @@ func start(clientConfig dsl.Config) (addr string, err error) {

addr = "http://" + listener.Addr().String()

c = common.NewClient(clientConfig)
c = common.NewClient(clientConfig, stateDir)

shutdownReceivers = make(map[chan bool]bool)
server.RegisterOnShutdown(handleOnShutdown)
Expand Down

0 comments on commit 56dfe8c

Please sign in to comment.