View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    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             // add all direct children too
    173             if (file.getType().hasChildren())
    174             {
    175                 // Traverse the children
    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                         // Traverse the children
    211                         final FileObject[] children = file.getChildren();
    212                         for (int i = 0; i < children.length; i++)
    213                         {
    214                             this.addFile(children[i]); // Add depth first
    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                 { // Not the root
    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             // For each entry in the map
    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()); // null?
    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             // Add listener so that it can be triggered
    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             // Remove it because a listener is added in the queueAddFile
    509             if (this.fm.getFileListener() != null)
    510             {
    511                 child.getFileSystem().removeListener(child,
    512                     this.fm.getFileListener());
    513             }
    514
    515             this.fm.queueAddFile(child); // Add
    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                         // See which new children are not listed in the current children map.
    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()); // null ?
    560                             // If the child's not there
    561                             if
    562                                 (!this.children.containsKey(newChildren[i].getName()))
    563                             {
    564                                 missingChildren.push(newChildren[i]);
    565                             }
    566                         }
    567
    568                         this.children = newChildrenMap;
    569
    570                         // If there were missing children
    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                         // First set of children - Break out the cigars
    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()); // null?
    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                 // If the file existed and now doesn't
    612                 if (this.exists && !this.file.exists())
    613                 {
    614                     this.exists = this.file.exists();
    615                     this.timestamp = -1;
    616
    617                     // Fire delete event
    618
    619                     ((AbstractFileSystem)
    620                         this.file.getFileSystem()).fireFileDeleted(this.file);
    621
    622                     // Remove listener in case file is re-created. Don't want to fire twice.
    623                     if (this.fm.getFileListener() != null)
    624                     {
    625                         this.file.getFileSystem().removeListener(this.file,
    626                             this.fm.getFileListener());
    627                     }
    628
    629                     // Remove from map
    630                     this.fm.queueRemoveFile(this.file);
    631                 }
    632                 else if (this.exists && this.file.exists())
    633                 {
    634
    635                     // Check the timestamp to see if it has been modified
    636                     if (this.timestamp !=
    637                         this.file.getContent().getLastModifiedTime())
    638                     {
    639                         this.timestamp =
    640                             this.file.getContent().getLastModifiedTime();
    641                         // Fire change event
    642
    643                         // Don't fire if it's a folder because new file children
    644                         // and deleted files in a folder have their own event triggered.
    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 }