1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs.impl;
18
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.apache.commons.vfs.FileListener;
22 import org.apache.commons.vfs.FileMonitor;
23 import org.apache.commons.vfs.FileName;
24 import org.apache.commons.vfs.FileObject;
25 import org.apache.commons.vfs.FileSystemException;
26 import org.apache.commons.vfs.FileType;
27 import org.apache.commons.vfs.provider.AbstractFileSystem;
28
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Stack;
32
33 /***
34 * A polling {@link FileMonitor} implementation.<br />
35 * <br />
36 * The DefaultFileMonitor is a Thread based polling file system monitor with a 1
37 * second delay.<br />
38 * <br />
39 * <b>Design:</b>
40 * <p/>
41 * There is a Map of monitors known as FileMonitorAgents. With the thread running,
42 * each FileMonitorAgent object is asked to "check" on the file it is
43 * responsible for.
44 * To do this check, the cache is cleared.
45 * </p>
46 * <ul>
47 * <li>If the file existed before the refresh and it no longer exists, a delete
48 * event is fired.</li>
49 * <li>If the file existed before the refresh and it still exists, check the
50 * last modified timestamp to see if that has changed.</li>
51 * <li>If it has, fire a change event.</li>
52 * </ul>
53 * <p/>
54 * With each file delete, the FileMonitorAgent of the parent is asked to
55 * re-build its
56 * list of children, so that they can be accurately checked when there are new
57 * children.<br/>
58 * New files are detected during each "check" as each file does a check for new
59 * children.
60 * If new children are found, create events are fired recursively if recursive
61 * descent is
62 * enabled.
63 * </p>
64 * <p/>
65 * For performance reasons, added a delay that increases as the number of files
66 * monitored
67 * increases. The default is a delay of 1 second for every 1000 files processed.
68 * </p>
69 * <p/>
70 * <br /><b>Example usage:</b><pre>
71 * FileSystemManager fsManager = VFS.getManager();
72 * FileObject listendir = fsManager.resolveFile("/home/username/monitored/");
73 * <p/>
74 * DefaultFileMonitor fm = new DefaultFileMonitor(new CustomFileListener());
75 * fm.setRecursive(true);
76 * fm.addFile(listendir);
77 * fm.start();
78 * </pre>
79 * <i>(where CustomFileListener is a class that implements the FileListener
80 * interface.)</i>
81 *
82 * @author <a href="mailto:xknight@users.sourceforge.net">Christopher Ottley</a>
83 * @version $Revision: 484943 $ $Date: 2006-12-09 08:42:06 +0100 (Sa, 09 Dez 2006) $
84 */
85 public class DefaultFileMonitor implements Runnable, FileMonitor
86 {
87 private final static Log log = LogFactory.getLog(DefaultFileMonitor.class);
88
89 /***
90 * Map from FileName to FileObject being monitored.
91 */
92 private final Map monitorMap = new HashMap();
93
94 /***
95 * The low priority thread used for checking the files being monitored.
96 */
97 private Thread monitorThread;
98
99 /***
100 * File objects to be removed from the monitor map.
101 */
102 private Stack deleteStack = new Stack();
103
104 /***
105 * File objects to be added to the monitor map.
106 */
107 private Stack addStack = new Stack();
108
109 /***
110 * A flag used to determine if the monitor thread should be running.
111 */
112 private boolean shouldRun = true;
113
114 /***
115 * A flag used to determine if adding files to be monitored should be recursive.
116 */
117 private boolean recursive = false;
118
119 /***
120 * Set the delay between checks
121 */
122 private long delay = 1000;
123
124 /***
125 * Set the number of files to check until a delay will be inserted
126 */
127 private int checksPerRun = 1000;
128
129 /***
130 * A listener object that if set, is notified on file creation and deletion.
131 */
132 private final FileListener listener;
133
134 public DefaultFileMonitor(final FileListener listener)
135 {
136 this.listener = listener;
137 }
138
139 /***
140 * Access method to get the recursive setting when adding files for monitoring.
141 */
142 public boolean isRecursive()
143 {
144 return this.recursive;
145 }
146
147 /***
148 * Access method to set the recursive setting when adding files for monitoring.
149 */
150 public void setRecursive(final boolean newRecursive)
151 {
152 this.recursive = newRecursive;
153 }
154
155 /***
156 * Access method to get the current FileListener object notified when there
157 * are changes with the files added.
158 */
159 FileListener getFileListener()
160 {
161 return this.listener;
162 }
163
164 /***
165 * Adds a file to be monitored.
166 */
167 public void addFile(final FileObject file)
168 {
169 _addFile(file);
170 try
171 {
172
173 if (file.getType().hasChildren())
174 {
175
176 final FileObject[] children = file.getChildren();
177 for (int i = 0; i < children.length; i++)
178 {
179 _addFile(children[i]);
180 }
181 }
182 }
183 catch (FileSystemException fse)
184 {
185 log.error(fse.getLocalizedMessage(), fse);
186 }
187 }
188
189 /***
190 * Adds a file to be monitored.
191 */
192 private void _addFile(final FileObject file)
193 {
194 synchronized (this.monitorMap)
195 {
196 if (this.monitorMap.get(file.getName()) == null)
197 {
198 this.monitorMap.put(file.getName(), new FileMonitorAgent(this,
199 file));
200
201 try
202 {
203 if (this.listener != null)
204 {
205 file.getFileSystem().addListener(file, this.listener);
206 }
207
208 if (file.getType().hasChildren() && this.recursive)
209 {
210
211 final FileObject[] children = file.getChildren();
212 for (int i = 0; i < children.length; i++)
213 {
214 this.addFile(children[i]);
215 }
216 }
217
218 }
219 catch (FileSystemException fse)
220 {
221 log.error(fse.getLocalizedMessage(), fse);
222 }
223
224 }
225 }
226 }
227
228 /***
229 * Removes a file from being monitored.
230 */
231 public void removeFile(final FileObject file)
232 {
233 synchronized (this.monitorMap)
234 {
235 FileName fn = file.getName();
236 if (this.monitorMap.get(fn) != null)
237 {
238 FileObject parent;
239 try
240 {
241 parent = file.getParent();
242 }
243 catch (FileSystemException fse)
244 {
245 parent = null;
246 }
247
248 this.monitorMap.remove(fn);
249
250 if (parent != null)
251 {
252 FileMonitorAgent parentAgent =
253 (FileMonitorAgent) this.monitorMap.get(parent.getName());
254 if (parentAgent != null)
255 {
256 parentAgent.resetChildrenList();
257 }
258 }
259 }
260 }
261 }
262
263 /***
264 * Queues a file for removal from being monitored.
265 */
266 protected void queueRemoveFile(final FileObject file)
267 {
268 this.deleteStack.push(file);
269 }
270
271 /***
272 * Get the delay between runs
273 */
274 public long getDelay()
275 {
276 return delay;
277 }
278
279 /***
280 * Set the delay between runs
281 */
282 public void setDelay(long delay)
283 {
284 if (delay > 0)
285 {
286 this.delay = delay;
287 }
288 else
289 {
290 this.delay = 1000;
291 }
292 }
293
294 /***
295 * get the number of files to check per run
296 */
297 public int getChecksPerRun()
298 {
299 return checksPerRun;
300 }
301
302 /***
303 * set the number of files to check per run.
304 * a additional delay will be added if there are more files to check
305 *
306 * @param checksPerRun a value less than 1 will disable this feature
307 */
308 public void setChecksPerRun(int checksPerRun)
309 {
310 this.checksPerRun = checksPerRun;
311 }
312
313 /***
314 * Queues a file for addition to be monitored.
315 */
316 protected void queueAddFile(final FileObject file)
317 {
318 this.addStack.push(file);
319 }
320
321 /***
322 * Starts monitoring the files that have been added.
323 */
324 public void start()
325 {
326 if (this.monitorThread == null)
327 {
328 this.monitorThread = new Thread(this);
329 this.monitorThread.setDaemon(true);
330 this.monitorThread.setPriority(Thread.MIN_PRIORITY);
331 }
332 this.monitorThread.start();
333 }
334
335 /***
336 * Stops monitoring the files that have been added.
337 */
338 public void stop()
339 {
340 this.shouldRun = false;
341 }
342
343 /***
344 * Asks the agent for each file being monitored to check its file for changes.
345 */
346 public void run()
347 {
348 mainloop:
349 while (!Thread.currentThread().isInterrupted() && this.shouldRun)
350 {
351 while (!this.deleteStack.empty())
352 {
353 this.removeFile((FileObject) this.deleteStack.pop());
354 }
355
356
357 Object fileNames[];
358 synchronized (this.monitorMap)
359 {
360 fileNames = this.monitorMap.keySet().toArray();
361 }
362 for (int iterFileNames = 0; iterFileNames < fileNames.length;
363 iterFileNames++)
364 {
365 FileName fileName = (FileName) fileNames[iterFileNames];
366 FileMonitorAgent agent;
367 synchronized (this.monitorMap)
368 {
369 agent = (FileMonitorAgent) this.monitorMap.get(fileName);
370 }
371 if (agent != null)
372 {
373 agent.check();
374 }
375
376 if (getChecksPerRun() > 0)
377 {
378 if ((iterFileNames % getChecksPerRun()) == 0)
379 {
380 try
381 {
382 Thread.sleep(getDelay());
383 }
384 catch (InterruptedException e)
385 {
386
387 }
388 }
389 }
390
391 if (Thread.currentThread().isInterrupted() || !this.shouldRun)
392 {
393 continue mainloop;
394 }
395 }
396
397 while (!this.addStack.empty())
398 {
399 this.addFile((FileObject) this.addStack.pop());
400 }
401
402 try
403 {
404 Thread.sleep(getDelay());
405 }
406 catch (InterruptedException e)
407 {
408 continue;
409 }
410 }
411
412 this.shouldRun = true;
413 }
414
415 /***
416 * File monitor agent.
417 */
418 private static class FileMonitorAgent
419 {
420 private final FileObject file;
421 private final DefaultFileMonitor fm;
422
423 private boolean exists;
424 private long timestamp;
425 private Map children = null;
426
427 private FileMonitorAgent(DefaultFileMonitor fm, FileObject file)
428 {
429 this.fm = fm;
430 this.file = file;
431
432 this.refresh();
433 this.resetChildrenList();
434
435 try
436 {
437 this.exists = this.file.exists();
438 }
439 catch (FileSystemException fse)
440 {
441 this.exists = false;
442 }
443
444 try
445 {
446 this.timestamp = this.file.getContent().getLastModifiedTime();
447 }
448 catch (FileSystemException fse)
449 {
450 this.timestamp = -1;
451 }
452
453 }
454
455 private void resetChildrenList()
456 {
457 try
458 {
459 if (this.file.getType().hasChildren())
460 {
461 this.children = new HashMap();
462 FileObject[] childrenList = this.file.getChildren();
463 for (int i = 0; i < childrenList.length; i++)
464 {
465 this.children.put(childrenList[i].getName(), new
466 Object());
467 }
468 }
469 }
470 catch (FileSystemException fse)
471 {
472 this.children = null;
473 }
474 }
475
476
477 /***
478 * Clear the cache and re-request the file object
479 */
480 private void refresh()
481 {
482 try
483 {
484 this.file.refresh();
485 }
486 catch (FileSystemException fse)
487 {
488 log.error(fse.getLocalizedMessage(), fse);
489 }
490 }
491
492
493 /***
494 * Recursively fires create events for all children if recursive descent is
495 * enabled. Otherwise the create event is only fired for the initial
496 * FileObject.
497 */
498 private void fireAllCreate(FileObject child)
499 {
500
501 if (this.fm.getFileListener() != null)
502 {
503 child.getFileSystem().addListener(child, this.fm.getFileListener());
504 }
505
506 ((AbstractFileSystem) child.getFileSystem()).fireFileCreated(child);
507
508
509 if (this.fm.getFileListener() != null)
510 {
511 child.getFileSystem().removeListener(child,
512 this.fm.getFileListener());
513 }
514
515 this.fm.queueAddFile(child);
516
517 try
518 {
519
520 if (this.fm.isRecursive())
521 {
522 if (child.getType().hasChildren())
523 {
524 FileObject[] newChildren = child.getChildren();
525 for (int i = 0; i < newChildren.length; i++)
526 {
527 fireAllCreate(newChildren[i]);
528 }
529 }
530 }
531
532 }
533 catch (FileSystemException fse)
534 {
535 log.error(fse.getLocalizedMessage(), fse);
536 }
537 }
538
539 /***
540 * Only checks for new children. If children are removed, they'll
541 * eventually be checked.
542 */
543 private void checkForNewChildren()
544 {
545 try
546 {
547 if (this.file.getType().hasChildren())
548 {
549 FileObject[] newChildren = this.file.getChildren();
550 if (this.children != null)
551 {
552
553 Map newChildrenMap = new HashMap();
554 Stack missingChildren = new Stack();
555
556 for (int i = 0; i < newChildren.length; i++)
557 {
558 newChildrenMap.put(newChildren[i].getName(), new
559 Object());
560
561 if
562 (!this.children.containsKey(newChildren[i].getName()))
563 {
564 missingChildren.push(newChildren[i]);
565 }
566 }
567
568 this.children = newChildrenMap;
569
570
571 if (!missingChildren.empty())
572 {
573
574 while (!missingChildren.empty())
575 {
576 FileObject child = (FileObject)
577 missingChildren.pop();
578 this.fireAllCreate(child);
579 }
580 }
581
582 }
583 else
584 {
585
586 if (newChildren.length > 0)
587 {
588 this.children = new HashMap();
589 }
590 for (int i = 0; i < newChildren.length; i++)
591 {
592 this.children.put(newChildren[i].getName(), new
593 Object());
594 this.fireAllCreate(newChildren[i]);
595 }
596 }
597 }
598 }
599 catch (FileSystemException fse)
600 {
601 log.error(fse.getLocalizedMessage(), fse);
602 }
603 }
604
605 private void check()
606 {
607 this.refresh();
608
609 try
610 {
611
612 if (this.exists && !this.file.exists())
613 {
614 this.exists = this.file.exists();
615 this.timestamp = -1;
616
617
618
619 ((AbstractFileSystem)
620 this.file.getFileSystem()).fireFileDeleted(this.file);
621
622
623 if (this.fm.getFileListener() != null)
624 {
625 this.file.getFileSystem().removeListener(this.file,
626 this.fm.getFileListener());
627 }
628
629
630 this.fm.queueRemoveFile(this.file);
631 }
632 else if (this.exists && this.file.exists())
633 {
634
635
636 if (this.timestamp !=
637 this.file.getContent().getLastModifiedTime())
638 {
639 this.timestamp =
640 this.file.getContent().getLastModifiedTime();
641
642
643
644
645 if (!this.file.getType().hasChildren())
646 {
647 ((AbstractFileSystem)
648 this.file.getFileSystem()).fireFileChanged(this.file);
649 }
650 }
651
652 }
653
654 this.checkForNewChildren();
655
656 }
657 catch (FileSystemException fse)
658 {
659 log.error(fse.getLocalizedMessage(), fse);
660 }
661 }
662
663 }
664
665 }