138138 * VFS</a>.
139139 */
140140public class Tailer implements Runnable , AutoCloseable {
141+ private static final boolean DEFAULT_IGNORE_TOUCH = false ;
141142
142143 // @formatter:off
143144 /**
@@ -186,6 +187,7 @@ private static Thread newDaemonThread(final Runnable runnable) {
186187 private boolean tailFromEnd ;
187188 private boolean reOpen ;
188189 private boolean startThread = true ;
190+ private boolean ignoreTouch = DEFAULT_IGNORE_TOUCH ;
189191 private ExecutorService executorService = Executors .newSingleThreadExecutor (Builder ::newDaemonThread );
190192
191193 /**
@@ -216,7 +218,7 @@ public Builder() {
216218 */
217219 @ Override
218220 public Tailer get () {
219- final Tailer tailer = new Tailer (tailable , getCharset (), tailerListener , delayDuration , tailFromEnd , reOpen , getBufferSize ());
221+ final Tailer tailer = new Tailer (tailable , getCharset (), tailerListener , delayDuration , tailFromEnd , reOpen , getBufferSize (), ignoreTouch );
220222 if (startThread ) {
221223 executorService .submit (tailer );
222224 }
@@ -310,6 +312,23 @@ public Builder setTailFromEnd(final boolean end) {
310312 this .tailFromEnd = end ;
311313 return this ;
312314 }
315+
316+ /**
317+ * Sets ignoreTouch behaviour
318+ *
319+ * @param ignoreTouch This can be useful when your watched file gets touched (meaning it gets more recent timestamps
320+ * without changing the file) for some reason or when you are working on file systems where timestamp
321+ * is updated before content.
322+ * The default behaviour (ignoreTouch=false) would then reissue the whole current file, while
323+ * ignoreTouch=true does nothing in that case.
324+ *
325+ * @return {@code this} instance.
326+ * @since 2.20.0
327+ */
328+ public Builder setIgnoreTouch (final boolean ignoreTouch ) {
329+ this .ignoreTouch = ignoreTouch ;
330+ return this ;
331+ }
313332 }
314333
315334 /**
@@ -695,6 +714,8 @@ public static Tailer create(final File file, final TailerListener listener, fina
695714 */
696715 private volatile boolean run = true ;
697716
717+ private boolean ignoreTouch = DEFAULT_IGNORE_TOUCH ;
718+
698719 /**
699720 * Creates a Tailer for the given file, with a specified buffer size.
700721 *
@@ -710,7 +731,7 @@ public static Tailer create(final File file, final TailerListener listener, fina
710731 @ Deprecated
711732 public Tailer (final File file , final Charset charset , final TailerListener listener , final long delayMillis , final boolean end , final boolean reOpen ,
712733 final int bufSize ) {
713- this (new TailablePath (file .toPath ()), charset , listener , Duration .ofMillis (delayMillis ), end , reOpen , bufSize );
734+ this (new TailablePath (file .toPath ()), charset , listener , Duration .ofMillis (delayMillis ), end , reOpen , bufSize , DEFAULT_IGNORE_TOUCH );
714735 }
715736
716737 /**
@@ -807,10 +828,11 @@ public Tailer(final File file, final TailerListener listener, final long delayMi
807828 * @param delayDuration the delay between checks of the file for new content in milliseconds.
808829 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
809830 * @param reOpen if true, close and reopen the file between reading chunks
831+ * @param ignoreTouch if true, file timestamp changes without content change get ignored
810832 * @param bufferSize Buffer size
811833 */
812834 private Tailer (final Tailable tailable , final Charset charset , final TailerListener listener , final Duration delayDuration , final boolean end ,
813- final boolean reOpen , final int bufferSize ) {
835+ final boolean reOpen , final int bufferSize , final boolean ignoreTouch ) {
814836 this .tailable = Objects .requireNonNull (tailable , "tailable" );
815837 this .listener = Objects .requireNonNull (listener , "listener" );
816838 this .delayDuration = delayDuration ;
@@ -821,6 +843,7 @@ private Tailer(final Tailable tailable, final Charset charset, final TailerListe
821843 listener .init (this );
822844 this .reOpen = reOpen ;
823845 this .charset = charset ;
846+ this .ignoreTouch = ignoreTouch ;
824847 }
825848
826849 /**
@@ -996,14 +1019,22 @@ public void run() {
9961019 last = tailable .lastModifiedFileTime ();
9971020 } else if (newer ) {
9981021 /*
999- * This can happen if the file is truncated or overwritten with the exact same length of information. In cases like
1000- * this, the file position needs to be reset
1022+ * This can happen if the file
1023+ * - is overwritten with the exact same length of information
1024+ * - gets "touched"
1025+ * - Files.getLastModifiedTime returns a new timestamp but newer data is not yet there (
1026+ * was reported to happen on busy systems or samba network shares, see IO-279)
1027+ * The default behaviour is to replay the whole file. If this is unsdesired in your usecase,
1028+ * use the ignoreTouch builder flag
10011029 */
1002- position = 0 ;
1003- reader .seek (position ); // cannot be null here
1030+ if (!ignoreTouch ) {
1031+ position = 0 ;
1032+ reader .seek (position ); // cannot be null here
10041033
1005- // Now we can read new lines
1006- position = readLines (reader );
1034+ // Now we can read new lines
1035+ position = readLines (reader );
1036+ }
1037+ // we eitherway continue with the new timestamp
10071038 last = tailable .lastModifiedFileTime ();
10081039 }
10091040 if (reOpen && reader != null ) {
0 commit comments