github.com/artpar/rclone@v1.67.3/fs/list/list.go (about)

     1  // Package list contains list functions
     2  package list
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/artpar/rclone/fs"
    11  	"github.com/artpar/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  	fi := filter.GetConfig(ctx)
    32  	if !includeAll && fi.ListContainsExcludeFile(entries) {
    33  		fs.Debugf(dir, "Excluded")
    34  		return nil, nil
    35  	}
    36  	return filterAndSortDir(ctx, entries, includeAll, dir, fi.IncludeObject, fi.IncludeDirectory(ctx, f))
    37  }
    38  
    39  // filter (if required) and check the entries, then sort them
    40  func filterAndSortDir(ctx context.Context, entries fs.DirEntries, includeAll bool, dir string,
    41  	IncludeObject func(ctx context.Context, o fs.Object) bool,
    42  	IncludeDirectory func(remote string) (bool, error)) (newEntries fs.DirEntries, err error) {
    43  	newEntries = entries[:0] // in place filter
    44  	prefix := ""
    45  	if dir != "" {
    46  		prefix = dir + "/"
    47  	}
    48  	for _, entry := range entries {
    49  		ok := true
    50  		// check includes and types
    51  		switch x := entry.(type) {
    52  		case fs.Object:
    53  			// Make sure we don't delete excluded files if not required
    54  			if !includeAll && !IncludeObject(ctx, x) {
    55  				ok = false
    56  				fs.Debugf(x, "Excluded")
    57  			}
    58  		case fs.Directory:
    59  			if !includeAll {
    60  				include, err := IncludeDirectory(x.Remote())
    61  				if err != nil {
    62  					return nil, err
    63  				}
    64  				if !include {
    65  					ok = false
    66  					fs.Debugf(x, "Excluded")
    67  				}
    68  			}
    69  		default:
    70  			return nil, fmt.Errorf("unknown object type %T", entry)
    71  		}
    72  		// check remote name belongs in this directory
    73  		remote := entry.Remote()
    74  		switch {
    75  		case !ok:
    76  			// ignore
    77  		case !strings.HasPrefix(remote, prefix):
    78  			ok = false
    79  			fs.Errorf(entry, "Entry doesn't belong in directory %q (too short) - ignoring", dir)
    80  		case remote == prefix:
    81  			ok = false
    82  			fs.Errorf(entry, "Entry doesn't belong in directory %q (same as directory) - ignoring", dir)
    83  		case strings.ContainsRune(remote[len(prefix):], '/'):
    84  			ok = false
    85  			fs.Errorf(entry, "Entry doesn't belong in directory %q (contains subdir) - ignoring", dir)
    86  		default:
    87  			// ok
    88  		}
    89  		if ok {
    90  			newEntries = append(newEntries, entry)
    91  		}
    92  	}
    93  	entries = newEntries
    94  
    95  	// Sort the directory entries by Remote
    96  	//
    97  	// We use a stable sort here just in case there are
    98  	// duplicates. Assuming the remote delivers the entries in a
    99  	// consistent order, this will give the best user experience
   100  	// in syncing as it will use the first entry for the sync
   101  	// comparison.
   102  	sort.Stable(entries)
   103  	return entries, nil
   104  }