@@ -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