mirror of
https://github.com/Luzifer/go_helpers.git
synced 2024-12-25 13:31:21 +00:00
Allow to set watcher to follow symlinks
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
98aacbd5ad
commit
d67686d26f
3 changed files with 61 additions and 24 deletions
|
@ -2,6 +2,7 @@ package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ type (
|
||||||
|
|
||||||
c chan WatcherEvent
|
c chan WatcherEvent
|
||||||
checks []WatcherCheck
|
checks []WatcherCheck
|
||||||
|
followSymlinks bool
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
stateCache map[string]any
|
stateCache map[string]any
|
||||||
}
|
}
|
||||||
|
@ -30,6 +32,14 @@ type (
|
||||||
// WatcherEvent is the detected change to be signeld through the
|
// WatcherEvent is the detected change to be signeld through the
|
||||||
// channel within the Watcher
|
// channel within the Watcher
|
||||||
WatcherEvent uint
|
WatcherEvent uint
|
||||||
|
|
||||||
|
// WatcherOpts holds configuration for newly created watcher
|
||||||
|
WatcherOpts struct {
|
||||||
|
// FollowSymlinks switches watchers based on file metadata into
|
||||||
|
// a mode where they follow a symlink and check the real target
|
||||||
|
// file instead of the symlink
|
||||||
|
FollowSymlinks bool
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,6 +50,12 @@ const (
|
||||||
WatcherEventFileVanished
|
WatcherEventFileVanished
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultWatcherOpts is used when creating the Watcher without
|
||||||
|
// giving options
|
||||||
|
var DefaultWatcherOpts = WatcherOpts{
|
||||||
|
FollowSymlinks: false,
|
||||||
|
}
|
||||||
|
|
||||||
// NewCryptographicWatcher is a wrapper around NewWatcher to configure
|
// NewCryptographicWatcher is a wrapper around NewWatcher to configure
|
||||||
// the Watcher with presence and sha256 hash checks.
|
// the Watcher with presence and sha256 hash checks.
|
||||||
func NewCryptographicWatcher(filePath string, interval time.Duration) (*Watcher, error) {
|
func NewCryptographicWatcher(filePath string, interval time.Duration) (*Watcher, error) {
|
||||||
|
@ -53,15 +69,27 @@ func NewSimpleWatcher(filePath string, interval time.Duration) (*Watcher, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWatcher creates a new Watcher configured with the given filePath,
|
// NewWatcher creates a new Watcher configured with the given filePath,
|
||||||
// interval and checks given. The checks are executed once during
|
// default options, interval and checks given. The checks are executed
|
||||||
// initialization and will not cause an event to be sent. The created
|
// once during initialization and will not cause an event to be sent. The
|
||||||
// Watcher will automatically start its periodic check and the C
|
// created Watcher will automatically start its periodic check and the C
|
||||||
// channel should immediately be watched for changes. If the channel
|
// channel should immediately be watched for changes. If the channel is
|
||||||
// is not listened on the check loop will be paused until events are
|
// not listened on the check loop will be paused until events are
|
||||||
// retrieved. If during the initial checks an error is detected the
|
// retrieved. If during the initial checks an error is detected the loop
|
||||||
// loop is NOT started and the watcher needs to be initialized again.
|
// is NOT started and the watcher needs to be initialized again.
|
||||||
func NewWatcher(filePath string, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
|
func NewWatcher(filePath string, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
|
||||||
w, err := newWatcher(filePath, interval, checks...)
|
return NewWatcherWithOpts(filePath, DefaultWatcherOpts, interval, checks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcherWithOpts creates a new Watcher configured with the given
|
||||||
|
// filePath, options, interval and checks given. The checks are executed
|
||||||
|
// once during initialization and will not cause an event to be sent. The
|
||||||
|
// created Watcher will automatically start its periodic check and the C
|
||||||
|
// channel should immediately be watched for changes. If the channel is
|
||||||
|
// not listened on the check loop will be paused until events are
|
||||||
|
// retrieved. If during the initial checks an error is detected the loop
|
||||||
|
// is NOT started and the watcher needs to be initialized again.
|
||||||
|
func NewWatcherWithOpts(filePath string, opts WatcherOpts, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
|
||||||
|
w, err := newWatcher(filePath, opts, interval, checks...)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
go w.loop()
|
go w.loop()
|
||||||
|
@ -70,7 +98,7 @@ func NewWatcher(filePath string, interval time.Duration, checks ...WatcherCheck)
|
||||||
return w, err
|
return w, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWatcher(filePath string, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
|
func newWatcher(filePath string, opts WatcherOpts, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
|
||||||
notify := make(chan WatcherEvent, 1)
|
notify := make(chan WatcherEvent, 1)
|
||||||
|
|
||||||
w := &Watcher{
|
w := &Watcher{
|
||||||
|
@ -80,6 +108,7 @@ func newWatcher(filePath string, interval time.Duration, checks ...WatcherCheck)
|
||||||
|
|
||||||
c: notify,
|
c: notify,
|
||||||
checks: checks,
|
checks: checks,
|
||||||
|
followSymlinks: opts.FollowSymlinks,
|
||||||
stateCache: make(map[string]any),
|
stateCache: make(map[string]any),
|
||||||
}
|
}
|
||||||
// Initially run checks once
|
// Initially run checks once
|
||||||
|
@ -141,3 +170,11 @@ func (w *Watcher) runStateChecks() (WatcherEvent, error) {
|
||||||
|
|
||||||
return WatcherEventNoChange, nil
|
return WatcherEventNoChange, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) stat(filePath string) (os.FileInfo, error) {
|
||||||
|
if w.followSymlinks {
|
||||||
|
return os.Stat(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Lstat(filePath)
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ func WatcherCheckHash(hcf func() hash.Hash) WatcherCheck {
|
||||||
lastHash = v
|
lastHash = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Lstat(w.FilePath); errors.Is(err, fs.ErrNotExist) {
|
if _, err := w.stat(w.FilePath); errors.Is(err, fs.ErrNotExist) {
|
||||||
return WatcherEventInvalid, nil
|
return WatcherEventInvalid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ func WatcherCheckMtime(w *Watcher) (WatcherEvent, error) {
|
||||||
lastChange = v
|
lastChange = v
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := os.Lstat(w.FilePath)
|
s, err := w.stat(w.FilePath)
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
// handle size change
|
// handle size change
|
||||||
|
@ -94,7 +94,7 @@ func WatcherCheckPresence(w *Watcher) (WatcherEvent, error) {
|
||||||
wasPresent = v
|
wasPresent = v
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := os.Lstat(w.FilePath)
|
_, err := w.stat(w.FilePath)
|
||||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
// Some weird error occurred
|
// Some weird error occurred
|
||||||
return WatcherEventInvalid, errors.Wrap(err, "getting file stat")
|
return WatcherEventInvalid, errors.Wrap(err, "getting file stat")
|
||||||
|
@ -124,7 +124,7 @@ func WatcherCheckSize(w *Watcher) (WatcherEvent, error) {
|
||||||
knownSize = v
|
knownSize = v
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := os.Lstat(w.FilePath)
|
s, err := w.stat(w.FilePath)
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
// handle size change
|
// handle size change
|
||||||
|
|
|
@ -22,7 +22,7 @@ func TestWatcherCheckHash(t *testing.T) {
|
||||||
|
|
||||||
testFile := path.Join(testDir, "test.txt")
|
testFile := path.Join(testDir, "test.txt")
|
||||||
|
|
||||||
w, err := newWatcher(testFile, time.Second, WatcherCheckHash(sha256.New))
|
w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckHash(sha256.New))
|
||||||
require.NoError(t, err, "initial check should not error on non existing file")
|
require.NoError(t, err, "initial check should not error on non existing file")
|
||||||
|
|
||||||
evt, err := w.runStateChecks()
|
evt, err := w.runStateChecks()
|
||||||
|
@ -73,7 +73,7 @@ func TestWatcherCheckMtime(t *testing.T) {
|
||||||
|
|
||||||
testFile := path.Join(testDir, "test.txt")
|
testFile := path.Join(testDir, "test.txt")
|
||||||
|
|
||||||
w, err := newWatcher(testFile, time.Second, WatcherCheckMtime)
|
w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckMtime)
|
||||||
require.NoError(t, err, "initial check should not error on non existing file")
|
require.NoError(t, err, "initial check should not error on non existing file")
|
||||||
|
|
||||||
evt, err := w.runStateChecks()
|
evt, err := w.runStateChecks()
|
||||||
|
@ -128,7 +128,7 @@ func TestWatcherCheckPresence(t *testing.T) {
|
||||||
|
|
||||||
testFile := path.Join(testDir, "test.txt")
|
testFile := path.Join(testDir, "test.txt")
|
||||||
|
|
||||||
w, err := newWatcher(testFile, time.Second, WatcherCheckPresence)
|
w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckPresence)
|
||||||
require.NoError(t, err, "initial check should not error on non existing file")
|
require.NoError(t, err, "initial check should not error on non existing file")
|
||||||
|
|
||||||
evt, err := w.runStateChecks()
|
evt, err := w.runStateChecks()
|
||||||
|
@ -165,7 +165,7 @@ func TestWatcherCheckSize(t *testing.T) {
|
||||||
|
|
||||||
testFile := path.Join(testDir, "test.txt")
|
testFile := path.Join(testDir, "test.txt")
|
||||||
|
|
||||||
w, err := newWatcher(testFile, time.Second, WatcherCheckSize)
|
w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckSize)
|
||||||
require.NoError(t, err, "initial check should not error on non existing file")
|
require.NoError(t, err, "initial check should not error on non existing file")
|
||||||
|
|
||||||
evt, err := w.runStateChecks()
|
evt, err := w.runStateChecks()
|
||||||
|
|
Loading…
Reference in a new issue