3232import java .nio .file .Path ;
3333import java .nio .file .attribute .BasicFileAttributes ;
3434import java .nio .file .attribute .FileTime ;
35+ import java .util .ArrayDeque ;
36+ import java .util .Deque ;
3537import java .util .HashSet ;
3638import java .util .Map ;
3739import java .util .Set ;
@@ -82,20 +84,29 @@ protected MemorylessRescanner.Generator newGenerator(Path path, WatchScope scope
8284 }
8385
8486 protected class Generator extends MemorylessRescanner .Generator {
85- // Field to keep track of the paths that are visited during the current
86- // rescan. After the visit, the `DELETED` events that happened since the
87- // previous rescan can be approximated.
88- private Set <Path > visited = new HashSet <>();
87+ // Field to keep track of (a stack of) the paths that are visited during
88+ // the current rescan (one frame for each nested subdirectory), to
89+ // approximate `DELETED` events that happened since the previous rescan.
90+ // Instances of this class are supposed to be used non-concurrently, so
91+ // no synchronization to access this field is needed.
92+ private Deque <Set <Path >> visited = new ArrayDeque <>();
8993
9094 public Generator (Path path , WatchScope scope ) {
9195 super (path , scope );
96+ this .visited .push (new HashSet <>());
97+ }
98+
99+ private <T > void addToPeeked (Deque <Set <T >> deque , T t ) {
100+ var peeked = deque .peek ();
101+ if (peeked != null ) {
102+ peeked .add (t );
103+ }
92104 }
93105
94106 // -- MemorylessRescanner.Generator --
95107
96108 @ Override
97109 protected void generateEvents (Path path , BasicFileAttributes attrs ) {
98- visited .add (path );
99110 var lastModifiedTimeOld = index .get (path );
100111 var lastModifiedTimeNew = attrs .lastModifiedTime ();
101112
@@ -111,14 +122,26 @@ else if (lastModifiedTimeOld.compareTo(lastModifiedTimeNew) < 0) {
111122 }
112123 }
113124
125+ @ Override
126+ public FileVisitResult preVisitDirectory (Path dir , BasicFileAttributes attrs ) throws IOException {
127+ addToPeeked (visited , dir );
128+ visited .push (new HashSet <>());
129+ return super .preVisitDirectory (dir , attrs );
130+ }
131+
132+ @ Override
133+ public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) throws IOException {
134+ addToPeeked (visited , file );
135+ return super .visitFile (file , attrs );
136+ }
137+
114138 @ Override
115139 public FileVisitResult postVisitDirectory (Path dir , IOException exc ) throws IOException {
116- // If the visitor is back at the root of the rescan, then the time
117- // is right to issue `DELETED` events based on the set of `visited`
118- // paths.
119- if (dir .equals (path )) {
140+ // Issue `DELETED` events based on the set of paths visited in `dir`
141+ var visitedInDir = visited .pop ();
142+ if (visitedInDir != null ) {
120143 for (var p : index .keySet ()) {
121- if (p . startsWith ( path ) && !visited .contains (p )) {
144+ if (dir . equals ( p . getParent ()) && !visitedInDir .contains (p )) {
122145 events .add (new WatchEvent (WatchEvent .Kind .DELETED , p ));
123146 }
124147 }
0 commit comments