001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.io.input; 018 019 import java.io.File; 020 import java.io.FileNotFoundException; 021 import java.io.IOException; 022 import java.io.RandomAccessFile; 023 024 import org.apache.commons.io.FileUtils; 025 import org.apache.commons.io.IOUtils; 026 027 /** 028 * Simple implementation of the unix "tail -f" functionality. 029 * <p> 030 * Example Usage: 031 * <pre> 032 * // Simplest invocation using static helper method: 033 * TailerListener listener = ... 034 * Tailer tailer = Tailer.create(file, listener, delay); 035 * 036 * // Alternative method using executor: 037 * TailerListener listener = ... 038 * Tailer tailer = new Tailer(file, listener, delay); 039 * Executor executor ... 040 * executor.execute(tailer); 041 * 042 * // Alternative method if you want to handle the threading yourself: 043 * TailerListener listener = ... 044 * Tailer tailer = new Tailer(file, listener, delay); 045 * Thread thread = new Thread(tailer); 046 * thread.setDaemon(true); // optional 047 * thread.start(); 048 * 049 * // Remember to stop the tailer when you have done with it: 050 * tailer.stop(); 051 * </pre> 052 * 053 * @version $Id: Tailer.java 1021884 2010-10-12 18:49:16Z ggregory $ 054 * @since Commons IO 2.0 055 */ 056 public class Tailer implements Runnable { 057 058 /** 059 * The file which will be tailed. 060 */ 061 private final File file; 062 063 /** 064 * The amount of time to wait for the file to be updated. 065 */ 066 private final long delay; 067 068 /** 069 * Whether to tail from the end or start of file 070 */ 071 private final boolean end; 072 073 /** 074 * The listener to notify of events when tailing. 075 */ 076 private final TailerListener listener; 077 078 /** 079 * The tailer will run as long as this value is true. 080 */ 081 private volatile boolean run = true; 082 083 /** 084 * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s. 085 * @param file The file to follow. 086 * @param listener the TailerListener to use. 087 */ 088 public Tailer(File file, TailerListener listener) { 089 this(file, listener, 1000); 090 } 091 092 /** 093 * Creates a Tailer for the given file, starting from the beginning. 094 * @param file the file to follow. 095 * @param listener the TailerListener to use. 096 * @param delay the delay between checks of the file for new content in milliseconds. 097 */ 098 public Tailer(File file, TailerListener listener, long delay) { 099 this(file, listener, 1000, false); 100 } 101 102 /** 103 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 104 * @param file the file to follow. 105 * @param listener the TailerListener to use. 106 * @param delay the delay between checks of the file for new content in milliseconds. 107 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 108 */ 109 public Tailer(File file, TailerListener listener, long delay, boolean end) { 110 111 this.file = file; 112 this.delay = delay; 113 this.end = end; 114 115 // Save and prepare the listener 116 this.listener = listener; 117 listener.init(this); 118 } 119 120 /** 121 * Creates and starts a Tailer for the given file. 122 * 123 * @param file the file to follow. 124 * @param listener the TailerListener to use. 125 * @param delay the delay between checks of the file for new content in milliseconds. 126 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 127 * @return The new tailer 128 */ 129 public static Tailer create(File file, TailerListener listener, long delay, boolean end) { 130 Tailer tailer = new Tailer(file, listener, delay, end); 131 Thread thread = new Thread(tailer); 132 thread.setDaemon(true); 133 thread.start(); 134 return tailer; 135 } 136 137 /** 138 * Creates and starts a Tailer for the given file, starting at the beginning of the file 139 * 140 * @param file the file to follow. 141 * @param listener the TailerListener to use. 142 * @param delay the delay between checks of the file for new content in milliseconds. 143 * @return The new tailer 144 */ 145 public static Tailer create(File file, TailerListener listener, long delay) { 146 return create(file, listener, delay, false); 147 } 148 149 /** 150 * Creates and starts a Tailer for the given file, starting at the beginning of the file 151 * with the default delay of 1.0s 152 * 153 * @param file the file to follow. 154 * @param listener the TailerListener to use. 155 * @return The new tailer 156 */ 157 public static Tailer create(File file, TailerListener listener) { 158 return create(file, listener, 1000, false); 159 } 160 161 /** 162 * Return the file. 163 * 164 * @return the file 165 */ 166 public File getFile() { 167 return file; 168 } 169 170 /** 171 * Return the delay. 172 * 173 * @return the delay 174 */ 175 public long getDelay() { 176 return delay; 177 } 178 179 /** 180 * Follows changes in the file, calling the TailerListener's handle method for each new line. 181 */ 182 public void run() { 183 RandomAccessFile reader = null; 184 try { 185 long last = 0; // The last time the file was checked for changes 186 long position = 0; // position within the file 187 // Open the file 188 while (run && reader == null) { 189 try { 190 reader = new RandomAccessFile(file, "r"); 191 } catch (FileNotFoundException e) { 192 listener.fileNotFound(); 193 } 194 195 if (reader == null) { 196 try { 197 Thread.sleep(delay); 198 } catch (InterruptedException e) { 199 } 200 } else { 201 // The current position in the file 202 position = end ? file.length() : 0; 203 last = System.currentTimeMillis(); 204 reader.seek(position); 205 } 206 } 207 208 209 while (run) { 210 211 // Check the file length to see if it was rotated 212 long length = file.length(); 213 214 if (length < position) { 215 216 // File was rotated 217 listener.fileRotated(); 218 219 // Reopen the reader after rotation 220 try { 221 // Ensure that the old file is closed iff we re-open it successfully 222 RandomAccessFile save = reader; 223 reader = new RandomAccessFile(file, "r"); 224 position = 0; 225 // close old file explicitly rather than relying on GC picking up previous RAF 226 IOUtils.closeQuietly(save); 227 } catch (FileNotFoundException e) { 228 // in this case we continue to use the previous reader and position values 229 listener.fileNotFound(); 230 } 231 continue; 232 } else { 233 234 // File was not rotated 235 236 // See if the file needs to be read again 237 if (length > position) { 238 239 // The file has more content than it did last time 240 last = System.currentTimeMillis(); 241 position = readLines(reader); 242 243 } else if (FileUtils.isFileNewer(file, last)) { 244 245 /* This can happen if the file is truncated or overwritten 246 * with the exact same length of information. In cases like 247 * this, the file position needs to be reset 248 */ 249 position = 0; 250 reader.seek(position); // cannot be null here 251 252 // Now we can read new lines 253 last = System.currentTimeMillis(); 254 position = readLines(reader); 255 } 256 } 257 try { 258 Thread.sleep(delay); 259 } catch (InterruptedException e) { 260 } 261 } 262 263 } catch (Exception e) { 264 265 listener.handle(e); 266 267 } finally { 268 IOUtils.closeQuietly(reader); 269 } 270 } 271 272 /** 273 * Allows the tailer to complete its current loop and return. 274 */ 275 public void stop() { 276 this.run = false; 277 } 278 279 /** 280 * Read new lines. 281 * 282 * @param reader The file to read 283 * @return The new position after the lines have been read 284 * @throws java.io.IOException if an I/O error occurs. 285 */ 286 private long readLines(RandomAccessFile reader) throws IOException { 287 String line = reader.readLine(); 288 while (line != null) { 289 listener.handle(line); 290 line = reader.readLine(); 291 } 292 return reader.getFilePointer(); 293 } 294 295 }