commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bugzi...@apache.org
Subject DO NOT REPLY [Bug 31547] New: - [vfs] [PATCH] Commons VFS Todo List Item - External File Monitor
Date Tue, 05 Oct 2004 14:09:34 GMT
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: commons-dev@jakarta.apache.org
        ReportedBy: xknight@users.sourceforge.net
                CC: mario@ops.co.at


========================
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() {
     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: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message