clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

archive/golden-reference
atlas / internal / bundle / stamp.go
stamp.go 2.8K
1package bundle23import (4 "crypto/sha256"5 "encoding/hex"6 "sort"7 "strings"8)910// Stamp accumulates a content fingerprint for a bundle without requiring the11// bundle's bytes: a producer feeds it a hash per part -- payloads it just12// built, pyramid stamps its tile pipeline already computed -- and the sum is13// stable however the parts arrived. Equal stamps mean a rebuild would write14// the same bundle, which is what lets an unchanged volume be skipped.15type Stamp struct {16 parts []string17}1819// Add records one named part. Order does not matter; the sum sorts.20func (s *Stamp) Add(name, hash string) {21 s.parts = append(s.parts, name+" "+hash)22}2324// Sum returns the fingerprint of everything added.25func (s *Stamp) Sum() string {26 sorted := make([]string, len(s.parts))27 copy(sorted, s.parts)28 sort.Strings(sorted)29 digest := sha256.Sum256([]byte(strings.Join(sorted, "\n")))30 return hex.EncodeToString(digest[:])31}3233// HashBytes fingerprints one part for Add.34func HashBytes(data []byte) string {35 digest := sha256.Sum256(data)36 return hex.EncodeToString(digest[:])37}3839// MoreRecent reports whether a should shadow b when both claim the same volume.40// Creation time decides; among builds of the same capture the newer policy41// revision decides; the stamp and then the path break the remaining ties, so42// the choice is total and two scans of the same directory always agree.43func MoreRecent(a, b *Bundle) bool {44 if a.Manifest.Version.CreatedAt != b.Manifest.Version.CreatedAt {45 return a.Manifest.Version.CreatedAt > b.Manifest.Version.CreatedAt46 }47 if a.Manifest.Version.Revision != b.Manifest.Version.Revision {48 return a.Manifest.Version.Revision > b.Manifest.Version.Revision49 }50 if a.Manifest.Version.Stamp != b.Manifest.Version.Stamp {51 return a.Manifest.Version.Stamp > b.Manifest.Version.Stamp52 }53 return a.Path > b.Path54}5556// ShortStamp is the stamp as URLs and logs carry it: long enough that two57// builds of a volume will not collide, short enough to read past.58func ShortStamp(stamp string) string {59 if len(stamp) <= 12 {60 return stamp61 }62 return stamp[:12]63}6465// VersionedFileName is the name a bundle carries in a registry directory:66// the volume, the day its data was captured, and enough of the stamp to tell67// two builds of the same capture apart. Versions of a volume sit side by side68// under these names, and the newest-wins fold is what serves the right one --69// the name is for people and for cheap existence checks, never for ordering.70func VersionedFileName(m Manifest) string {71 var day strings.Builder72 for _, r := range m.Version.CreatedAt {73 if r >= '0' && r <= '9' {74 day.WriteRune(r)75 if day.Len() == 8 {76 break77 }78 }79 }80 name := m.Volume.Slug81 if day.Len() > 0 {82 name += "-" + day.String()83 }84 return name + "-" + ShortStamp(m.Version.Stamp) + ".atlas"85}