1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.impl;
18
19 import java.time.Duration;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.Stack;
23 import java.util.concurrent.ThreadFactory;
24 import java.util.stream.Stream;
25
26 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.commons.vfs2.FileListener;
30 import org.apache.commons.vfs2.FileMonitor;
31 import org.apache.commons.vfs2.FileName;
32 import org.apache.commons.vfs2.FileObject;
33 import org.apache.commons.vfs2.FileSystemException;
34 import org.apache.commons.vfs2.provider.AbstractFileSystem;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 public class DefaultFileMonitor implements Runnable, FileMonitor, AutoCloseable {
81
82
83
84
85 private static final class FileMonitorAgent {
86
87 private final FileObject fileObject;
88 private final DefaultFileMonitor defaultFileMonitor;
89 private boolean exists;
90 private long timestamp;
91 private Map<FileName, Object> children;
92
93 private FileMonitorAgent(final DefaultFileMonitor defaultFileMonitor, final FileObject fileObject) {
94 this.defaultFileMonitor = defaultFileMonitor;
95 this.fileObject = fileObject;
96
97 refresh();
98 resetChildrenList();
99
100 try {
101 exists = fileObject.exists();
102 } catch (final FileSystemException fse) {
103 exists = false;
104 timestamp = -1;
105 }
106
107 if (exists) {
108 try {
109 timestamp = fileObject.getContent().getLastModifiedTime();
110 } catch (final FileSystemException fse) {
111 timestamp = -1;
112 }
113 }
114 }
115
116 private void check() {
117 refresh();
118
119 try {
120
121 if (exists && !fileObject.exists()) {
122 exists = fileObject.exists();
123 timestamp = -1;
124
125
126
127 ((AbstractFileSystem) fileObject.getFileSystem()).fireFileDeleted(fileObject);
128
129
130 if (defaultFileMonitor.getFileListener() != null) {
131 fileObject.getFileSystem().removeListener(fileObject, defaultFileMonitor.getFileListener());
132 }
133
134
135 defaultFileMonitor.queueRemoveFile(fileObject);
136 } else if (exists && fileObject.exists()) {
137
138
139 if (timestamp != fileObject.getContent().getLastModifiedTime()) {
140 timestamp = fileObject.getContent().getLastModifiedTime();
141
142
143
144
145 if (!fileObject.getType().hasChildren()) {
146 ((AbstractFileSystem) fileObject.getFileSystem()).fireFileChanged(fileObject);
147 }
148 }
149
150 } else if (!exists && fileObject.exists()) {
151 exists = fileObject.exists();
152 timestamp = fileObject.getContent().getLastModifiedTime();
153
154
155 if (!fileObject.getType().hasChildren()) {
156 ((AbstractFileSystem) fileObject.getFileSystem()).fireFileCreated(fileObject);
157 }
158 }
159
160 checkForNewChildren();
161
162 } catch (final FileSystemException fse) {
163 LOG.error(fse.getLocalizedMessage(), fse);
164 }
165 }
166
167
168
169
170 private void checkForNewChildren() {
171 try {
172 if (fileObject.getType().hasChildren()) {
173 final FileObject[] newChildren = fileObject.getChildren();
174 if (children != null) {
175
176 final Map<FileName, Object> newChildrenMap = new HashMap<>();
177 final Stack<FileObject> missingChildren = new Stack<>();
178
179 for (final FileObject element : newChildren) {
180 newChildrenMap.put(element.getName(), new Object());
181
182 if (!children.containsKey(element.getName())) {
183 missingChildren.push(element);
184 }
185 }
186
187 children = newChildrenMap;
188
189
190 if (!missingChildren.empty()) {
191
192 while (!missingChildren.empty()) {
193 fireAllCreate(missingChildren.pop());
194 }
195 }
196
197 } else if (newChildren.length > 0) {
198
199 children = new HashMap<>();
200 for (final FileObject element : newChildren) {
201 children.put(element.getName(), new Object());
202 fireAllCreate(element);
203 }
204 }
205 }
206 } catch (final FileSystemException fse) {
207 LOG.error(fse.getLocalizedMessage(), fse);
208 }
209 }
210
211
212
213
214
215
216
217 private void fireAllCreate(final FileObject child) {
218
219 if (defaultFileMonitor.getFileListener() != null) {
220 child.getFileSystem().addListener(child, defaultFileMonitor.getFileListener());
221 }
222
223 ((AbstractFileSystem) child.getFileSystem()).fireFileCreated(child);
224
225
226 if (defaultFileMonitor.getFileListener() != null) {
227 child.getFileSystem().removeListener(child, defaultFileMonitor.getFileListener());
228 }
229
230 defaultFileMonitor.queueAddFile(child);
231
232 try {
233 if (defaultFileMonitor.isRecursive() && child.getType().hasChildren()) {
234 Stream.of(child.getChildren()).forEach(this::fireAllCreate);
235 }
236 } catch (final FileSystemException fse) {
237 LOG.error(fse.getLocalizedMessage(), fse);
238 }
239 }
240
241
242
243
244 private void refresh() {
245 try {
246 fileObject.refresh();
247 } catch (final FileSystemException fse) {
248 LOG.error(fse.getLocalizedMessage(), fse);
249 }
250 }
251
252 private void resetChildrenList() {
253 try {
254 if (fileObject.getType().hasChildren()) {
255 children = new HashMap<>();
256 for (final FileObject element : fileObject.getChildren()) {
257 children.put(element.getName(), new Object());
258 }
259 }
260 } catch (final FileSystemException fse) {
261 children = null;
262 }
263 }
264
265 }
266
267 private static final ThreadFactory THREAD_FACTORY = new BasicThreadFactory.Builder().daemon(true).priority(Thread.MIN_PRIORITY).build();
268
269 private static final Log LOG = LogFactory.getLog(DefaultFileMonitor.class);
270
271 private static final Duration DEFAULT_DELAY = Duration.ofSeconds(1);
272
273 private static final int DEFAULT_MAX_FILES = 1000;
274
275
276
277
278 private final Map<FileName, FileMonitorAgent> monitorMap = new HashMap<>();
279
280
281
282
283 private Thread monitorThread;
284
285
286
287
288 private final Stack<FileObject> deleteStack = new Stack<>();
289
290
291
292
293 private final Stack<FileObject> addStack = new Stack<>();
294
295
296
297
298 private volatile boolean runFlag = true;
299
300
301
302
303 private boolean recursive;
304
305
306
307
308 private Duration delay = DEFAULT_DELAY;
309
310
311
312
313 private int checksPerRun = DEFAULT_MAX_FILES;
314
315
316
317
318 private final FileListener listener;
319
320
321
322
323
324
325 public DefaultFileMonitor(final FileListener listener) {
326 this.listener = listener;
327 }
328
329
330
331
332
333
334 @Override
335 public void addFile(final FileObject file) {
336 synchronized (monitorMap) {
337 if (monitorMap.get(file.getName()) == null) {
338 monitorMap.put(file.getName(), new FileMonitorAgent(this, file));
339
340 try {
341 if (listener != null) {
342 file.getFileSystem().addListener(file, listener);
343 }
344
345 if (file.getType().hasChildren() && recursive) {
346
347
348 Stream.of(file.getChildren()).forEach(this::addFile);
349 }
350
351 } catch (final FileSystemException fse) {
352 LOG.error(fse.getLocalizedMessage(), fse);
353 }
354
355 }
356 }
357 }
358
359 @Override
360 public void close() {
361 runFlag = false;
362 if (monitorThread != null) {
363 monitorThread.interrupt();
364 try {
365 monitorThread.join();
366 } catch (final InterruptedException e) {
367
368 }
369 monitorThread = null;
370 }
371 }
372
373
374
375
376
377
378 public int getChecksPerRun() {
379 return checksPerRun;
380 }
381
382
383
384
385
386
387
388 @Deprecated
389 public long getDelay() {
390 return delay.toMillis();
391 }
392
393
394
395
396
397
398 public Duration getDelayDuration() {
399 return delay;
400 }
401
402
403
404
405
406
407 FileListener getFileListener() {
408 return listener;
409 }
410
411
412
413
414
415
416 public boolean isRecursive() {
417 return recursive;
418 }
419
420
421
422
423
424
425 protected void queueAddFile(final FileObject file) {
426 addStack.push(file);
427 }
428
429
430
431
432
433
434 protected void queueRemoveFile(final FileObject file) {
435 deleteStack.push(file);
436 }
437
438
439
440
441
442
443 @Override
444 public void removeFile(final FileObject file) {
445 synchronized (monitorMap) {
446 final FileName fn = file.getName();
447 if (monitorMap.get(fn) != null) {
448 FileObject parent;
449 try {
450 parent = file.getParent();
451 } catch (final FileSystemException fse) {
452 parent = null;
453 }
454
455 monitorMap.remove(fn);
456
457 if (parent != null) {
458 final FileMonitorAgent parentAgent = monitorMap.get(parent.getName());
459 if (parentAgent != null) {
460 parentAgent.resetChildrenList();
461 }
462 }
463 }
464 }
465 }
466
467
468
469
470 @Override
471 public void run() {
472 mainloop: while (!monitorThread.isInterrupted() && runFlag) {
473
474 final Object[] fileNames;
475 synchronized (monitorMap) {
476 fileNames = monitorMap.keySet().toArray();
477 }
478 for (int iterFileNames = 0; iterFileNames < fileNames.length; iterFileNames++) {
479 final FileName fileName = (FileName) fileNames[iterFileNames];
480 final FileMonitorAgent agent;
481 synchronized (monitorMap) {
482 agent = monitorMap.get(fileName);
483 }
484 if (agent != null) {
485 agent.check();
486 }
487
488 if (getChecksPerRun() > 0 && (iterFileNames + 1) % getChecksPerRun() == 0) {
489 try {
490 Thread.sleep(getDelayDuration().toMillis());
491 } catch (final InterruptedException e) {
492
493 }
494 }
495
496 if (monitorThread.isInterrupted() || !runFlag) {
497 continue mainloop;
498 }
499 }
500
501 while (!addStack.empty()) {
502 addFile(addStack.pop());
503 }
504
505 while (!deleteStack.empty()) {
506 removeFile(deleteStack.pop());
507 }
508
509 try {
510 Thread.sleep(getDelayDuration().toMillis());
511 } catch (final InterruptedException e) {
512 continue;
513 }
514 }
515
516 runFlag = true;
517 }
518
519
520
521
522
523
524 public void setChecksPerRun(final int checksPerRun) {
525 this.checksPerRun = checksPerRun;
526 }
527
528
529
530
531
532
533
534 public void setDelay(final Duration delay) {
535 this.delay = delay == null || delay.isNegative() ? DEFAULT_DELAY : delay;
536 }
537
538
539
540
541
542
543
544 @Deprecated
545 public void setDelay(final long delay) {
546 setDelay(delay > 0 ? Duration.ofMillis(delay) : DEFAULT_DELAY);
547 }
548
549
550
551
552
553
554 public void setRecursive(final boolean newRecursive) {
555 recursive = newRecursive;
556 }
557
558
559
560
561 public synchronized void start() {
562 if (monitorThread == null) {
563 monitorThread = THREAD_FACTORY.newThread(this);
564 }
565 monitorThread.start();
566 }
567
568
569
570
571 public synchronized void stop() {
572 close();
573 }
574 }