Uploaded image for project: 'Commons VFS'
  1. Commons VFS
  2. VFS-54

[vfs][PATCH] External File Monitor (Todo List Item)

    XMLWordPrintableJSON

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: None
    • Labels:
      None
    • Environment:

      Operating System: Linux
      Platform: PC

    • Bugzilla Id:
      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)

      { 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()
        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++)

      { 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(); }

      }

      }

      }

        Attachments

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              xknight@users.sourceforge.net Christopher Ottley
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: