internal/app/state: include et header
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m17s
Test / Hakurei (push) Successful in 3m5s
Test / Hpkg (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 4m2s
Test / Hakurei (race detector) (push) Successful in 4m49s
Test / Flake checks (push) Successful in 1m22s

This is the initial step of implementing #19.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-10-25 22:01:26 +09:00
parent 470e545d27
commit fe2929d5f7
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
2 changed files with 18 additions and 17 deletions

View File

@ -117,7 +117,7 @@ type multiBackend struct {
func (b *multiBackend) filename(id *hst.ID) string { return path.Join(b.path, id.String()) }
// reads all launchers in simpleBackend
// reads all launchers in multiBackend
// file contents are ignored if decode is false
func (b *multiBackend) load(decode bool) (map[hst.ID]*hst.State, error) {
b.mu.RLock()
@ -161,18 +161,22 @@ func (b *multiBackend) load(decode bool) (map[hst.ID]*hst.State, error) {
// append regardless, but only parse if required, implements Len
if decode {
if err = gob.NewDecoder(f).Decode(&s); err != nil {
var et hst.Enablement
if et, err = entryReadHeader(f); err != nil {
_ = f.Close()
return &hst.AppError{Step: "decode state data", Err: err}
return &hst.AppError{Step: "decode state header", Err: err}
} else if err = gob.NewDecoder(f).Decode(&s); err != nil {
_ = f.Close()
return &hst.AppError{Step: "decode state body", Err: err}
} else if s.ID != id {
_ = f.Close()
return fmt.Errorf("state entry %s has unexpected id %s", id, &s.ID)
} else if err = f.Close(); err != nil {
return &hst.AppError{Step: "close state file", Err: err}
}
if s.Config == nil {
return ErrNoConfig
} else if err = s.Config.Validate(); err != nil {
return err
} else if s.Enablements.Unwrap() != et {
return fmt.Errorf("state entry %s has unexpected enablement byte %x, %x", id, s.Enablements, et)
}
}
@ -186,22 +190,24 @@ func (b *multiBackend) load(decode bool) (map[hst.ID]*hst.State, error) {
return r, nil
}
// Save writes process state to filesystem
// Save writes process state to filesystem.
func (b *multiBackend) Save(state *hst.State) error {
b.mu.Lock()
defer b.mu.Unlock()
if state.Config == nil {
return ErrNoConfig
if err := state.Config.Validate(); err != nil {
return err
}
statePath := b.filename(&state.ID)
if f, err := os.OpenFile(statePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600); err != nil {
return &hst.AppError{Step: "create state file", Err: err}
} else if err = entryWriteHeader(f, state.Enablements.Unwrap()); err != nil {
_ = f.Close()
return &hst.AppError{Step: "encode state header", Err: err}
} else if err = gob.NewEncoder(f).Encode(state); err != nil {
_ = f.Close()
return &hst.AppError{Step: "encode state data", Err: err}
return &hst.AppError{Step: "encode state body", Err: err}
} else if err = f.Close(); err != nil {
return &hst.AppError{Step: "close state file", Err: err}
}

View File

@ -2,14 +2,9 @@
package state
import (
"errors"
"hakurei.app/hst"
)
// ErrNoConfig is returned by [Cursor] when used with a nil [hst.Config].
var ErrNoConfig = errors.New("state does not contain config")
type Store interface {
// Do calls f exactly once and ensures store exclusivity until f returns.
// Returns whether f is called and any errors during the locking process.