kept second-drive copies (D25: R-1175, R-1181, R-1179); .fab import keeps the family gate (R-1191); stale restore temp (R-1188 c); the overcommit sentence made true (R-1192); escrow before the floor (R-1176); unreleased
gates / gates (push) Successful in 1m20s
gates / gates (push) Successful in 1m20s
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// KeptCopy is one second-drive copy whose source is gone (D25, operator ruling 2026-10-21). The record is what the
|
||||
// backups page lists and what „Eltávolítás" removes — by Path, identity-checked against Dev/Ino, never by size or a
|
||||
// name pattern (07 §8.2 as amended by D25).
|
||||
type KeptCopy struct {
|
||||
ID string `json:"id"` // stable: sha256 of Path, first 16 hex
|
||||
Kind string `json:"kind"` // "share" | "app"
|
||||
Source string `json:"source"` // the share's name, or "<app>: <folder>"
|
||||
Drive string `json:"drive"` // the SOURCE drive's label (or its path when the drive is no longer registered)
|
||||
Reason string `json:"reason"` // "drive_away" | "drive_gone" | "folder_missing"
|
||||
DestBase string `json:"dest_base"` // the copy tree it lives in; Path must stay inside it
|
||||
Path string `json:"path"` // the copy's absolute directory
|
||||
Dev uint64 `json:"dev"` // identity at record time
|
||||
Ino uint64 `json:"ino"` // identity at record time
|
||||
SizeBytes int64 `json:"size_bytes"` // at the latest run
|
||||
Since string `json:"since"` // RFC3339, first time it was kept
|
||||
}
|
||||
|
||||
// KeptCopyID is the record's id for a copy path.
|
||||
func KeptCopyID(path string) string {
|
||||
h := sha256.Sum256([]byte(path))
|
||||
return hex.EncodeToString(h[:8])
|
||||
}
|
||||
|
||||
// GetKeptCopies returns a copy of the kept-copy records.
|
||||
func (s *Settings) GetKeptCopies() []KeptCopy {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return append([]KeptCopy(nil), s.KeptCopies...)
|
||||
}
|
||||
|
||||
// UpsertKeptCopy records a kept copy (or refreshes its size and identity), keeping the first Since.
|
||||
func (s *Settings) UpsertKeptCopy(k KeptCopy) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if k.Path == "" || k.DestBase == "" {
|
||||
return fmt.Errorf("kept copy: empty path or dest base")
|
||||
}
|
||||
k.ID = KeptCopyID(k.Path)
|
||||
for i := range s.KeptCopies {
|
||||
if s.KeptCopies[i].ID == k.ID {
|
||||
since := s.KeptCopies[i].Since
|
||||
k.Since = since
|
||||
if s.KeptCopies[i] == k {
|
||||
return nil // nothing changed — no write
|
||||
}
|
||||
s.KeptCopies[i] = k
|
||||
return s.save()
|
||||
}
|
||||
}
|
||||
if k.Since == "" {
|
||||
k.Since = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
s.KeptCopies = append(s.KeptCopies, k)
|
||||
if s.log != nil {
|
||||
s.log.Printf("[INFO] [settings] kept copy recorded: kind=%s source=%q reason=%s path=%s", k.Kind, k.Source, k.Reason, k.Path)
|
||||
}
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// DropKeptCopy forgets the record of a copy path (the source came back, or the household removed the copy). The
|
||||
// copy's data is not touched here.
|
||||
func (s *Settings) DropKeptCopy(id string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for i := range s.KeptCopies {
|
||||
if s.KeptCopies[i].ID == id {
|
||||
s.KeptCopies = append(s.KeptCopies[:i], s.KeptCopies[i+1:]...)
|
||||
return s.save()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user