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  }