1
0
Fork 0
mirror of https://github.com/Luzifer/go_helpers.git synced 2024-12-25 13:31:21 +00:00
go_helpers/file/watcher_checks.go
Knut Ahlers d1f1007b33
Fix logic bug in run loop, replace Stat with Lstat
in order to use a less expensive syscall when applied to symlinks like
in Kubernetes ConfigMap mounts

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2023-03-18 15:26:18 +01:00

145 lines
3.7 KiB
Go

package file
import (
"crypto/sha512"
"fmt"
"hash"
"io"
"io/fs"
"os"
"github.com/pkg/errors"
)
const (
keyWatcherCheckHash = "WatcherCheckHash"
keyWatcherCheckMtime = "WatcherCheckMtime"
keyWatcherCheckPresence = "WatcherCheckPresence"
keyWatcherCheckSize = "WatcherCheckSize"
)
// WatcherCheckHash returns a WatcherCheck configured with the given
// hash method (i.e. provide md5.New, sha1.New, ...). If the file is
// not present at the time of the check the check is skipped and will
// NOT cause an error.
func WatcherCheckHash(hcf func() hash.Hash) WatcherCheck {
return func(w *Watcher) (WatcherEvent, error) {
var lastHash string
if v, ok := w.GetState(keyWatcherCheckHash).(string); ok {
lastHash = v
}
if _, err := os.Lstat(w.FilePath); errors.Is(err, fs.ErrNotExist) {
return WatcherEventInvalid, nil
}
f, err := os.Open(w.FilePath)
if err != nil {
return WatcherEventInvalid, errors.Wrap(err, "opening file")
}
defer f.Close()
h := hcf()
if _, err = io.Copy(h, f); err != nil {
return WatcherEventInvalid, errors.Wrap(err, "reading file")
}
currentHash := fmt.Sprintf("%x", h.Sum(nil))
if lastHash == currentHash {
return WatcherEventNoChange, nil
}
w.SetState(keyWatcherCheckHash, currentHash)
return WatcherEventFileModified, nil
}
}
var _ WatcherCheck = WatcherCheckHash(sha512.New)
// WatcherCheckMtime checks whether the mtime attribute of the file
// has changed. If the file is not present at the time of the check
// the check is skipped and will NOT cause an error.
func WatcherCheckMtime(w *Watcher) (WatcherEvent, error) {
var lastChange int64
if v, ok := w.GetState(keyWatcherCheckMtime).(int64); ok {
lastChange = v
}
s, err := os.Lstat(w.FilePath)
switch {
case err == nil:
// handle size change
case errors.Is(err, fs.ErrNotExist):
return WatcherEventInvalid, nil
default:
return WatcherEventInvalid, errors.Wrap(err, "getting file stat")
}
if s.ModTime().UnixNano() == lastChange {
return WatcherEventNoChange, nil
}
w.SetState(keyWatcherCheckMtime, s.ModTime().UnixNano())
return WatcherEventFileModified, nil
}
var _ WatcherCheck = WatcherCheckMtime
// WatcherCheckPresence simply checks whether the file is present and
// allows to emit WatcherEventFileAppeared / WatcherEventFileVanished
// events when the file existence state changes.
func WatcherCheckPresence(w *Watcher) (WatcherEvent, error) {
var wasPresent bool
if v, ok := w.GetState(keyWatcherCheckPresence).(bool); ok {
wasPresent = v
}
_, err := os.Lstat(w.FilePath)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
// Some weird error occurred
return WatcherEventInvalid, errors.Wrap(err, "getting file stat")
}
isPresent := err == nil
w.SetState(keyWatcherCheckPresence, isPresent)
switch {
case !wasPresent && isPresent:
return WatcherEventFileAppeared, nil
case wasPresent && !isPresent:
return WatcherEventFileVanished, nil
default:
return WatcherEventNoChange, nil
}
}
var _ WatcherCheck = WatcherCheckPresence
// WatcherCheckSize checks whether the size of the file has changed.
// If the file is not present at the time of the check the check is
// skipped and will NOT cause an error.
func WatcherCheckSize(w *Watcher) (WatcherEvent, error) {
var knownSize int64
if v, ok := w.GetState(keyWatcherCheckSize).(int64); ok {
knownSize = v
}
s, err := os.Lstat(w.FilePath)
switch {
case err == nil:
// handle size change
case errors.Is(err, fs.ErrNotExist):
return WatcherEventInvalid, nil
default:
return WatcherEventInvalid, errors.Wrap(err, "getting file stat")
}
if s.Size() == knownSize {
return WatcherEventNoChange, nil
}
w.SetState(keyWatcherCheckSize, s.Size())
return WatcherEventFileModified, nil
}
var _ WatcherCheck = WatcherCheckSize