1package main23import (4 "crypto/sha256"5 "embed"6 "encoding/hex"7 "fmt"8 "io/fs"9 "os"10 "path/filepath"11 "sort"12 "sync"13)1415// Every pyramid was derived afresh on every run: adding one game re-decoded and16// re-reduced the other seventeen, half a minute of work to arrive at the bytes17// that were already there. A layer is derived from tiles that are stored under18// their own content hash, so what went into a pyramid can be written down and19// compared, and a layer whose capture has not moved can be carried over from20// the last run instead.21//22// The tool's own source counts as an input. Changing how a level is reduced or23// where the content bounds land has to invalidate every pyramid, and a stamp24// that only watched the archive would quietly keep serving the old derivation.25//26//go:embed main.go stamp.go warp.go27var toolSource embed.FS2829var toolStamp = sync.OnceValue(func() string {30 sum := sha256.New()31 entries, err := fs.ReadDir(toolSource, ".")32 if err != nil {33 // The files are embedded at build time, so this cannot fail in a built34 // binary. Falling back to a random-looking constant would defeat the35 // point of the stamp, and rebuilding everything is the safe answer.36 return "unstamped"37 }38 names := make([]string, 0, len(entries))39 for _, entry := range entries {40 names = append(names, entry.Name())41 }42 sort.Strings(names)43 for _, name := range names {44 data, err := toolSource.ReadFile(name)45 if err != nil {46 return "unstamped"47 }48 fmt.Fprintf(sum, "%s\x00%d\x00", name, len(data))49 sum.Write(data)50 }51 return hex.EncodeToString(sum.Sum(nil))52})5354// planStamp names everything a pyramid is derived from: the tool that derives55// it, the shape the plan settled on, and the content hash of every source tile56// that goes into it. Two runs that would write the same bytes stamp the same.57func planStamp(plan tilePlan) string {58 sum := sha256.New()59 fmt.Fprintf(sum, "tool\x00%s\x00", toolStamp())60 fmt.Fprintf(sum, "layer\x00%s\x00%s\x00", plan.SourcePath, plan.AssetPath)61 fmt.Fprintf(sum, "zooms\x00%d\x00%d\x00", plan.MaxFullZoom, plan.MaxSourceZoom)62 fmt.Fprintf(sum, "format\x00%s\x00%t\x00", plan.PreferredFormat, plan.Interpolate)63 if plan.Bounds != nil {64 fmt.Fprintf(sum, "bounds\x00%d\x00%d\x00%d\x00%d\x00",65 plan.Bounds.X, plan.Bounds.Y, plan.Bounds.Width, plan.Bounds.Height)66 }67 // A warped pyramid is derived from its alignment as much as from its68 // tiles: the same donor resampled through a different transformation is69 // a different picture.70 if plan.Warp != nil {71 fmt.Fprintf(sum, "warp\x00%s\x00%d\x00%d\x00%d\x00",72 plan.Warp.Base.SourcePath, plan.Warp.TargetZoom,73 plan.Warp.Base.Frame.BaseZoom, plan.Warp.Base.Frame.BaseTile)74 affine := plan.Warp.Affine75 fmt.Fprintf(sum, "%.9f\x00%.9f\x00%.9f\x00%.9f\x00%.9f\x00%.9f\x00",76 affine.AX, affine.BX, affine.CX, affine.AY, affine.BY, affine.CY)77 }7879 zooms := make([]int, 0, len(plan.Levels))80 for zoom := range plan.Levels {81 zooms = append(zooms, zoom)82 }83 sort.Ints(zooms)84 for _, zoom := range zooms {85 files := append([]tileFile(nil), plan.Levels[zoom]...)86 sort.Slice(files, func(i, j int) bool {87 if files[i].Record.X != files[j].Record.X {88 return files[i].Record.X < files[j].Record.X89 }90 return files[i].Record.Y < files[j].Record.Y91 })92 fmt.Fprintf(sum, "level\x00%d\x00%d\x00", zoom, len(files))93 for _, file := range files {94 fmt.Fprintf(sum, "%d\x00%d\x00%s\x00%s\x00",95 file.Record.X, file.Record.Y, file.Record.ContentHash, file.Format)96 }97 }98 return hex.EncodeToString(sum.Sum(nil))99}100101// linkTree reproduces a directory as links to the files it already holds, and102// reports how many it brought across. Directories are visited before their103// contents, so each one is made once rather than checked for again per file.104func linkTree(from, to string) (int, error) {105 tiles := 0106 err := filepath.WalkDir(from, func(path string, entry fs.DirEntry, err error) error {107 if err != nil {108 return err109 }110 relative, err := filepath.Rel(from, path)111 if err != nil {112 return err113 }114 destination := filepath.Join(to, relative)115 if entry.IsDir() {116 return os.Mkdir(destination, 0o755)117 }118 tiles++119 if err := os.Link(path, destination); err == nil {120 return nil121 }122 // A link can fail across filesystems, and on one that has none. The123 // bytes are what matter, so fall back to writing them out.124 return copyFile(path, destination)125 })126 return tiles, err127}128129// readManifest is the index left by the last run, or an empty one where there130// was no last run. A manifest that cannot be read is not an error: it means131// everything is derived again, which is what a first run does anyway.132func readManifest(output string) manifest {133 var previous manifest134 if err := readJSON(filepath.Join(output, "index.json"), &previous); err != nil {135 return manifest{}136 }137 return previous138}139140func manifestBySource(previous manifest) map[string]lensManifest {141 bySource := make(map[string]lensManifest, len(previous.Lenses))142 for _, variant := range previous.Lenses {143 bySource[variant.SourcePath] = variant144 }145 return bySource146}