Files
mapsize/internal/inventory/category.go
T
Andy Dixon 61fd9bf711 Add filesystem inventory model
Append-only chunked node arena with stable NodeIDs, intrusive child
lists, per-directory category totals and batched ancestor propagation.
2026-10-02 08:02:32 +01:00

118 lines
3.7 KiB
Go

package inventory
import "strings"
// Category is a coarse file classification used for colouring and stats.
type Category uint8
const (
CatOther Category = iota
CatImage
CatVideo
CatAudio
CatArchive
CatDiskImage
CatDatabase
CatCode
CatDocument
CatExecutable
CatLog
CatCache
NumCategories
)
var categoryNames = [NumCategories]string{
"Other", "Image", "Video", "Audio", "Archive", "Disk image", "Database",
"Source code", "Document", "Executable", "Log", "Cache",
}
func (c Category) String() string {
if c < NumCategories {
return categoryNames[c]
}
return "Other"
}
// ParseCategory resolves a category by (case-insensitive) name or prefix.
func ParseCategory(s string) (Category, bool) {
s = strings.ToLower(strings.TrimSpace(s))
for i, n := range categoryNames {
ln := strings.ToLower(n)
if ln == s || strings.ReplaceAll(ln, " ", "") == s || strings.HasPrefix(ln, s) && len(s) >= 3 {
return Category(i), true
}
}
switch s {
case "vm", "virtual machine", "disk":
return CatDiskImage, true
case "db":
return CatDatabase, true
case "src", "source":
return CatCode, true
case "exe", "binary":
return CatExecutable, true
}
return 0, false
}
var extCategory = map[string]Category{}
func init() {
add := func(c Category, exts string) {
for _, e := range strings.Fields(exts) {
extCategory[e] = c
}
}
add(CatImage, "jpg jpeg png gif bmp tif tiff webp heic heif raw cr2 cr3 nef arw dng svg ico psd xcf avif jxl orf rw2")
add(CatVideo, "mp4 mkv mov avi wmv flv webm m4v mpg mpeg ts m2ts vob 3gp ogv mts")
add(CatAudio, "mp3 flac wav ogg oga opus m4a aac wma aiff aif alac mid midi")
add(CatArchive, "zip tar gz tgz bz2 tbz xz txz zst zstd 7z rar lz lz4 lzma cab deb rpm apk jar war cpio pkg snap whl gem")
add(CatDiskImage, "iso img qcow qcow2 vmdk vdi vhd vhdx hdd ova ovf dmg raw vmem vmsn nvram sav")
add(CatDatabase, "db sqlite sqlite3 db3 mdb accdb frm ibd myd myi ldf mdf ndf dbf wal kdbx realm lmdb rdb aof")
add(CatCode, "c h cc cpp cxx hpp hh go rs py pyc js mjs cjs ts tsx jsx java kt kts scala rb php pl pm sh bash zsh fish lua swift m mm cs fs vb r jl hs ml ex exs erl clj dart sql css scss sass less html htm vue svelte json yaml yml toml xml proto gradle cmake mk ipynb o a lib obj class")
add(CatDocument, "pdf doc docx odt rtf txt md rst tex xls xlsx ods csv tsv ppt pptx odp epub mobi djvu pages numbers key org adoc")
add(CatExecutable, "exe dll so dylib bin msi app elf ko sys efi appimage com")
add(CatLog, "log journal")
add(CatCache, "cache tmp temp swp bak old part crdownload")
}
// CategoryFor classifies a file by its (lower-case) extension and by
// whether it lives beneath a cache or log directory.
func CategoryFor(ext string, inCache, inLog bool) Category {
c, ok := extCategory[ext]
switch {
case inCache && (!ok || c == CatOther || c == CatCode):
return CatCache
case inLog && (!ok || c == CatArchive || c == CatOther):
return CatLog
case ok:
return c
}
return CatOther
}
// PathHints reports whether a directory path contains a cache or log
// component. It is evaluated once per directory, not per file.
func PathHints(path string) (inCache, inLog bool) {
lp := strings.ToLower(path)
for _, part := range strings.FieldsFunc(lp, func(r rune) bool { return r == '/' || r == '\\' }) {
switch part {
case "cache", ".cache", "caches", "__pycache__", ".npm", ".gradle", "inetcache", "temp", "tmp":
inCache = true
case "log", "logs", ".logs", "journal":
inLog = true
}
}
return
}
// Ext returns the lower-case extension of name without the dot ("" if none,
// or if the name is a dotfile like ".bashrc").
func Ext(name string) string {
i := strings.LastIndexByte(name, '.')
if i <= 0 || i == len(name)-1 || len(name)-i > 16 {
return ""
}
return strings.ToLower(name[i+1:])
}