github.com/durkenhe/tailf@v0.0.0-20210829070326-d7341c377de4/watch/polling.go (about)

     1  // Copyright (c) 2015 HPE Software Inc. All rights reserved.
     2  // Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
     3  
     4  package watch
     5  
     6  import (
     7  	"os"
     8  	"runtime"
     9  	"time"
    10  
    11  	"github.com/durkenhe/tailf/util"
    12  	"gopkg.in/tomb.v1"
    13  )
    14  
    15  // PollingFileWatcher polls the file for changes.
    16  type PollingFileWatcher struct {
    17  	Filename string
    18  	Size     int64
    19  }
    20  
    21  func NewPollingFileWatcher(filename string) *PollingFileWatcher {
    22  	fw := &PollingFileWatcher{filename, 0}
    23  	return fw
    24  }
    25  
    26  var POLL_DURATION time.Duration
    27  
    28  func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
    29  	for {
    30  		if _, err := os.Stat(fw.Filename); err == nil {
    31  			return nil
    32  		} else if !os.IsNotExist(err) {
    33  			return err
    34  		}
    35  		select {
    36  		case <-time.After(POLL_DURATION):
    37  			continue
    38  		case <-t.Dying():
    39  			return tomb.ErrDying
    40  		}
    41  	}
    42  	panic("unreachable")
    43  }
    44  
    45  func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
    46  	origFi, err := os.Stat(fw.Filename)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	changes := NewFileChanges()
    52  	var prevModTime time.Time
    53  
    54  	// XXX: use tomb.Tomb to cleanly manage these goroutines. replace
    55  	// the fatal (below) with tomb's Kill.
    56  
    57  	fw.Size = pos
    58  
    59  	go func() {
    60  		prevSize := fw.Size
    61  		for {
    62  			select {
    63  			case <-t.Dying():
    64  				return
    65  			default:
    66  			}
    67  
    68  			time.Sleep(POLL_DURATION)
    69  			fi, err := os.Stat(fw.Filename)
    70  			if err != nil {
    71  				// Windows cannot delete a file if a handle is still open (tail keeps one open)
    72  				// so it gives access denied to anything trying to read it until all handles are released.
    73  				if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) {
    74  					// File does not exist (has been deleted).
    75  					changes.NotifyDeleted()
    76  					return
    77  				}
    78  
    79  				// XXX: report this error back to the user
    80  				util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
    81  			}
    82  
    83  			// File got moved/renamed?
    84  			if !os.SameFile(origFi, fi) {
    85  				changes.NotifyDeleted()
    86  				return
    87  			}
    88  
    89  			// File got truncated?
    90  			fw.Size = fi.Size()
    91  			if prevSize > 0 && prevSize > fw.Size {
    92  				changes.NotifyTruncated()
    93  				prevSize = fw.Size
    94  				continue
    95  			}
    96  			// File got bigger?
    97  			if prevSize > 0 && prevSize < fw.Size {
    98  				changes.NotifyModified()
    99  				prevSize = fw.Size
   100  				continue
   101  			}
   102  			prevSize = fw.Size
   103  
   104  			// File was appended to (changed)?
   105  			modTime := fi.ModTime()
   106  			if modTime != prevModTime {
   107  				prevModTime = modTime
   108  				changes.NotifyModified()
   109  			}
   110  		}
   111  	}()
   112  
   113  	return changes, nil
   114  }
   115  
   116  func init() {
   117  	POLL_DURATION = 250 * time.Millisecond
   118  }