Add filesystem inventory model
Append-only chunked node arena with stable NodeIDs, intrusive child lists, per-directory category totals and batched ancestor propagation.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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:])
|
||||
}
|
||||
Reference in New Issue
Block a user