github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/list/list.go (about)

     1  // Package list contains list functions
     2  package list
     3  
     4  import (
     5  	"context"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  	"github.com/rclone/rclone/fs"
    11  	"github.com/rclone/rclone/fs/filter"
    12  )
    13  
    14  // DirSorted reads Object and *Dir into entries for the given Fs.
    15  //
    16  // dir is the start directory, "" for root
    17  //
    18  // If includeAll is specified all files will be added, otherwise only
    19  // files and directories passing the filter will be added.
    20  //
    21  // Files will be returned in sorted order
    22  func DirSorted(ctx context.Context, f fs.Fs, includeAll bool, dir string) (entries fs.DirEntries, err error) {
    23  	// Get unfiltered entries from the fs
    24  	entries, err = f.List(ctx, dir)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  	// This should happen only if exclude files lives in the
    29  	// starting directory, otherwise ListDirSorted should not be
    30  	// called.
    31  	if !includeAll && filter.Active.ListContainsExcludeFile(entries) {
    32  		fs.Debugf(dir, "Excluded")
    33  		return nil, nil
    34  	}
    35  	return filterAndSortDir(ctx, entries, includeAll, dir, filter.Active.IncludeObject, filter.Active.IncludeDirectory(ctx, f))
    36  }
    37  
    38  // filter (if required) and check the entries, then sort them
    39  func filterAndSortDir(ctx context.Context, entries fs.DirEntries, includeAll bool, dir string,
    40  	IncludeObject func(ctx context.Context, o fs.Object) bool,
    41  	IncludeDirectory func(remote string) (bool, error)) (newEntries fs.DirEntries, err error) {
    42  	newEntries = entries[:0] // in place filter
    43  	prefix := ""
    44  	if dir != "" {
    45  		prefix = dir + "/"
    46  	}
    47  	for _, entry := range entries {
    48  		ok := true
    49  		// check includes and types
    50  		switch x := entry.(type) {
    51  		case fs.Object:
    52  			// Make sure we don't delete excluded files if not required
    53  			if !includeAll && !IncludeObject(ctx, x) {
    54  				ok = false
    55  				fs.Debugf(x, "Excluded")
    56  			}
    57  		case fs.Directory:
    58  			if !includeAll {
    59  				include, err := IncludeDirectory(x.Remote())
    60  				if err != nil {
    61  					return nil, err
    62  				}
    63  				if !include {
    64  					ok = false
    65  					fs.Debugf(x, "Excluded")
    66  				}
    67  			}
    68  		default:
    69  			return nil, errors.Errorf("unknown object type %T", entry)
    70  		}
    71  		// check remote name belongs in this directory
    72  		remote := entry.Remote()
    73  		switch {
    74  		case !ok:
    75  			// ignore
    76  		case !strings.HasPrefix(remote, prefix):
    77  			ok = false
    78  			fs.Errorf(entry, "Entry doesn't belong in directory %q (too short) - ignoring", dir)
    79  		case remote == prefix:
    80  			ok = false
    81  			fs.Errorf(entry, "Entry doesn't belong in directory %q (same as directory) - ignoring", dir)
    82  		case strings.ContainsRune(remote[len(prefix):], '/'):
    83  			ok = false
    84  			fs.Errorf(entry, "Entry doesn't belong in directory %q (contains subdir) - ignoring", dir)
    85  		default:
    86  			// ok
    87  		}
    88  		if ok {
    89  			newEntries = append(newEntries, entry)
    90  		}
    91  	}
    92  	entries = newEntries
    93  
    94  	// Sort the directory entries by Remote
    95  	//
    96  	// We use a stable sort here just in case there are
    97  	// duplicates. Assuming the remote delivers the entries in a
    98  	// consistent order, this will give the best user experience
    99  	// in syncing as it will use the first entry for the sync
   100  	// comparison.
   101  	sort.Stable(entries)
   102  	return entries, nil
   103  }