package core import ( "golang.org/x/exp/slices" "path" ) // IndexFiles are stored as a map of path -> (indexFile or alias -> indexFile) // The latter is used for multiple copies with the same path but different alias type IndexFiles map[string]IndexPathHolder type IndexPathHolder interface { updateHash(hash string, format string) markFound() markMetaFile() markedFound() bool IsMetaFile() bool } // indexFile is a file in the index type indexFile struct { // Files are stored in forward-slash format relative to the index file File string `toml:"file"` Hash string `toml:"hash,omitempty"` HashFormat string `toml:"hash-format,omitempty"` Alias string `toml:"alias,omitempty"` MetaFile bool `toml:"metafile,omitempty"` // True when it is a .toml metadata file Preserve bool `toml:"preserve,omitempty"` // Don't overwrite the file when updating fileFound bool } func (i *indexFile) updateHash(hash string, format string) { i.Hash = hash i.HashFormat = format } func (i *indexFile) markFound() { i.fileFound = true } func (i *indexFile) markMetaFile() { i.MetaFile = true } func (i *indexFile) markedFound() bool { return i.fileFound } func (i *indexFile) IsMetaFile() bool { return i.MetaFile } type indexFileMultipleAlias map[string]indexFile func (i *indexFileMultipleAlias) updateHash(hash string, format string) { for k, v := range *i { v.updateHash(hash, format) (*i)[k] = v // Can't mutate map value in place } } // (indexFileMultipleAlias == map[string]indexFile) func (i *indexFileMultipleAlias) markFound() { for k, v := range *i { v.markFound() (*i)[k] = v // Can't mutate map value in place } } func (i *indexFileMultipleAlias) markMetaFile() { for k, v := range *i { v.markMetaFile() (*i)[k] = v // Can't mutate map value in place } } func (i *indexFileMultipleAlias) markedFound() bool { for _, v := range *i { return v.markedFound() } panic("No entries in indexFileMultipleAlias") } func (i *indexFileMultipleAlias) IsMetaFile() bool { for _, v := range *i { return v.MetaFile } panic("No entries in indexFileMultipleAlias") } // updateFileEntry updates the hash of a file and marks as found; adding it if it doesn't exist // This also sets metafile if markAsMetaFile is set // This updates all existing aliassed variants of a file, but doesn't create new ones func (f *IndexFiles) updateFileEntry(path string, format string, hash string, markAsMetaFile bool) { // Ensure map is non-nil if *f == nil { *f = make(IndexFiles) } // Fetch existing entry file, found := (*f)[path] if found { // Exists: update hash/format/metafile file.markFound() file.updateHash(hash, format) if markAsMetaFile { file.markMetaFile() } // (don't do anything if markAsMetaFile is false - don't reset metafile status of existing metafiles) } else { // Doesn't exist: create new file data newFile := indexFile{ File: path, Hash: hash, HashFormat: format, MetaFile: markAsMetaFile, fileFound: true, } (*f)[path] = &newFile } } type indexFilesTomlRepresentation []indexFile // toMemoryRep converts the TOML representation of IndexFiles to that used in memory // These silly converter functions are necessary because the TOML libraries don't support custom non-primitive serializers func (rep indexFilesTomlRepresentation) toMemoryRep() IndexFiles { out := make(IndexFiles) // Add entries to map for _, v := range rep { v := v // Narrow scope of loop variable v.File = path.Clean(v.File) v.Alias = path.Clean(v.Alias) // path.Clean converts "" into "." - undo this for Alias as we use omitempty if v.Alias == "." { v.Alias = "" } if existing, ok := out[v.File]; ok { if existingFile, ok := existing.(*indexFile); ok { // Is this the same as the existing file? if v.Alias == existingFile.Alias { // Yes: overwrite out[v.File] = &v } else { // No: convert to new map m := make(indexFileMultipleAlias) m[existingFile.Alias] = *existingFile m[v.Alias] = v out[v.File] = &m } } else if existingMap, ok := existing.(*indexFileMultipleAlias); ok { // Add to alias map (*existingMap)[v.Alias] = v } else { panic("Unknown type in IndexFiles") } } else { out[v.File] = &v } } return out } // toTomlRep converts the in-memory representation of IndexFiles to that used in TOML // These silly converter functions are necessary because the TOML libraries don't support custom non-primitive serializers func (f *IndexFiles) toTomlRep() indexFilesTomlRepresentation { // Turn internal representation into TOML representation rep := make(indexFilesTomlRepresentation, 0, len(*f)) for _, v := range *f { if file, ok := v.(*indexFile); ok { rep = append(rep, *file) } else if file, ok := v.(*indexFileMultipleAlias); ok { for _, alias := range *file { rep = append(rep, alias) } } else { panic("Unknown type in IndexFiles") } } slices.SortFunc(rep, func(a indexFile, b indexFile) bool { if a.File == b.File { return a.Alias < b.Alias } else { return a.File < b.File } }) return rep }