DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT <http://issues.apache.org/bugzilla/show_bug.cgi?id=31547>. ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND INSERTED IN THE BUG DATABASE.
http://issues.apache.org/bugzilla/show_bug.cgi?id=31547 [vfs] [PATCH] Commons VFS Todo List Item - External File Monitor Summary: [vfs] [PATCH] Commons VFS Todo List Item - External File Monitor Product: Commons Version: 1.0 Alpha Platform: PC OS/Version: Linux Status: NEW Severity: Normal Priority: Other Component: Sandbox AssignedTo: [EMAIL PROTECTED] ReportedBy: [EMAIL PROTECTED] CC: [EMAIL PROTECTED] ======================== 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:[EMAIL PROTECTED]">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) { file.getFileSystem().addListener(file, this.listener); } if (file.getType().hasChildren() && this.recursive) { // Traverse the children final FileObject[] children = file.getChildren(); for (int i=0; i<children.length; i++) { this.addFile(children[i]); // Add depth first } } } 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) { parentAgent.resetChildrenList(); } } } } } /** * 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() { 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++) { this.children.put(childrenList[i].getName(), new Object()); // null? } } } catch (FileSystemException fse) { this.children = null; } } /** * 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())) { missingChildren.push(newChildren[i]); } } 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) { this.children = new HashMap(); } for (int i=0; i<newChildren.length; i++) { this.children.put(newChildren[i].getName(), new Object()); // null? this.fireAllCreate(newChildren[i]); } } } } catch (FileSystemException fse) { fse.printStackTrace(); } } 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(); } } } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
