Skip to content

Commit 6613c81

Browse files
committed
feat(conf): discover the GGUF files tree
1 parent e15024e commit 6613c81

1 file changed

Lines changed: 105 additions & 11 deletions

File tree

conf/models.go

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -492,14 +492,14 @@ func verify(path string) (int64, error) {
492492
return size, nil // OK
493493
}
494494

495-
// DiscoverModelFolders returns a sorted slice
495+
// DiscoverModelParentFolders returns a sorted slice
496496
// of *top‑most* directories that contain *.gguf files.
497497
// If no root paths are supplied, the function scans
498-
// the typical locations: /mnt /var/opt /opt /home/user
498+
// the typical locations: /mnt /var/opt /opt /home/username
499499
//
500500
// Example usage:
501501
//
502-
// dirs := conf.DiscoverModelFolders() // []string{"/home/bob/models", "/mnt/models"}
502+
// dirs := conf.DiscoverModelParentFolders() // []string{"/home/bob/models", "/mnt/models"}
503503
//
504504
// This Go function is a rewrite of the following bash command line:
505505
//
@@ -513,17 +513,17 @@ func verify(path string) (int64, error) {
513513
// - sort them, -u to keep a unique copy of each folder (`z` = input is `\0` separated)
514514
// - while read xxx; do xxx; done => keep the parent folders only
515515
// - echo $d: prints each parent folder separated by ":" (`-n` no newline)
516-
func DiscoverModelFolders(roots ...string) []string {
517-
// default roots = /mnt /var/opt /opt /home/$USER
516+
func DiscoverModelParentFolders(roots ...string) []string {
517+
// default roots = /mnt /var/opt /opt /home/username
518518
if len(roots) == 0 {
519519
roots = []string{"/mnt", "/var/opt", "/opt"}
520-
home, _ := os.UserHomeDir() // ignore error – if we can't get it we just omit it
520+
home, _ := os.UserHomeDir()
521521
if home != "" {
522522
roots = append(roots, home)
523523
}
524524
}
525525

526-
// collect the parent directories of *.gguf files
526+
// collect all directories containing *.gguf files
527527
dirSet := make(map[string]struct{}) // set for uniqueness
528528
for _, r := range roots {
529529
_ = filepath.Walk(r, func(path string, fi os.FileInfo, e error) error {
@@ -542,13 +542,107 @@ func DiscoverModelFolders(roots ...string) []string {
542542
sort.Strings(dirs)
543543

544544
// drop sub‑directories that are already covered by a higher‑level entry
545-
out := make([]string, 0, len(dirs))
545+
parents := make([]string, 0, len(dirs))
546546
sep := string(os.PathSeparator)
547547
for _, d := range dirs {
548-
if len(out) > 0 && strings.HasPrefix(d, out[len(out)-1]+sep) {
548+
if len(parents) > 0 && strings.HasPrefix(d, parents[len(parents)-1]+sep) {
549549
continue // skip d = child of the previously kept directory
550550
}
551-
out = append(out, d)
551+
parents = append(parents, d)
552552
}
553-
return out
553+
return parents
554+
}
555+
556+
// DiscoverModelsTree returns the GGUF files tree
557+
// by walking the supplied roots (or a default set).
558+
// It returns a `map[parentDir]map[childDir][]file` where:
559+
//
560+
// - parentDir – shallowest directory that contains at least one *.gguf file.
561+
// - childDir – path relative to that parent (empty string for files directly inside the parent).
562+
// - file – the basename of the *.gguf file (including the ".gguf" suffix).
563+
//
564+
// All filesystem errors are ignored:
565+
// the function always returns whatever it could discover.
566+
//
567+
// Example (files on disk):
568+
//
569+
// /home/bob/models/model1.gguf
570+
// /home/bob/models/subdir/model2.gguf
571+
// /mnt/models/model3.gguf
572+
//
573+
// tree := DiscoverModelsTree()
574+
//
575+
// // equivalent to:
576+
//
577+
// tree = map[string]map[string][]string{
578+
// "/home/bob/models": {
579+
// "": {"model1.gguf"},
580+
// "subdir": {"model2.gguf"},
581+
// },
582+
// "/mnt/models": {
583+
// "": {"model3.gguf"},
584+
// },
585+
// }
586+
func DiscoverModelsTree(roots ...string) map[string]map[string][]string {
587+
if len(roots) == 0 {
588+
// default roots = /mnt /var/opt /opt /home/$USER
589+
roots = []string{"/mnt", "/var/opt", "/opt"}
590+
home, _ := os.UserHomeDir()
591+
if home != "" {
592+
roots = append(roots, home)
593+
}
594+
}
595+
596+
dirFiles := collectModelFiles(roots)
597+
598+
return buildModelsTree(dirFiles)
599+
}
600+
601+
// collectModelFiles walks each root and groups *.gguf files by the directory that holds them.
602+
func collectModelFiles(roots []string) map[string][]string {
603+
dirFiles := make(map[string][]string) // dir → []basename
604+
for _, root := range roots {
605+
_ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
606+
if err != nil || d.IsDir() {
607+
return nil // ignore unreadable paths and directories
608+
}
609+
if strings.EqualFold(filepath.Ext(path), ".gguf") {
610+
dir := filepath.Dir(path)
611+
dirFiles[dir] = append(dirFiles[dir], filepath.Base(path))
612+
}
613+
return nil
614+
})
615+
}
616+
return dirFiles
617+
}
618+
619+
// buildModelsTree turns the flat dir→files map into the hierarchical result.
620+
func buildModelsTree(dirFiles map[string][]string) map[string]map[string][]string {
621+
// sort the directory keys so parents come first.
622+
dirs := make([]string, 0, len(dirFiles))
623+
for d := range dirFiles {
624+
dirs = append(dirs, d)
625+
}
626+
sort.Strings(dirs)
627+
628+
// Collapse children under their parent directory.
629+
const sep = string(filepath.Separator)
630+
tree := make(map[string]map[string][]string)
631+
var parent string // current parent directory
632+
633+
for _, d := range dirs {
634+
files := dirFiles[d]
635+
sort.Strings(files) // deterministic order of file names
636+
637+
// Is this a new top‑level parent?
638+
if parent == "" || !strings.HasPrefix(d, parent+sep) {
639+
parent = d
640+
tree[parent] = map[string][]string{"": files}
641+
continue
642+
}
643+
// d is a descendant of the current parent
644+
relativeDir := d[len(parent)+1:] // trim parent dir
645+
tree[parent][relativeDir] = files
646+
}
647+
return tree
554648
}

0 commit comments

Comments
 (0)