dirent_unix.go

  1//go:build darwin || aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris
  2
  3package fastwalk
  4
  5import (
  6	"io/fs"
  7	"os"
  8	"sort"
  9	"sync"
 10
 11	"github.com/charlievieth/fastwalk/internal/fmtdirent"
 12)
 13
 14type unixDirent struct {
 15	parent string
 16	name   string
 17	typ    fs.FileMode
 18	depth  uint32 // uint32 so that we can pack it next to typ
 19	info   *fileInfo
 20	stat   *fileInfo
 21}
 22
 23func (d *unixDirent) Name() string      { return d.name }
 24func (d *unixDirent) IsDir() bool       { return d.typ.IsDir() }
 25func (d *unixDirent) Type() fs.FileMode { return d.typ }
 26func (d *unixDirent) Depth() int        { return int(d.depth) }
 27func (d *unixDirent) String() string    { return fmtdirent.FormatDirEntry(d) }
 28
 29func (d *unixDirent) Info() (fs.FileInfo, error) {
 30	info := loadFileInfo(&d.info)
 31	info.once.Do(func() {
 32		info.FileInfo, info.err = os.Lstat(d.parent + "/" + d.name)
 33	})
 34	return info.FileInfo, info.err
 35}
 36
 37func (d *unixDirent) Stat() (fs.FileInfo, error) {
 38	if d.typ&os.ModeSymlink == 0 {
 39		return d.Info()
 40	}
 41	stat := loadFileInfo(&d.stat)
 42	stat.once.Do(func() {
 43		stat.FileInfo, stat.err = os.Stat(d.parent + "/" + d.name)
 44	})
 45	return stat.FileInfo, stat.err
 46}
 47
 48func newUnixDirent(parent, name string, typ fs.FileMode, depth int) *unixDirent {
 49	return &unixDirent{
 50		parent: parent,
 51		name:   name,
 52		typ:    typ,
 53		depth:  uint32(depth),
 54	}
 55}
 56
 57func fileInfoToDirEntry(dirname string, fi fs.FileInfo) DirEntry {
 58	info := &fileInfo{
 59		FileInfo: fi,
 60	}
 61	info.once.Do(func() {})
 62	return &unixDirent{
 63		parent: dirname,
 64		name:   fi.Name(),
 65		typ:    fi.Mode().Type(),
 66		info:   info,
 67	}
 68}
 69
 70var direntSlicePool = sync.Pool{
 71	New: func() any {
 72		a := make([]*unixDirent, 0, 32)
 73		return &a
 74	},
 75}
 76
 77func putDirentSlice(p *[]*unixDirent) {
 78	if p != nil && cap(*p) <= 32*1024 /* 256Kb */ {
 79		a := *p
 80		for i := range a {
 81			a[i] = nil
 82		}
 83		*p = a[:0]
 84		direntSlicePool.Put(p)
 85	}
 86}
 87
 88func sortDirents(mode SortMode, dents []*unixDirent) {
 89	if len(dents) <= 1 {
 90		return
 91	}
 92	switch mode {
 93	case SortLexical:
 94		sort.Slice(dents, func(i, j int) bool {
 95			return dents[i].name < dents[j].name
 96		})
 97	case SortFilesFirst:
 98		sort.Slice(dents, func(i, j int) bool {
 99			d1 := dents[i]
100			d2 := dents[j]
101			r1 := d1.typ.IsRegular()
102			r2 := d2.typ.IsRegular()
103			switch {
104			case r1 && !r2:
105				return true
106			case !r1 && r2:
107				return false
108			case !r1 && !r2:
109				// Both are not regular files: sort directories last
110				dd1 := d1.typ.IsDir()
111				dd2 := d2.typ.IsDir()
112				switch {
113				case !dd1 && dd2:
114					return true
115				case dd1 && !dd2:
116					return false
117				}
118			}
119			return d1.name < d2.name
120		})
121	case SortDirsFirst:
122		sort.Slice(dents, func(i, j int) bool {
123			d1 := dents[i]
124			d2 := dents[j]
125			dd1 := d1.typ.IsDir()
126			dd2 := d2.typ.IsDir()
127			switch {
128			case dd1 && !dd2:
129				return true
130			case !dd1 && dd2:
131				return false
132			case !dd1 && !dd2:
133				// Both are not directories: sort regular files first
134				r1 := d1.typ.IsRegular()
135				r2 := d2.typ.IsRegular()
136				switch {
137				case r1 && !r2:
138					return true
139				case !r1 && r2:
140					return false
141				}
142			}
143			return d1.name < d2.name
144		})
145	}
146}