@@ -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
6667func 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.
857910func (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