| // Copyright (c) 2015 HPE Software Inc. All rights reserved. |
| // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. |
| |
| package watch |
| |
| import ( |
| "os" |
| "runtime" |
| "time" |
| |
| "github.com/hpcloud/tail/util" |
| "gopkg.in/tomb.v1" |
| ) |
| |
| // PollingFileWatcher polls the file for changes. |
| type PollingFileWatcher struct { |
| Filename string |
| Size int64 |
| } |
| |
| func NewPollingFileWatcher(filename string) *PollingFileWatcher { |
| fw := &PollingFileWatcher{filename, 0} |
| return fw |
| } |
| |
| var POLL_DURATION time.Duration |
| |
| func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error { |
| for { |
| if _, err := os.Stat(fw.Filename); err == nil { |
| return nil |
| } else if !os.IsNotExist(err) { |
| return err |
| } |
| select { |
| case <-time.After(POLL_DURATION): |
| continue |
| case <-t.Dying(): |
| return tomb.ErrDying |
| } |
| } |
| panic("unreachable") |
| } |
| |
| func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) { |
| origFi, err := os.Stat(fw.Filename) |
| if err != nil { |
| return nil, err |
| } |
| |
| changes := NewFileChanges() |
| var prevModTime time.Time |
| |
| // XXX: use tomb.Tomb to cleanly manage these goroutines. replace |
| // the fatal (below) with tomb's Kill. |
| |
| fw.Size = pos |
| |
| go func() { |
| prevSize := fw.Size |
| for { |
| select { |
| case <-t.Dying(): |
| return |
| default: |
| } |
| |
| time.Sleep(POLL_DURATION) |
| fi, err := os.Stat(fw.Filename) |
| if err != nil { |
| // Windows cannot delete a file if a handle is still open (tail keeps one open) |
| // so it gives access denied to anything trying to read it until all handles are released. |
| if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) { |
| // File does not exist (has been deleted). |
| changes.NotifyDeleted() |
| return |
| } |
| |
| // XXX: report this error back to the user |
| util.Fatal("Failed to stat file %v: %v", fw.Filename, err) |
| } |
| |
| // File got moved/renamed? |
| if !os.SameFile(origFi, fi) { |
| changes.NotifyDeleted() |
| return |
| } |
| |
| // File got truncated? |
| fw.Size = fi.Size() |
| if prevSize > 0 && prevSize > fw.Size { |
| changes.NotifyTruncated() |
| prevSize = fw.Size |
| continue |
| } |
| // File got bigger? |
| if prevSize > 0 && prevSize < fw.Size { |
| changes.NotifyModified() |
| prevSize = fw.Size |
| continue |
| } |
| prevSize = fw.Size |
| |
| // File was appended to (changed)? |
| modTime := fi.ModTime() |
| if modTime != prevModTime { |
| prevModTime = modTime |
| changes.NotifyModified() |
| } |
| } |
| }() |
| |
| return changes, nil |
| } |
| |
| func init() { |
| POLL_DURATION = 250 * time.Millisecond |
| } |