View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.server;
30  
31  import org.apache.log4j.Logger;
32  
33  import java.io.File;
34  
35  import uk.ac.rdg.resc.jstyx.StyxException;
36  import uk.ac.rdg.resc.jstyx.types.ULong;
37  import uk.ac.rdg.resc.jstyx.server.StyxDirectory;
38  
39  /***
40   * A file on disk that is continuously monitored.  This is basically an
41   * AsyncStyxFile that wraps a FileOnDisk, with a separate thread that monitors
42   * the underlying file (checks for changes to the last modified time and length
43   * of the file).  Note that the underlying file does not have to exist at the 
44   * time the MonitoredFileOnDisk object is constructed or at the time the monitor
45   * is started.
46   *
47   * @todo Start monitoring when the first client opens this file?  Stop monitoring
48   * when the last client closes the file?
49   *
50   * @todo Link refresh() with notifications to clients?
51   *
52   * @author Jon Blower
53   * $Revision: 223 $
54   * $Date: 2005-05-11 16:15:35 +0100 (Wed, 11 May 2005) $
55   * $Log$
56   * Revision 1.5  2005/05/11 15:15:35  jonblower
57   * Added new constructor
58   *
59   * Revision 1.4  2005/05/11 10:33:50  jonblower
60   * Implemented MonitoredFileOnDisk.java
61   *
62   *
63   * Revision 1.1  2005/05/09 13:22:43  jonblower
64   * Initial import
65   *
66   */
67  public class MonitoredFileOnDisk extends AsyncStyxFile implements Runnable
68  {
69      
70      private static final Logger log = Logger.getLogger(MonitoredFileOnDisk.class);
71      
72      private File file;            // the file to monitor
73      private long monitorInterval; // The time between polls to the filesystem,
74                                    // in milliseconds
75      private boolean fileExists;   // true if the file exists
76      private long length;          // length of the file in bytes
77      
78      private boolean monitoring;   // Set this false to stop the monitoring
79      private Thread monitor;       // The thread that does the monitoring
80      
81      /***
82       * Creates a new instance of MonitoredFileOnDisk. Call startMonitoring() to
83       * start monitoring the underlying file.
84       * @param filename The name of the underlying file to monitor. Note that this
85       * file does not need to exist yet
86       * @param monitorInterval The time in milliseconds between each check to see
87       * if the file has changed
88       * @throws StyxException if the file name is illegal
89       */
90      public MonitoredFileOnDisk(String filename, long monitorInterval) throws StyxException
91      {
92          this(new File(filename), monitorInterval);
93      }
94      
95      /***
96       * Creates a new instance of MonitoredFileOnDisk that monitors the underlying
97       * file every 2 seconds. Call startMonitoring() to start monitoring the
98       * underlying file.
99       * @param filename The name of the underlying file to monitor. Note that this
100      * file does not need to exist yet
101      * @throws StyxException if the file name is illegal
102      */
103     public MonitoredFileOnDisk(String filename) throws StyxException
104     {
105         this(new File(filename));
106     }
107     
108     /***
109      * Creates a new instance of MonitoredFileOnDisk. Call startMonitoring() to
110      * start monitoring the underlying file.
111      * @param name The name of the file as it will appear in the Styx namespace
112      * @param file The underlying file to monitor. Note that this file does not
113      * need to exist yet
114      * @param monitorInterval The time in milliseconds between each check to see
115      * if the file has changed
116      * @throws StyxException if the file name is illegal
117      */
118     public MonitoredFileOnDisk(String name, File file, long monitorInterval) throws StyxException
119     {
120         // The "false" means that the underlying java.io.File does not have to
121         // exist: if it does not exist, it will appear as an empty read-only file.
122         // TODO: are the file permissions appropriate?
123         super(new FileOnDisk(name, file, 0444, false));
124         this.file = file;
125         this.monitorInterval = monitorInterval;
126         this.fileExists = file.exists();
127         this.lastModifiedTime = file.lastModified() / 1000;
128         this.length = file.length();
129         // Prepare the thread but don't start monitoring the file yet
130         this.monitor = new Thread(this);
131     }
132     
133     /***
134      * Creates a new instance of MonitoredFileOnDisk. Call startMonitoring() to
135      * start monitoring the underlying file.
136      * @param file The underlying file to monitor. Note that this file does not
137      * need to exist yet
138      * @param monitorInterval The time in milliseconds between each check to see
139      * if the file has changed
140      * @throws StyxException if the file name is illegal
141      */
142     public MonitoredFileOnDisk(File file, long monitorInterval) throws StyxException
143     {
144         this(file.getName(), file, monitorInterval);
145     }
146     
147     /***
148      * Creates a new instance of MonitoredFileOnDisk that monitors the underlying
149      * file every 2 seconds. Call startMonitoring() to start monitoring the
150      * underlying file.
151      * @param file The underlying file to monitor. Note that this file does not
152      * need to exist yet
153      * @throws StyxException if the file name is illegal
154      */
155     public MonitoredFileOnDisk(File file) throws StyxException
156     {
157         this(file, 2000);
158     }
159     
160     public void run()
161     {
162         try
163         {
164             while(this.monitoring)
165             {
166                 
167                 long start = System.currentTimeMillis();
168                 if (this.file.exists())
169                 {
170                     if (this.fileExists)
171                     {
172                         // We already knew that this file exists. Let's look at
173                         // the last modified time.
174                         long newLastMod = this.file.lastModified() / 1000;
175                         if (newLastMod != this.lastModifiedTime)
176                         {
177                             this.lastModifiedTime = newLastMod;
178                             // notify that the contents have changed.
179                             this.contentsChanged();
180                         }
181                         // Last mod time hasn't changed.  Check the length of the file
182                         // TODO: is this necessary?  Will this ever change if lastModified
183                         // hasn't changed?  Just a double-check really.
184                         else
185                         {
186                             long newLength = this.file.length();
187                             if (newLength != this.length)
188                             {
189                                 this.length = newLength;
190                                 this.contentsChanged();
191                             }
192                         }
193                     }
194                     else
195                     {
196                         // This is the first time that we've noticed that the file exists
197                         this.fileExists = true;
198                         this.length = this.file.length();
199                         this.lastModifiedTime = this.file.lastModified() / 1000;
200                         this.contentsChanged();
201                     }
202                 }
203                 else
204                 {
205                     // The file doesn't exist
206                     if (this.fileExists)
207                     {
208                         // The file previously existed, now it has vanished.
209                         this.length = 0L;
210                         this.lastModifiedTime = 0L;
211                         this.contentsChanged(); // This will write EOF to all waiting clients
212                     }
213                 }
214                 
215                 // Work out how long we have to wait before the next poll, given
216                 // that the preceding code may have taken a little time
217                 long timeTaken = System.currentTimeMillis() - start;
218                 long timeToWait = this.monitorInterval - timeTaken;
219                 if (timeToWait > 0)
220                 {
221                     Thread.sleep(timeToWait);
222                 }
223                 
224             } // end while loop
225         }
226         catch (InterruptedException ie)
227         {
228             if (log.isDebugEnabled())
229             {
230                 log.debug("MonitoredFileOnDisk " + this.name +
231                     " interrupted while sleeping");
232             }
233         }
234     }
235     
236     public ULong getLength()
237     {
238         return new ULong(this.length);
239     }
240     
241     /***
242      * Starts monitoring the file
243      */
244     public void startMonitoring()
245     {
246         log.debug("Starting to monitor " + this.file.getPath());
247         this.monitoring = true;
248         this.monitor.start();
249     }
250     
251     /***
252      * Stops monitoring the file
253      */
254     public void stopMonitoring()
255     {
256         log.debug("Stopping monitoring of " + this.file.getPath());
257         this.monitoring = false;
258         this.monitor.interrupt();
259     }
260     
261     /***
262      * Simple test program
263      */
264     public static void main (String[] args) throws Exception
265     {
266         MonitoredFileOnDisk monFile = new MonitoredFileOnDisk("C://monitorme.txt");
267         monFile.startMonitoring();
268         StyxDirectory root = new StyxDirectory("/").addChild(monFile);
269         new StyxServer(9996, root).start();
270     }
271     
272 }