Skip to content

Commit 666276b

Browse files
committed
wip: vfs: use specialized stat interface when available rather than
listing Signed-off-by: Anagh Kumar Baranwal <6824881+darthShadow@users.noreply.github.com>
1 parent 728367e commit 666276b

File tree

6 files changed

+170
-75
lines changed

6 files changed

+170
-75
lines changed

backend/drive/drive.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3798,16 +3798,16 @@ Use the --interactive/-i or --dry-run flag to see what would be moved beforehand
37983798
Usage:
37993799
38003800
rclone backend query drive: query
3801-
3802-
The query syntax is documented at [Google Drive Search query terms and
3801+
3802+
The query syntax is documented at [Google Drive Search query terms and
38033803
operators](https://developers.google.com/drive/api/guides/ref-search-terms).
38043804
38053805
For example:
38063806
38073807
rclone backend query drive: "'0ABc9DEFGHIJKLMNop0QRatUVW3X' in parents and name contains 'foo'"
38083808
38093809
If the query contains literal ' or \ characters, these need to be escaped with
3810-
\ characters. "'" becomes "\'" and "\" becomes "\\\", for example to match a
3810+
\ characters. "'" becomes "\'" and "\" becomes "\\\", for example to match a
38113811
file named "foo ' \.txt":
38123812
38133813
rclone backend query drive: "name = 'foo \' \\\.txt'"

backend/local/local.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,13 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
11231123
}
11241124
}
11251125

1126+
// Stat returns the DirEntry for the given name
1127+
//
1128+
// It should return fs.ErrorNotImplemented if it can't return a DirEntry
1129+
func (f *Fs) Stat(ctx context.Context, dir string, leaf string) (fs.DirEntry, error) {
1130+
return nil, fs.ErrorNotImplemented
1131+
}
1132+
11261133
// ------------------------------------------------------------
11271134

11281135
// Fs returns the parent Fs
@@ -1768,6 +1775,7 @@ var (
17681775
_ fs.OpenWriterAter = &Fs{}
17691776
_ fs.DirSetModTimer = &Fs{}
17701777
_ fs.MkdirMetadataer = &Fs{}
1778+
_ fs.Stater = &Fs{}
17711779
_ fs.Object = &Object{}
17721780
_ fs.Metadataer = &Object{}
17731781
_ fs.SetMetadataer = &Object{}

backend/onedrive/onedrive.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ block download of the file.
356356
357357
In this case you will see a message like this
358358
359-
server reports this file is infected with a virus - use --onedrive-av-override to download anyway: Infected (name of virus): 403 Forbidden:
359+
server reports this file is infected with a virus - use --onedrive-av-override to download anyway: Infected (name of virus): 403 Forbidden:
360360
361361
If you are 100% sure you want to download this file anyway then use
362362
the --onedrive-av-override flag, or av_override = true in the config
@@ -2988,13 +2988,13 @@ var (
29882988
_ fs.ListRer = (*Fs)(nil)
29892989
_ fs.Shutdowner = (*Fs)(nil)
29902990
_ fs.Object = (*Object)(nil)
2991-
_ fs.MimeTyper = &Object{}
2992-
_ fs.IDer = &Object{}
2991+
_ fs.MimeTyper = (*Object)(nil)
2992+
_ fs.IDer = (*Object)(nil)
29932993
_ fs.Metadataer = (*Object)(nil)
29942994
_ fs.Metadataer = (*Directory)(nil)
29952995
_ fs.SetModTimer = (*Directory)(nil)
29962996
_ fs.SetMetadataer = (*Directory)(nil)
2997-
_ fs.MimeTyper = &Directory{}
2997+
_ fs.MimeTyper = (*Directory)(nil)
29982998
_ fs.DirSetModTimer = (*Fs)(nil)
29992999
_ fs.MkdirMetadataer = (*Fs)(nil)
30003000
)

backend/union/union.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,13 @@ func (f *Fs) CleanUp(ctx context.Context) error {
911911
return errs.Err()
912912
}
913913

914+
// Stat returns the DirEntry for the given name
915+
//
916+
// It should return fs.ErrorNotImplemented if it can't return a DirEntry
917+
func (f *Fs) Stat(ctx context.Context, dir string, leaf string) (fs.DirEntry, error) {
918+
return nil, fs.ErrorNotImplemented
919+
}
920+
914921
// NewFs constructs an Fs from the path.
915922
//
916923
// The returned Fs is the actual Fs, referenced by remote in the config
@@ -1093,4 +1100,5 @@ var (
10931100
_ fs.ListRer = (*Fs)(nil)
10941101
_ fs.Shutdowner = (*Fs)(nil)
10951102
_ fs.CleanUpper = (*Fs)(nil)
1103+
_ fs.Stater = (*Fs)(nil)
10961104
)

fs/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,14 @@ type Fder interface {
218218
Fd(ctx context.Context, flags int) (uintptr, error)
219219
}
220220

221+
// Stater is an optional interface for Directory
222+
type Stater interface {
223+
// Stat returns the DirEntry for the given leaf
224+
//
225+
// It should return fs.ErrorNotImplemented if it can't return a DirEntry
226+
Stat(ctx context.Context, dir string, leaf string) (DirEntry, error)
227+
}
228+
221229
// SetModTimer is an optional interface for Directory.
222230
//
223231
// Object implements this as part of its requires set of interfaces.

vfs/dir.go

Lines changed: 139 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@ type Dir struct {
3333
f fs.Fs // read only
3434
cleanupTimer *time.Timer // read only: timer to call cacheCleanup
3535

36-
mu sync.RWMutex // protects the following
37-
parent *Dir // parent, nil for root
38-
path string
39-
entry fs.Directory
40-
read time.Time // time directory entry last read
41-
polled time.Time // time directory entry last polled
42-
items map[string]Node // directory entries - can be empty but not nil
43-
virtual map[string]vState // virtual directory entries - may be nil
44-
sys atomic.Value // user defined info to be attached here
36+
mu sync.RWMutex // protects the following
37+
parent *Dir // parent, nil for root
38+
path string
39+
entry fs.Directory
40+
read time.Time // time directory entry last read
41+
polled time.Time // time directory entry last polled
42+
items map[string]Node // directory entries - can be empty but not nil
43+
virtual map[string]vState // virtual directory entries - may be nil
44+
statRead map[string]time.Time // time directory entry last read
45+
sys atomic.Value // user defined info to be attached here
4546

4647
modTimeMu sync.Mutex // protects the following
4748
modTime time.Time
@@ -65,14 +66,15 @@ const (
6566

6667
func newDir(vfs *VFS, f fs.Fs, parent *Dir, fsDir fs.Directory) *Dir {
6768
d := &Dir{
68-
vfs: vfs,
69-
f: f,
70-
parent: parent,
71-
entry: fsDir,
72-
path: fsDir.Remote(),
73-
modTime: fsDir.ModTime(context.TODO()),
74-
inode: newInode(),
75-
items: make(map[string]Node),
69+
vfs: vfs,
70+
f: f,
71+
parent: parent,
72+
entry: fsDir,
73+
path: fsDir.Remote(),
74+
modTime: fsDir.ModTime(context.TODO()),
75+
inode: newInode(),
76+
items: make(map[string]Node),
77+
statRead: make(map[string]time.Time),
7678
}
7779
// Set timer up like this to avoid race of d.cacheCleanup being called
7880
// before d.cleanupTimer is assigned to
@@ -759,59 +761,69 @@ func (mv manageVirtuals) end(d *Dir) {
759761
}
760762
}
761763

762-
// update d.items and if dirTree is not nil update each dir in the DirTree below this one and
763-
// set the last read time - must be called with the lock held
764-
func (d *Dir) _readDirFromEntries(entries fs.DirEntries, dirTree dirtree.DirTree, when time.Time) error {
764+
func (d *Dir) _processEntry(entry fs.DirEntry, mv manageVirtuals, dirTree dirtree.DirTree, when time.Time) error {
765765
var err error
766-
mv := d._newManageVirtuals()
767-
for _, entry := range entries {
768-
name := path.Base(entry.Remote())
769-
if name == "." || name == ".." {
770-
continue
771-
}
772-
if d.vfs.Opt.Links {
773-
name, _ = strings.CutSuffix(name, fs.LinkSuffix)
766+
name := path.Base(entry.Remote())
767+
if name == "." || name == ".." {
768+
return nil
769+
}
770+
if d.vfs.Opt.Links {
771+
name, _ = strings.CutSuffix(name, fs.LinkSuffix)
772+
}
773+
if mv.add(d, name) {
774+
return nil
775+
}
776+
777+
node := d.items[name]
778+
779+
switch item := entry.(type) {
780+
case fs.Object:
781+
obj := item
782+
// Reuse old file value if it exists
783+
if file, ok := node.(*File); node != nil && ok {
784+
file.setObjectNoUpdate(obj)
785+
} else {
786+
node = newFile(d, d.path, obj, name)
774787
}
775-
node := d.items[name]
776-
if mv.add(d, name) {
777-
continue
788+
case fs.Directory:
789+
// Reuse old dir value if it exists
790+
if node == nil || !node.IsDir() {
791+
node = newDir(d.vfs, d.f, d, item)
778792
}
779-
switch item := entry.(type) {
780-
case fs.Object:
781-
obj := item
782-
// Reuse old file value if it exists
783-
if file, ok := node.(*File); node != nil && ok {
784-
file.setObjectNoUpdate(obj)
785-
} else {
786-
node = newFile(d, d.path, obj, name)
787-
}
788-
case fs.Directory:
789-
// Reuse old dir value if it exists
790-
if node == nil || !node.IsDir() {
791-
node = newDir(d.vfs, d.f, d, item)
792-
}
793-
dir := node.(*Dir)
794-
dir.mu.Lock()
795-
dir.modTime = item.ModTime(context.TODO())
796-
if dirTree != nil {
797-
err = dir._readDirFromDirTree(dirTree, when)
798-
if err != nil {
799-
dir.read = time.Time{}
800-
} else {
801-
dir.read = when
802-
dir.cleanupTimer.Reset(time.Duration(d.vfs.Opt.DirCacheTime * 2))
803-
}
804-
}
805-
dir.mu.Unlock()
793+
dir := node.(*Dir)
794+
dir.mu.Lock()
795+
dir.modTime = item.ModTime(context.TODO())
796+
if dirTree != nil {
797+
err = dir._readDirFromDirTree(dirTree, when)
806798
if err != nil {
807-
return err
799+
dir.read = time.Time{}
800+
} else {
801+
dir.read = when
802+
dir.cleanupTimer.Reset(time.Duration(d.vfs.Opt.DirCacheTime * 2))
808803
}
809-
default:
810-
err = fmt.Errorf("unknown type %T", item)
811-
fs.Errorf(d, "readDir error: %v", err)
804+
}
805+
dir.mu.Unlock()
806+
if err != nil {
812807
return err
813808
}
814-
d.items[name] = node
809+
default:
810+
err = fmt.Errorf("unknown type %T", item)
811+
fs.Errorf(d, "readDir error: %v", err)
812+
return err
813+
}
814+
815+
d.items[name] = node
816+
return nil
817+
}
818+
819+
// update d.items and if dirTree is not nil update each dir in the DirTree below this one and
820+
// set the last read time - must be called with the lock held
821+
func (d *Dir) _readDirFromEntries(entries fs.DirEntries, dirTree dirtree.DirTree, when time.Time) error {
822+
mv := d._newManageVirtuals()
823+
for _, entry := range entries {
824+
if err := d._processEntry(entry, mv, dirTree, when); err != nil {
825+
return fmt.Errorf("%s: processing entry: %s: %v", d.path, path.Base(entry.Remote()), err)
826+
}
815827
}
816828
mv.end(d)
817829
return nil
@@ -849,20 +861,78 @@ func (d *Dir) readDir() error {
849861
return d._readDir()
850862
}
851863

864+
// stat a single item in the directory via the stat-er interface
865+
//
866+
// returns ENOENT if not found.
867+
func (d *Dir) _stat(leaf string, when time.Time) (bool, error) {
868+
stater, staterOk := d.f.(fs.Stater)
869+
if !staterOk {
870+
return false, nil
871+
}
872+
873+
// Use statRead to cache the stat time and avoid doing it again
874+
statTime, statTimeOk := d.statRead[leaf]
875+
if statTimeOk {
876+
age := when.Sub(statTime)
877+
stale := age > time.Duration(d.vfs.Opt.DirCacheTime)
878+
if !stale {
879+
return true, nil
880+
}
881+
}
882+
883+
entry, err := stater.Stat(context.TODO(), d.path, leaf)
884+
if err != nil {
885+
switch {
886+
case errors.Is(err, fs.ErrorObjectNotFound):
887+
return false, ENOENT
888+
case errors.Is(err, fs.ErrorNotImplemented):
889+
return false, nil
890+
default:
891+
fs.Errorf(d, "stat error: %s: %v", leaf, err)
892+
}
893+
}
894+
895+
mv := d._newManageVirtuals()
896+
if err = d._processEntry(entry, mv, nil, when); err != nil {
897+
return false, fmt.Errorf("%s: processing entry: %s: %v", d.path, path.Base(entry.Remote()), err)
898+
}
899+
900+
d.statRead[leaf] = when
901+
902+
return true, nil
903+
}
904+
852905
// stat a single item in the directory
853906
//
854907
// returns ENOENT if not found.
855908
// returns a custom error if directory on a case-insensitive file system
856909
// contains files with names that differ only by case.
857910
func (d *Dir) stat(leaf string) (Node, error) {
911+
var (
912+
ok bool
913+
err error
914+
item Node
915+
)
916+
858917
d.mu.Lock()
859918
defer d.mu.Unlock()
860-
// TODO: Define & Use the Stat-er interface to avoid reading the entire directory
861-
err := d._readDir()
862-
if err != nil {
863-
return nil, err
919+
920+
when := time.Now()
921+
if _, stale := d._age(when); stale {
922+
ok, err = d._stat(leaf, when)
923+
if err != nil {
924+
return nil, err
925+
}
864926
}
865-
item, ok := d.items[leaf]
927+
928+
if !ok {
929+
err := d._readDir()
930+
if err != nil {
931+
return nil, err
932+
}
933+
}
934+
935+
item, ok = d.items[leaf]
866936

867937
ci := fs.GetConfig(context.TODO())
868938
normUnicode := !ci.NoUnicodeNormalization
@@ -885,6 +955,7 @@ func (d *Dir) stat(leaf string) (Node, error) {
885955
if !ok {
886956
return nil, ENOENT
887957
}
958+
888959
return item, nil
889960
}
890961

0 commit comments

Comments
 (0)