github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/cmd/ncdu/scan/scan.go (about) 1 // Package scan does concurrent scanning of an Fs building up a directory tree. 2 package scan 3 4 import ( 5 "context" 6 "path" 7 "sync" 8 9 "github.com/pkg/errors" 10 "github.com/rclone/rclone/fs" 11 "github.com/rclone/rclone/fs/walk" 12 ) 13 14 // Dir represents a directory found in the remote 15 type Dir struct { 16 parent *Dir 17 path string 18 mu sync.Mutex 19 count int64 20 size int64 21 entries fs.DirEntries 22 dirs map[string]*Dir 23 } 24 25 // Parent returns the directory above this one 26 func (d *Dir) Parent() *Dir { 27 // no locking needed since these are write once in newDir() 28 return d.parent 29 } 30 31 // Path returns the position of the dir in the filesystem 32 func (d *Dir) Path() string { 33 // no locking needed since these are write once in newDir() 34 return d.path 35 } 36 37 // make a new directory 38 func newDir(parent *Dir, dirPath string, entries fs.DirEntries) *Dir { 39 d := &Dir{ 40 parent: parent, 41 path: dirPath, 42 entries: entries, 43 dirs: make(map[string]*Dir), 44 } 45 // Count size in this dir 46 for _, entry := range entries { 47 if o, ok := entry.(fs.Object); ok { 48 d.count++ 49 d.size += o.Size() 50 } 51 } 52 // Set my directory entry in parent 53 if parent != nil { 54 parent.mu.Lock() 55 leaf := path.Base(dirPath) 56 d.parent.dirs[leaf] = d 57 parent.mu.Unlock() 58 } 59 // Accumulate counts in parents 60 for ; parent != nil; parent = parent.parent { 61 parent.mu.Lock() 62 parent.count += d.count 63 parent.size += d.size 64 parent.mu.Unlock() 65 } 66 return d 67 } 68 69 // Entries returns a copy of the entries in the directory 70 func (d *Dir) Entries() fs.DirEntries { 71 return append(fs.DirEntries(nil), d.entries...) 72 } 73 74 // Remove removes the i-th entry from the 75 // in-memory representation of the remote directory 76 func (d *Dir) Remove(i int) { 77 d.mu.Lock() 78 defer d.mu.Unlock() 79 d.remove(i) 80 } 81 82 // removes the i-th entry from the 83 // in-memory representation of the remote directory 84 // 85 // Call with d.mu held 86 func (d *Dir) remove(i int) { 87 size := d.entries[i].Size() 88 count := int64(1) 89 90 subDir, ok := d.getDir(i) 91 if ok { 92 size = subDir.size 93 count = subDir.count 94 delete(d.dirs, path.Base(subDir.path)) 95 } 96 97 d.size -= size 98 d.count -= count 99 d.entries = append(d.entries[:i], d.entries[i+1:]...) 100 101 dir := d 102 // populate changed size and count to parent(s) 103 for parent := d.parent; parent != nil; parent = parent.parent { 104 parent.mu.Lock() 105 parent.dirs[path.Base(dir.path)] = dir 106 parent.size -= size 107 parent.count -= count 108 dir = parent 109 parent.mu.Unlock() 110 } 111 } 112 113 // gets the directory of the i-th entry 114 // 115 // returns nil if it is a file 116 // returns a flag as to whether is directory or not 117 // 118 // Call with d.mu held 119 func (d *Dir) getDir(i int) (subDir *Dir, isDir bool) { 120 obj := d.entries[i] 121 dir, ok := obj.(fs.Directory) 122 if !ok { 123 return nil, false 124 } 125 leaf := path.Base(dir.Remote()) 126 subDir = d.dirs[leaf] 127 return subDir, true 128 } 129 130 // GetDir returns the Dir of the i-th entry 131 // 132 // returns nil if it is a file 133 // returns a flag as to whether is directory or not 134 func (d *Dir) GetDir(i int) (subDir *Dir, isDir bool) { 135 d.mu.Lock() 136 defer d.mu.Unlock() 137 return d.getDir(i) 138 } 139 140 // Attr returns the size and count for the directory 141 func (d *Dir) Attr() (size int64, count int64) { 142 d.mu.Lock() 143 defer d.mu.Unlock() 144 return d.size, d.count 145 } 146 147 // AttrI returns the size, count and flags for the i-th directory entry 148 func (d *Dir) AttrI(i int) (size int64, count int64, isDir bool, readable bool) { 149 d.mu.Lock() 150 defer d.mu.Unlock() 151 subDir, isDir := d.getDir(i) 152 if !isDir { 153 return d.entries[i].Size(), 0, false, true 154 } 155 if subDir == nil { 156 return 0, 0, true, false 157 } 158 size, count = subDir.Attr() 159 return size, count, true, true 160 } 161 162 // Scan the Fs passed in, returning a root directory channel and an 163 // error channel 164 func Scan(ctx context.Context, f fs.Fs) (chan *Dir, chan error, chan struct{}) { 165 root := make(chan *Dir, 1) 166 errChan := make(chan error, 1) 167 updated := make(chan struct{}, 1) 168 go func() { 169 parents := map[string]*Dir{} 170 err := walk.Walk(ctx, f, "", false, fs.Config.MaxDepth, func(dirPath string, entries fs.DirEntries, err error) error { 171 if err != nil { 172 return err // FIXME mark directory as errored instead of aborting 173 } 174 var parent *Dir 175 if dirPath != "" { 176 parentPath := path.Dir(dirPath) 177 if parentPath == "." { 178 parentPath = "" 179 } 180 var ok bool 181 parent, ok = parents[parentPath] 182 if !ok { 183 errChan <- errors.Errorf("couldn't find parent for %q", dirPath) 184 } 185 } 186 d := newDir(parent, dirPath, entries) 187 parents[dirPath] = d 188 if dirPath == "" { 189 root <- d 190 } 191 // Mark updated 192 select { 193 case updated <- struct{}{}: 194 default: 195 break 196 } 197 return nil 198 }) 199 if err != nil { 200 errChan <- errors.Wrap(err, "ncdu listing failed") 201 } 202 errChan <- nil 203 }() 204 return root, errChan, updated 205 }