Implemented confi snapshots
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package controllogic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// VersionMeta describes a single persisted revision of a control-logic graph,
|
||||
// mirroring storage.VersionMeta so the frontend can treat all versioned
|
||||
// document types uniformly.
|
||||
type VersionMeta struct {
|
||||
Version int `json:"version"`
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Current bool `json:"current"`
|
||||
SavedAt time.Time `json:"savedAt"`
|
||||
}
|
||||
|
||||
// Versions returns metadata for every persisted revision of the graph, newest
|
||||
// first. The live revision (held in controllogic.json) is flagged Current.
|
||||
func (s *Store) Versions(id string) ([]VersionMeta, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
cur, ok := s.items[id]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
curV := cur.Version
|
||||
if curV < 1 {
|
||||
curV = 1
|
||||
}
|
||||
|
||||
var savedAt time.Time
|
||||
if info, err := os.Stat(s.path); err == nil {
|
||||
savedAt = info.ModTime()
|
||||
}
|
||||
out := []VersionMeta{{Version: curV, Name: cur.Name, Tag: cur.Tag, Current: true, SavedAt: savedAt}}
|
||||
|
||||
entries, err := os.ReadDir(s.versionsDir)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
prefix := id + ".v"
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if e.IsDir() || !strings.HasPrefix(name, prefix) || !strings.HasSuffix(name, ".json") {
|
||||
continue
|
||||
}
|
||||
vStr := strings.TrimSuffix(strings.TrimPrefix(name, prefix), ".json")
|
||||
v, err := strconv.Atoi(vStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
g, err := s.readVersion(id, v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
info, err := e.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, VersionMeta{Version: v, Name: g.Name, Tag: g.Tag, SavedAt: info.ModTime()})
|
||||
}
|
||||
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Version > out[j].Version })
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetVersion returns a specific revision of the graph. The current revision is
|
||||
// served from the live store; older revisions come from their backup file.
|
||||
func (s *Store) GetVersion(id string, version int) (Graph, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
cur, ok := s.items[id]
|
||||
if !ok {
|
||||
return Graph{}, ErrNotFound
|
||||
}
|
||||
curV := cur.Version
|
||||
if curV < 1 {
|
||||
curV = 1
|
||||
}
|
||||
if version == curV {
|
||||
return cur, nil
|
||||
}
|
||||
return s.readVersion(id, version)
|
||||
}
|
||||
|
||||
// readVersion loads a backup revision file. Caller must hold s.mu.
|
||||
func (s *Store) readVersion(id string, version int) (Graph, error) {
|
||||
data, err := os.ReadFile(s.versionPath(id, version))
|
||||
if os.IsNotExist(err) {
|
||||
return Graph{}, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
var g Graph
|
||||
if err := json.Unmarshal(data, &g); err != nil {
|
||||
return Graph{}, fmt.Errorf("parse revision: %w", err)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Promote makes a past revision current by re-saving it on top of history. The
|
||||
// existing current revision is preserved as a backup, so promotion is
|
||||
// non-destructive. Returns the resulting (new current) graph.
|
||||
func (s *Store) Promote(id string, version int) (Graph, error) {
|
||||
g, err := s.GetVersion(id, version)
|
||||
if err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
g.Tag = fmt.Sprintf("restored from v%d", version)
|
||||
if err := s.Save(g); err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
return s.Get(id)
|
||||
}
|
||||
|
||||
// Fork creates a brand-new graph from a specific revision, assigning a fresh id
|
||||
// and resetting its version to 1. Returns the new graph.
|
||||
func (s *Store) Fork(id string, version int) (Graph, error) {
|
||||
g, err := s.GetVersion(id, version)
|
||||
if err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
g.ID = fmt.Sprintf("%s-fork-%d", id, time.Now().UnixMilli())
|
||||
g.Version = 1
|
||||
g.Tag = ""
|
||||
if g.Name != "" {
|
||||
g.Name = g.Name + " (fork)"
|
||||
}
|
||||
if err := s.Save(g); err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
Reference in New Issue
Block a user