Implemented confi snapshots
This commit is contained in:
@@ -17,19 +17,25 @@ var ErrNotFound = errors.New("control logic graph not found")
|
||||
|
||||
// Store persists control-logic graphs as a single JSON file in the storage dir.
|
||||
// Writes are atomic (tmp file + rename); all access is mutex-guarded.
|
||||
//
|
||||
// Git-style versioning: the live graph lives in controllogic.json (the current
|
||||
// revision), while every superseded revision is preserved as a backup file
|
||||
// {id}.vN.json under versionsDir. Promote/Fork build on these backups.
|
||||
type Store struct {
|
||||
mu sync.RWMutex
|
||||
path string
|
||||
trashDir string
|
||||
items map[string]Graph
|
||||
mu sync.RWMutex
|
||||
path string
|
||||
trashDir string
|
||||
versionsDir string
|
||||
items map[string]Graph
|
||||
}
|
||||
|
||||
// NewStore opens (or initialises) the control-logic store under storageDir.
|
||||
func NewStore(storageDir string) (*Store, error) {
|
||||
s := &Store{
|
||||
path: filepath.Join(storageDir, definitionsFile),
|
||||
trashDir: filepath.Join(storageDir, "trash", "controllogic"),
|
||||
items: map[string]Graph{},
|
||||
path: filepath.Join(storageDir, definitionsFile),
|
||||
trashDir: filepath.Join(storageDir, "trash", "controllogic"),
|
||||
versionsDir: filepath.Join(storageDir, "controllogic_versions"),
|
||||
items: map[string]Graph{},
|
||||
}
|
||||
if err := s.load(); err != nil {
|
||||
return nil, err
|
||||
@@ -94,14 +100,46 @@ func (s *Store) Get(id string) (Graph, error) {
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Save inserts or replaces a graph and persists the store.
|
||||
// Save inserts or replaces a graph and persists the store. When replacing an
|
||||
// existing graph the superseded revision is backed up as {id}.vN.json and the
|
||||
// new graph's Version is bumped; a brand-new graph starts at version 1.
|
||||
func (s *Store) Save(g Graph) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if old, ok := s.items[g.ID]; ok {
|
||||
oldV := old.Version
|
||||
if oldV < 1 {
|
||||
oldV = 1
|
||||
old.Version = 1
|
||||
}
|
||||
if err := s.backupLocked(old); err != nil {
|
||||
return fmt.Errorf("back up control logic revision: %w", err)
|
||||
}
|
||||
g.Version = oldV + 1
|
||||
} else if g.Version < 1 {
|
||||
g.Version = 1
|
||||
}
|
||||
s.items[g.ID] = g
|
||||
return s.saveLocked()
|
||||
}
|
||||
|
||||
// backupLocked writes a single graph revision to versionsDir as {id}.vN.json.
|
||||
// Caller must hold s.mu.
|
||||
func (s *Store) backupLocked(g Graph) error {
|
||||
if err := os.MkdirAll(s.versionsDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(g, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(s.versionPath(g.ID, g.Version), data, 0o644)
|
||||
}
|
||||
|
||||
func (s *Store) versionPath(id string, version int) string {
|
||||
return filepath.Join(s.versionsDir, fmt.Sprintf("%s.v%d.json", id, version))
|
||||
}
|
||||
|
||||
// Delete removes a graph by id, first writing a copy into the trash folder so it
|
||||
// can be recovered if needed. The trash backup is best-effort: a failure to write
|
||||
// it does not prevent the delete (but is reported).
|
||||
|
||||
Reference in New Issue
Block a user