All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.io.input.Tailer Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.io.input;

import static org.apache.commons.io.IOUtils.CR;
import static org.apache.commons.io.IOUtils.EOF;
import static org.apache.commons.io.IOUtils.LF;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.ThreadUtils;
import org.apache.commons.io.build.AbstractOrigin;
import org.apache.commons.io.build.AbstractStreamBuilder;
import org.apache.commons.io.file.PathUtils;
import org.apache.commons.io.file.attribute.FileTimes;

/**
 * Simple implementation of the UNIX "tail -f" functionality.
 * 

* To build an instance, use {@link Builder}. *

*

1. Create a TailerListener implementation

*

* First you need to create a {@link TailerListener} implementation; ({@link TailerListenerAdapter} is provided for * convenience so that you don't have to implement every method). *

*

* For example: *

*
 * public class MyTailerListener extends TailerListenerAdapter {
 *     public void handle(String line) {
 *         System.out.println(line);
 *     }
 * }
 * 
*

2. Using a Tailer

*

* You can create and use a Tailer in one of three ways: *

*
    *
  • Using a {@link Builder}
  • *
  • Using an {@link java.util.concurrent.Executor}
  • *
  • Using a {@link Thread}
  • *
*

* An example of each is shown below. *

*

2.1 Using a Builder

*
 * TailerListener listener = new MyTailerListener();
 * Tailer tailer = Tailer.builder()
 *   .setFile(file)
 *   .setTailerListener(listener)
 *   .setDelayDuration(delay)
 *   .get();
 * 
*

2.2 Using an Executor

*
 * TailerListener listener = new MyTailerListener();
 * Tailer tailer = new Tailer(file, listener, delay);
 *
 * // stupid executor impl. for demo purposes
 * Executor executor = new Executor() {
 *     public void execute(Runnable command) {
 *         command.run();
 *     }
 * };
 *
 * executor.execute(tailer);
 * 
*

2.3 Using a Thread

*
 * TailerListener listener = new MyTailerListener();
 * Tailer tailer = new Tailer(file, listener, delay);
 * Thread thread = new Thread(tailer);
 * thread.setDaemon(true); // optional
 * thread.start();
 * 
*

3. Stopping a Tailer

*

* Remember to stop the tailer when you have done with it: *

*
 * tailer.stop();
 * 
*

4. Interrupting a Tailer

*

* You can interrupt the thread a tailer is running on by calling {@link Thread#interrupt()}. *

*
 * thread.interrupt();
 * 
*

* If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}. *

*

* The file is read using the default Charset; this can be overridden if necessary. *

* * @see Builder * @see TailerListener * @see TailerListenerAdapter * @since 2.0 * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()}. * @since 2.12.0 Add {@link Tailable} and {@link RandomAccessResourceBridge} interfaces to tail of files accessed using * alternative libraries such as jCIFS or Apache Commons * VFS. */ public class Tailer implements Runnable, AutoCloseable { // @formatter:off /** * Builds a new {@link Tailer}. * *

* For example: *

*
{@code
     * Tailer t = Tailer.builder()
     *   .setPath(path)
     *   .setCharset(StandardCharsets.UTF_8)
     *   .setDelayDuration(Duration.ofSeconds(1))
     *   .setExecutorService(Executors.newSingleThreadExecutor(Builder::newDaemonThread))
     *   .setReOpen(false)
     *   .setStartThread(true)
     *   .setTailable(tailable)
     *   .setTailerListener(tailerListener)
     *   .setTailFromEnd(false)
     *   .get();}
     * 
* * @see #get() * @since 2.12.0 */ // @formatter:on public static class Builder extends AbstractStreamBuilder { private static final Duration DEFAULT_DELAY_DURATION = Duration.ofMillis(DEFAULT_DELAY_MILLIS); /** * Creates a new daemon thread. * * @param runnable the thread's runnable. * @return a new daemon thread. */ private static Thread newDaemonThread(final Runnable runnable) { final Thread thread = new Thread(runnable, "commons-io-tailer"); thread.setDaemon(true); return thread; } private Tailable tailable; private TailerListener tailerListener; private Duration delayDuration = DEFAULT_DELAY_DURATION; private boolean tailFromEnd; private boolean reOpen; private boolean startThread = true; private ExecutorService executorService = Executors.newSingleThreadExecutor(Builder::newDaemonThread); /** * Builds a new {@link Tailer}. * *

* This builder use the following aspects: *

*
    *
  • {@link #getBufferSize()}
  • *
  • {@link #getCharset()}
  • *
  • {@link Tailable}
  • *
  • {@link TailerListener}
  • *
  • delayDuration
  • *
  • tailFromEnd
  • *
  • reOpen
  • *
* * @return a new instance. */ @Override public Tailer get() { final Tailer tailer = new Tailer(tailable, getCharset(), tailerListener, delayDuration, tailFromEnd, reOpen, getBufferSize()); if (startThread) { executorService.submit(tailer); } return tailer; } /** * Sets the delay duration. null resets to the default delay of one second. * * @param delayDuration the delay between checks of the file for new content. * @return this */ public Builder setDelayDuration(final Duration delayDuration) { this.delayDuration = delayDuration != null ? delayDuration : DEFAULT_DELAY_DURATION; return this; } /** * Sets the executor service to use when startThread is true. * * @param executorService the executor service to use when startThread is true. * @return this */ public Builder setExecutorService(final ExecutorService executorService) { this.executorService = Objects.requireNonNull(executorService, "executorService"); return this; } /** * Sets the origin. * * @throws UnsupportedOperationException if the origin cannot be converted to a Path. */ @Override protected Builder setOrigin(final AbstractOrigin origin) { setTailable(new TailablePath(origin.getPath())); return super.setOrigin(origin); } /** * Sets the re-open behavior. * * @param reOpen whether to close/reopen the file between chunks * @return this */ public Builder setReOpen(final boolean reOpen) { this.reOpen = reOpen; return this; } /** * Sets the daemon thread startup behavior. * * @param startThread whether to create a daemon thread automatically. * @return this */ public Builder setStartThread(final boolean startThread) { this.startThread = startThread; return this; } /** * Sets the tailable. * * @param tailable the tailable. * @return this. */ public Builder setTailable(final Tailable tailable) { this.tailable = Objects.requireNonNull(tailable, "tailable"); return this; } /** * Sets the listener. * * @param tailerListener the listener. * @return this */ public Builder setTailerListener(final TailerListener tailerListener) { this.tailerListener = Objects.requireNonNull(tailerListener, "tailerListener"); return this; } /** * Sets the tail start behavior. * * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @return this */ public Builder setTailFromEnd(final boolean end) { this.tailFromEnd = end; return this; } } /** * Bridges random access to a {@link RandomAccessFile}. */ private static final class RandomAccessFileBridge implements RandomAccessResourceBridge { private final RandomAccessFile randomAccessFile; private RandomAccessFileBridge(final File file, final String mode) throws FileNotFoundException { randomAccessFile = new RandomAccessFile(file, mode); } @Override public void close() throws IOException { randomAccessFile.close(); } @Override public long getPointer() throws IOException { return randomAccessFile.getFilePointer(); } @Override public int read(final byte[] b) throws IOException { return randomAccessFile.read(b); } @Override public void seek(final long position) throws IOException { randomAccessFile.seek(position); } } /** * Bridges access to a resource for random access, normally a file. Allows substitution of remote files for example * using jCIFS. * * @since 2.12.0 */ public interface RandomAccessResourceBridge extends Closeable { /** * Gets the current offset in this tailable. * * @return the offset from the beginning of the tailable, in bytes, at which the next read or write occurs. * @throws IOException if an I/O error occurs. */ long getPointer() throws IOException; /** * Reads up to {@code b.length} bytes of data from this tailable into an array of bytes. This method blocks until at * least one byte of input is available. * * @param b the buffer into which the data is read. * @return the total number of bytes read into the buffer, or {@code -1} if there is no more data because the end of * this tailable has been reached. * @throws IOException If the first byte cannot be read for any reason other than end of tailable, or if the random * access tailable has been closed, or if some other I/O error occurs. */ int read(final byte[] b) throws IOException; /** * Sets the file-pointer offset, measured from the beginning of this tailable, at which the next read or write occurs. * The offset may be set beyond the end of the tailable. Setting the offset beyond the end of the tailable does not * change the tailable length. The tailable length will change only by writing after the offset has been set beyond the * end of the tailable. * * @param pos the offset position, measured in bytes from the beginning of the tailable, at which to set the tailable * pointer. * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs. */ void seek(final long pos) throws IOException; } /** * A tailable resource like a file. * * @since 2.12.0 */ public interface Tailable { /** * Creates a random access file stream to read. * * @param mode the access mode, by default this is for {@link RandomAccessFile}. * @return a random access file stream to read. * @throws FileNotFoundException if the tailable object does not exist. */ RandomAccessResourceBridge getRandomAccess(final String mode) throws FileNotFoundException; /** * Tests if this tailable is newer than the specified {@link FileTime}. * * @param fileTime the file time reference. * @return true if the {@link File} exists and has been modified after the given {@link FileTime}. * @throws IOException if an I/O error occurs. */ boolean isNewer(final FileTime fileTime) throws IOException; /** * Gets the last modification {@link FileTime}. * * @return See {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}. * @throws IOException if an I/O error occurs. */ FileTime lastModifiedFileTime() throws IOException; /** * Gets the size of this tailable. * * @return The size, in bytes, of this tailable, or {@code 0} if the file does not exist. Some operating systems may * return {@code 0} for path names denoting system-dependent entities such as devices or pipes. * @throws IOException if an I/O error occurs. */ long size() throws IOException; } /** * A tailable for a file {@link Path}. */ private static final class TailablePath implements Tailable { private final Path path; private final LinkOption[] linkOptions; private TailablePath(final Path path, final LinkOption... linkOptions) { this.path = Objects.requireNonNull(path, "path"); this.linkOptions = linkOptions; } Path getPath() { return path; } @Override public RandomAccessResourceBridge getRandomAccess(final String mode) throws FileNotFoundException { return new RandomAccessFileBridge(path.toFile(), mode); } @Override public boolean isNewer(final FileTime fileTime) throws IOException { return PathUtils.isNewer(path, fileTime, linkOptions); } @Override public FileTime lastModifiedFileTime() throws IOException { return Files.getLastModifiedTime(path, linkOptions); } @Override public long size() throws IOException { return Files.size(path); } @Override public String toString() { return "TailablePath [file=" + path + ", linkOptions=" + Arrays.toString(linkOptions) + "]"; } } private static final int DEFAULT_DELAY_MILLIS = 1000; private static final String RAF_READ_ONLY_MODE = "r"; // The default charset used for reading files private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); /** * Constructs a new {@link Builder}. * * @return Creates a new {@link Builder}. * @since 2.12.0 */ public static Builder builder() { return new Builder(); } /** * Creates and starts a Tailer for the given file. * * @param file the file to follow. * @param charset the character set to use for reading the file. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen whether to close/reopen the file between chunks. * @param bufferSize buffer size. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufferSize) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .setCharset(charset) .setDelayDuration(Duration.ofMillis(delayMillis)) .setTailFromEnd(end) .setReOpen(reOpen) .setBufferSize(bufferSize) .get(); //@formatter:on } /** * Creates and starts a Tailer for the given file, starting at the beginning of the file with the default delay of 1.0s * * @param file the file to follow. * @param listener the TailerListener to use. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final TailerListener listener) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .get(); //@formatter:on } /** * Creates and starts a Tailer for the given file, starting at the beginning of the file * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final TailerListener listener, final long delayMillis) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .setDelayDuration(Duration.ofMillis(delayMillis)) .get(); //@formatter:on } /** * Creates and starts a Tailer for the given file with default buffer size. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .setDelayDuration(Duration.ofMillis(delayMillis)) .setTailFromEnd(end) .get(); //@formatter:on } /** * Creates and starts a Tailer for the given file with default buffer size. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen whether to close/reopen the file between chunks. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .setDelayDuration(Duration.ofMillis(delayMillis)) .setTailFromEnd(end) .setReOpen(reOpen) .get(); //@formatter:on } /** * Creates and starts a Tailer for the given file. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen whether to close/reopen the file between chunks. * @param bufferSize buffer size. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufferSize) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .setDelayDuration(Duration.ofMillis(delayMillis)) .setTailFromEnd(end) .setReOpen(reOpen) .setBufferSize(bufferSize) .get(); //@formatter:on } /** * Creates and starts a Tailer for the given file. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param bufferSize buffer size. * @return The new tailer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) { //@formatter:off return builder() .setFile(file) .setTailerListener(listener) .setDelayDuration(Duration.ofMillis(delayMillis)) .setTailFromEnd(end) .setBufferSize(bufferSize) .get(); //@formatter:on } /** * Buffer on top of RandomAccessResourceBridge. */ private final byte[] inbuf; /** * The file which will be tailed. */ private final Tailable tailable; /** * The character set that will be used to read the file. */ private final Charset charset; /** * The amount of time to wait for the file to be updated. */ private final Duration delayDuration; /** * Whether to tail from the end or start of file */ private final boolean tailAtEnd; /** * The listener to notify of events when tailing. */ private final TailerListener listener; /** * Whether to close and reopen the file whilst waiting for more input. */ private final boolean reOpen; /** * The tailer will run as long as this value is true. */ private volatile boolean run = true; /** * Creates a Tailer for the given file, with a specified buffer size. * * @param file the file to follow. * @param charset the Charset to be used for reading the file * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen if true, close and reopen the file between reading chunks * @param bufSize Buffer size * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufSize) { this(new TailablePath(file.toPath()), charset, listener, Duration.ofMillis(delayMillis), end, reOpen, bufSize); } /** * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s. * * @param file The file to follow. * @param listener the TailerListener to use. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final TailerListener listener) { this(file, listener, DEFAULT_DELAY_MILLIS); } /** * Creates a Tailer for the given file, starting from the beginning. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final TailerListener listener, final long delayMillis) { this(file, listener, delayMillis, false); } /** * Creates a Tailer for the given file, with a delay other than the default 1.0s. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) { this(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE); } /** * Creates a Tailer for the given file, with a delay other than the default 1.0s. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen if true, close and reopen the file between reading chunks * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) { this(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE); } /** * Creates a Tailer for the given file, with a specified buffer size. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen if true, close and reopen the file between reading chunks * @param bufferSize Buffer size * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufferSize) { this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufferSize); } /** * Creates a Tailer for the given file, with a specified buffer size. * * @param file the file to follow. * @param listener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param bufferSize Buffer size * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) { this(file, listener, delayMillis, end, false, bufferSize); } /** * Creates a Tailer for the given file, with a specified buffer size. * * @param tailable the file to follow. * @param charset the Charset to be used for reading the file * @param listener the TailerListener to use. * @param delayDuration the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. * @param reOpen if true, close and reopen the file between reading chunks * @param bufferSize Buffer size */ private Tailer(final Tailable tailable, final Charset charset, final TailerListener listener, final Duration delayDuration, final boolean end, final boolean reOpen, final int bufferSize) { this.tailable = Objects.requireNonNull(tailable, "tailable"); this.listener = Objects.requireNonNull(listener, "listener"); this.delayDuration = delayDuration; this.tailAtEnd = end; this.inbuf = IOUtils.byteArray(bufferSize); // Save and prepare the listener listener.init(this); this.reOpen = reOpen; this.charset = charset; } /** * Requests the tailer to complete its current loop and return. */ @Override public void close() { this.run = false; } /** * Gets the delay in milliseconds. * * @return the delay in milliseconds. * @deprecated Use {@link #getDelayDuration()}. */ @Deprecated public long getDelay() { return delayDuration.toMillis(); } /** * Gets the delay Duration. * * @return the delay Duration. * @since 2.12.0 */ public Duration getDelayDuration() { return delayDuration; } /** * Gets the file. * * @return the file * @throws IllegalStateException if constructed using a user provided {@link Tailable} implementation */ public File getFile() { if (tailable instanceof TailablePath) { return ((TailablePath) tailable).getPath().toFile(); } throw new IllegalStateException("Cannot extract java.io.File from " + tailable.getClass().getName()); } /** * Gets whether to keep on running. * * @return whether to keep on running. * @since 2.5 */ protected boolean getRun() { return run; } /** * Gets the Tailable. * * @return the Tailable * @since 2.12.0 */ public Tailable getTailable() { return tailable; } /** * Reads new lines. * * @param reader The file to read * @return The new position after the lines have been read * @throws IOException if an I/O error occurs. */ private long readLines(final RandomAccessResourceBridge reader) throws IOException { try (ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64)) { long pos = reader.getPointer(); long rePos = pos; // position to re-read int num; boolean seenCR = false; while (getRun() && (num = reader.read(inbuf)) != EOF) { for (int i = 0; i < num; i++) { final byte ch = inbuf[i]; switch (ch) { case LF: seenCR = false; // swallow CR before LF listener.handle(new String(lineBuf.toByteArray(), charset)); lineBuf.reset(); rePos = pos + i + 1; break; case CR: if (seenCR) { lineBuf.write(CR); } seenCR = true; break; default: if (seenCR) { seenCR = false; // swallow final CR listener.handle(new String(lineBuf.toByteArray(), charset)); lineBuf.reset(); rePos = pos + i + 1; } lineBuf.write(ch); } } pos = reader.getPointer(); } reader.seek(rePos); // Ensure we can re-read if necessary if (listener instanceof TailerListenerAdapter) { ((TailerListenerAdapter) listener).endOfFileReached(); } return rePos; } } /** * Follows changes in the file, calling {@link TailerListener#handle(String)} with each new line. */ @Override public void run() { RandomAccessResourceBridge reader = null; try { FileTime last = FileTimes.EPOCH; // The last time the file was checked for changes long position = 0; // position within the file // Open the file while (getRun() && reader == null) { try { reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE); } catch (final FileNotFoundException e) { listener.fileNotFound(); } if (reader == null) { ThreadUtils.sleep(delayDuration); } else { // The current position in the file position = tailAtEnd ? tailable.size() : 0; last = tailable.lastModifiedFileTime(); reader.seek(position); } } while (getRun()) { final boolean newer = tailable.isNewer(last); // IO-279, must be done first // Check the file length to see if it was rotated final long length = tailable.size(); if (length < position) { // File was rotated listener.fileRotated(); // Reopen the reader after rotation ensuring that the old file is closed iff we re-open it // successfully try (RandomAccessResourceBridge save = reader) { reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE); // At this point, we're sure that the old file is rotated // Finish scanning the old file and then we'll start with the new one try { readLines(save); } catch (final IOException ioe) { listener.handle(ioe); } position = 0; } catch (final FileNotFoundException e) { // in this case we continue to use the previous reader and position values listener.fileNotFound(); ThreadUtils.sleep(delayDuration); } continue; } // File was not rotated // See if the file needs to be read again if (length > position) { // The file has more content than it did last time position = readLines(reader); last = tailable.lastModifiedFileTime(); } else if (newer) { /* * This can happen if the file is truncated or overwritten with the exact same length of information. In cases like * this, the file position needs to be reset */ position = 0; reader.seek(position); // cannot be null here // Now we can read new lines position = readLines(reader); last = tailable.lastModifiedFileTime(); } if (reOpen && reader != null) { reader.close(); } ThreadUtils.sleep(delayDuration); if (getRun() && reOpen) { reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE); reader.seek(position); } } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); listener.handle(e); } catch (final Exception e) { listener.handle(e); } finally { try { IOUtils.close(reader); } catch (final IOException e) { listener.handle(e); } close(); } } /** * Requests the tailer to complete its current loop and return. * * @deprecated Use {@link #close()}. */ @Deprecated public void stop() { close(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy