2018-01-12 16:30:54 +00:00
|
|
|
// Package list contains list functions
|
|
|
|
package list
|
|
|
|
|
|
|
|
import (
|
2019-06-17 08:34:30 +00:00
|
|
|
"context"
|
2021-11-04 10:12:57 +00:00
|
|
|
"fmt"
|
2018-01-12 16:30:54 +00:00
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
2019-07-28 17:47:38 +00:00
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/filter"
|
2018-01-12 16:30:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// DirSorted reads Object and *Dir into entries for the given Fs.
|
|
|
|
//
|
|
|
|
// dir is the start directory, "" for root
|
|
|
|
//
|
|
|
|
// If includeAll is specified all files will be added, otherwise only
|
|
|
|
// files and directories passing the filter will be added.
|
|
|
|
//
|
|
|
|
// Files will be returned in sorted order
|
2019-06-17 08:34:30 +00:00
|
|
|
func DirSorted(ctx context.Context, f fs.Fs, includeAll bool, dir string) (entries fs.DirEntries, err error) {
|
2018-01-12 16:30:54 +00:00
|
|
|
// Get unfiltered entries from the fs
|
2019-06-17 08:34:30 +00:00
|
|
|
entries, err = f.List(ctx, dir)
|
2018-01-12 16:30:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// This should happen only if exclude files lives in the
|
|
|
|
// starting directory, otherwise ListDirSorted should not be
|
|
|
|
// called.
|
2020-11-26 17:10:41 +00:00
|
|
|
fi := filter.GetConfig(ctx)
|
|
|
|
if !includeAll && fi.ListContainsExcludeFile(entries) {
|
2018-09-20 10:56:54 +00:00
|
|
|
fs.Debugf(dir, "Excluded")
|
2018-01-12 16:30:54 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
2020-11-26 17:10:41 +00:00
|
|
|
return filterAndSortDir(ctx, entries, includeAll, dir, fi.IncludeObject, fi.IncludeDirectory(ctx, f))
|
2018-01-12 16:30:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// filter (if required) and check the entries, then sort them
|
2019-06-17 08:34:30 +00:00
|
|
|
func filterAndSortDir(ctx context.Context, entries fs.DirEntries, includeAll bool, dir string,
|
|
|
|
IncludeObject func(ctx context.Context, o fs.Object) bool,
|
2018-01-12 16:30:54 +00:00
|
|
|
IncludeDirectory func(remote string) (bool, error)) (newEntries fs.DirEntries, err error) {
|
|
|
|
newEntries = entries[:0] // in place filter
|
|
|
|
prefix := ""
|
|
|
|
if dir != "" {
|
|
|
|
prefix = dir + "/"
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
|
|
ok := true
|
|
|
|
// check includes and types
|
|
|
|
switch x := entry.(type) {
|
|
|
|
case fs.Object:
|
|
|
|
// Make sure we don't delete excluded files if not required
|
2019-06-17 08:34:30 +00:00
|
|
|
if !includeAll && !IncludeObject(ctx, x) {
|
2018-01-12 16:30:54 +00:00
|
|
|
ok = false
|
2018-09-20 10:56:54 +00:00
|
|
|
fs.Debugf(x, "Excluded")
|
2018-01-12 16:30:54 +00:00
|
|
|
}
|
|
|
|
case fs.Directory:
|
|
|
|
if !includeAll {
|
|
|
|
include, err := IncludeDirectory(x.Remote())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !include {
|
|
|
|
ok = false
|
2018-09-20 10:56:54 +00:00
|
|
|
fs.Debugf(x, "Excluded")
|
2018-01-12 16:30:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
2021-11-04 10:12:57 +00:00
|
|
|
return nil, fmt.Errorf("unknown object type %T", entry)
|
2018-01-12 16:30:54 +00:00
|
|
|
}
|
2019-04-30 12:06:24 +00:00
|
|
|
// check remote name belongs in this directory
|
2018-01-12 16:30:54 +00:00
|
|
|
remote := entry.Remote()
|
|
|
|
switch {
|
|
|
|
case !ok:
|
|
|
|
// ignore
|
|
|
|
case !strings.HasPrefix(remote, prefix):
|
|
|
|
ok = false
|
|
|
|
fs.Errorf(entry, "Entry doesn't belong in directory %q (too short) - ignoring", dir)
|
|
|
|
case remote == prefix:
|
|
|
|
ok = false
|
|
|
|
fs.Errorf(entry, "Entry doesn't belong in directory %q (same as directory) - ignoring", dir)
|
|
|
|
case strings.ContainsRune(remote[len(prefix):], '/'):
|
|
|
|
ok = false
|
|
|
|
fs.Errorf(entry, "Entry doesn't belong in directory %q (contains subdir) - ignoring", dir)
|
|
|
|
default:
|
|
|
|
// ok
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
newEntries = append(newEntries, entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entries = newEntries
|
|
|
|
|
|
|
|
// Sort the directory entries by Remote
|
|
|
|
//
|
|
|
|
// We use a stable sort here just in case there are
|
|
|
|
// duplicates. Assuming the remote delivers the entries in a
|
|
|
|
// consistent order, this will give the best user experience
|
|
|
|
// in syncing as it will use the first entry for the sync
|
|
|
|
// comparison.
|
|
|
|
sort.Stable(entries)
|
|
|
|
return entries, nil
|
|
|
|
}
|