Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
None
-
None
-
None
-
Operating System: Linux
Platform: PC
-
31547
Description
========================
FileMonitor.java
========================
/*
- Copyright 2002, 2003,2004 The Apache Software Foundation.
* - Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
* - http://www.apache.org/licenses/LICENSE-2.0
* - Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
*/
package org.apache.commons.vfs.provider;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileListener;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.provider.AbstractFileSystem;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Stack;
/**
- Implementation Documentation
- ============================
* - The FileMonitor is a Thread based polling file system monitor with a 1 second
delay.
* - Design:
- There is a Map of monitors known as FileMonitorAgents. With the thread running,
- each FileMonitorAgent object is asked to "check" on the file it is
responsible for. - To do this check, the cache is cleared. If the file existed before the
refresh and - it no longer exists, a delete event is fired. If the file existed before the
refresh - and it still exists, check the last modified timestamp to see if that has
changed. - If it has, fire a change event (BUG no change event, fires a create event
instead). - With each file delete, the FileMonitorAgent of the parent is asked to
re-build its - list of children, so that they can be accurately checked when there are new
children. - New files are detected during each "check" as each file does a check for new
children. - If new children are found, create events are fired recursively if recursive
descent is - enabled.
* - Known bugs:
- If the fileListener is changed during the course of the monitoring, the
original listener - will not be removed if the file associated with it is removed.
*
* - Example usage:
try
{ FileSystemManager fsManager = VFS.getManager(); FileObject listendir = fsManager.resolveFile("/home/username/monitored/"); FileMonitor fm = new FileMonitor(); fm.setRecursive(true); fm.setFileListener(new CustomFileSystemListener()); fm.addFile(listendir); fm.start(); }catch (FileSystemException fse)
{ fse.printStackTrace(); }*
- where CustomFileSystemListener is a class that implements the FileListener
interface.
*/
/**
- A polling FileMonitor implementation.
* - @author <a href="mailto:xknight@users.sourceforge.net">Christopher Ottley</a>
- @version $Revision: 1.0 $ $Date: 2004/10/05 03:22:13 $
*/
public class FileMonitor implements java.lang.Runnable {
/**
- Map from FileName to FileObject being monitored.
*/
private final Map monitorMap = new HashMap();
/**
- The low priority thread used for checking the files being monitored.
*/
private Thread monitorThread;
/**
- File objects to be removed from the monitor map.
*/
private Stack deleteStack = new Stack();
/**
- File objects to be added to the monitor map.
*/
private Stack addStack = new Stack();
/**
- A flag used to determine if the monitor thread should be running.
*/
private boolean shouldRun = true;
/**
- A flag used to determine if adding files to be monitored should be recursive.
*/
private boolean recursive = false;
/**
- A listener object that if set, is notified on file creation and deletion.
*/
private FileListener listener = null;
public FileMonitor() {
}
/**
- Access method to get the recursive setting when adding files for monitoring.
*/
public boolean recursive() { return this.recursive; }
/**
- Access method to set the recursive setting when adding files for monitoring.
*/
public void setRecursive(final boolean newRecursive) { this.recursive = newRecursive; }
/**
- Access method to get the current FileListener object notified when there
are changes with the files added.
*/
public FileListener fileListener() { return this.listener; }
/**
- Access method to set the current FileListener object notified when there
are changes with the files added.
*/
public void setFileListener(final FileListener newListener) { this.listener = newListener; }
/**
- Adds a file to be monitored.
*/
public void addFile(final FileObject file) {
synchronized (this.monitorMap) {
if (this.monitorMap.get(file.getName()) == null) {
this.monitorMap.put(file.getName(), new FileMonitorAgent(this, file));
try {
if (this.listener != null)
if (file.getType().hasChildren() && this.recursive) {
// Traverse the children
final FileObject[] children = file.getChildren();
for (int i=0; i<children.length; i++)
}
} catch(FileSystemException fse) {
}
}
}
}
/**
- Removes a file from being monitored.
*/
public void removeFile(final FileObject file) {
synchronized (this.monitorMap) {
FileName fn = file.getName();
if (this.monitorMap.get(fn) != null) {
FileObject parent = null;
try { parent = file.getParent(); }catch (FileSystemException fse) {
}
this.monitorMap.remove(fn);
if (parent != null) { // Not the root
FileMonitorAgent parentAgent =
(FileMonitorAgent)this.monitorMap.get(parent.getName());
if (parentAgent != null)
}
}
}
}
/**
- Queues a file for removal from being monitored.
*/
public void queueRemoveFile(final FileObject file) { this.deleteStack.push(file); }
/**
- Queues a file for addition to be monitored.
*/
public void queueAddFile(final FileObject file) { this.addStack.push(file); }
/**
- Starts monitoring the files that have been added.
*/
public void start()Unknown macro: { if (this.monitorThread == null) { this.monitorThread = new Thread(this); this.monitorThread.setPriority(Thread.MIN_PRIORITY); } this.monitorThread.start(); }
/**
- Stops monitoring the files that have been added.
*/
public void stop() { this.shouldRun = false; }
/**
- Asks the agent for each file being monitored to check its file for changes.
*/
public void run() {
while (this.shouldRun) {
while (!this.deleteStack.empty()) { this.removeFile((FileObject)this.deleteStack.pop()); }
// For each entry in the map
Iterator i = this.monitorMap.keySet().iterator();
while ((i.hasNext()) && (this.shouldRun))
{ ((FileMonitorAgent)this.monitorMap.get((FileName)i.next())).check(); }while (!this.addStack.empty())
{ this.addFile((FileObject)this.addStack.pop()); }try
{ Thread.sleep(1000); }catch(Exception e) {
}
}
this.shouldRun = true;
}
/**
- File monitor agent.
*/
private class FileMonitorAgent {
private FileObject file;
private boolean exists;
private long timestamp;
private FileMonitor fm;
private Map children = null;
public FileMonitorAgent(FileMonitor fm, FileObject file) {
this.fm = fm;
this.file = file;
this.refresh();
this.resetChildrenList();
try
{ this.exists = this.file.exists(); }catch (FileSystemException fse)
{ this.exists = false; }try
{ this.timestamp = this.file.getContent().getLastModifiedTime(); }catch(FileSystemException fse)
{ this.timestamp = -1; }}
public void resetChildrenList() {
try {
if (this.file.getType() == FileType.FOLDER) {
this.children = new HashMap();
FileObject[] childrenList = this.file.getChildren();
for (int i=0; i<childrenList.length; i++)
}
} catch (FileSystemException fse)
}
/**
- Clear the cache and re-request the file object
*/
public void refresh() {
// TODO: Check to see if there's a better way to do this
try
{ this.exists = this.file.exists(); ((AbstractFileSystem)this.file.getFileSystem()).removeFileFromCache(this.file.getName()); FileSystemManager fsManager = this.file.getFileSystem().getFileSystemManager(); this.file = fsManager.resolveFile(this.file.getName().getPath()); }catch (FileSystemException fse)
{ fse.printStackTrace(); }}
/**
* Recursively fires create events for all children if recursive descent is
* enabled. Otherwise the create event is only fired for the initial
FileObject.
*/
public void fireAllCreate(FileObject child) {
// Add listener so that it can be triggered
if (this.fm.fileListener() != null) { child.getFileSystem().addListener(child, this.fm.fileListener()); }
((AbstractFileSystem)child.getFileSystem()).fireFileCreated(child);
// Remove it because a listener is added in the queueAddFile
if (this.fm.fileListener() != null) { child.getFileSystem().removeListener(child, this.fm.fileListener()); }
this.fm.queueAddFile(child); // Add
try {
if (this.fm.recursive()) {
if (child.getType() == FileType.FOLDER) {
FileObject[] newChildren = child.getChildren();
for (int i=0; i<newChildren.length; i++) { fireAllCreate(newChildren[i]); }
}
}
} catch (FileSystemException fse) { fse.printStackTrace(); }
}
/**
- Only checks for new children. If children are removed, they'll
eventually be checked.
*/
public void checkForNewChildren() {
try {
if (this.file.getType() == FileType.FOLDER) {
FileObject[] newChildren = this.file.getChildren();
if (this.children != null) {
// See which new children are not listed in the current children map.
Map newChildrenMap = new HashMap();
Stack missingChildren = new Stack();
for (int i=0; i<newChildren.length; i++) {
newChildrenMap.put(newChildren[i].getName(), new Object()); //
null?
// If the child's not there
if (!this.children.containsKey(newChildren[i].getName()))
}
this.children = newChildrenMap;
// If there were missing children
if (!missingChildren.empty()) {
while (!missingChildren.empty())
{ FileObject child = (FileObject)missingChildren.pop(); this.fireAllCreate(child); }}
} else {
// First set of children - Break out the cigars
if (newChildren.length > 0)
for (int i=0; i<newChildren.length; i++)
{ this.children.put(newChildren[i].getName(), new Object()); // null? this.fireAllCreate(newChildren[i]); } }
}
} catch (FileSystemException fse)
}
public void check() {
this.refresh();
try {
// If the file existed and now doesn't
if (this.exists && !this.file.exists()) {
this.exists = this.file.exists();
this.timestamp = -1;
// Fire delete event
((AbstractFileSystem)this.file.getFileSystem()).fireFileDeleted(this.file);
// Remove listener in case file is re-created. Don't want to fire
twice.
// TODO - Figure out what to do if the original file listener used
was changed.
if (this.fm.fileListener() != null) { this.file.getFileSystem().removeListener(this.file, this.fm.fileListener()); }
// Remove from map
this.fm.queueRemoveFile(this.file);
} else if (this.exists && this.file.exists()) {
// Check the timestamp to see if it has been modified
if (this.timestamp != this.file.getContent().getLastModifiedTime()) {
this.timestamp = this.file.getContent().getLastModifiedTime();
// Fire change event
// TODO - Add fireFileChanged method to AbstractFileSystem?
// TODO - Add fileChanged(FileChangeEvent event) to FileListener
interface?
// I can't find a way to specify solely a change event, so fire a
create event instead
// Don't fire if it's a folder because new file children
// and deleted files in a folder have their own event triggered.
if (this.file.getType() != FileType.FOLDER) { ((AbstractFileSystem)this.file.getFileSystem()).fireFileCreated(this.file); }
}
}
this.checkForNewChildren();
} catch(FileSystemException fse) { fse.printStackTrace(); }
}
}
}