package manifest import ( "encoding/json" "os" "sync" ) // Entry is one copied file record type Entry struct { Source string `json:"source"` LocalDest string `json:"localDest"` S3Path string `json:"s3Path,omitempty"` SHA256 string `json:"sha256"` Bytes int64 `json:"bytes"` Status string `json:"status"` // "local" | "s3" | "done" | "error" Error string `json:"error,omitempty"` } // Manifest holds all entries for a session type Manifest struct { SessionID string `json:"sessionID"` Entries []Entry `json:"entries"` mu sync.Mutex } // New creates a new manifest func New(sessionID string) *Manifest { return &Manifest{SessionID: sessionID} } // Append adds an entry atomically func (m *Manifest) Append(e Entry) { m.mu.Lock() defer m.mu.Unlock() m.Entries = append(m.Entries, e) } // Save writes manifest atomically via temp file + rename func (m *Manifest) Save(path string) error { m.mu.Lock() defer m.mu.Unlock() data, err := json.MarshalIndent(m, "", " ") if err != nil { return err } tmp := path + ".tmp" if err := os.WriteFile(tmp, data, 0644); err != nil { return err } return os.Rename(tmp, path) } // Load reads a manifest from disk func Load(path string) (*Manifest, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var m Manifest if err := json.Unmarshal(data, &m); err != nil { return nil, err } return &m, nil }