mukan-ignite/ignite/pkg/localfs/watcher.go
Mukan Erkin Törük c32551b6f7
Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:24 +03:00

157 lines
3 KiB
Go

package localfs
import (
"context"
"os"
"path/filepath"
"strings"
"sync"
"time"
wt "github.com/radovskyb/watcher"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
)
type watcher struct {
wt *wt.Watcher
workdir string
ignoreHidden bool
ignoreFolders bool
ignoreExts []string
onChange func()
interval time.Duration
ctx context.Context
done *sync.WaitGroup
}
// WatcherOption used to configure watcher.
type WatcherOption func(*watcher)
// WatcherWorkdir to set as a root to paths needs to be watched.
func WatcherWorkdir(path string) WatcherOption {
return func(w *watcher) {
w.workdir = path
}
}
// WatcherOnChange sets a hook that executed on every change on filesystem.
func WatcherOnChange(hook func()) WatcherOption {
return func(w *watcher) {
w.onChange = hook
}
}
// WatcherPollingInterval overwrites default polling interval to check filesystem changes.
func WatcherPollingInterval(d time.Duration) WatcherOption {
return func(w *watcher) {
w.interval = d
}
}
// WatcherIgnoreHidden ignores hidden(dot) files.
func WatcherIgnoreHidden() WatcherOption {
return func(w *watcher) {
w.ignoreHidden = true
}
}
func WatcherIgnoreFolders() WatcherOption {
return func(w *watcher) {
w.ignoreFolders = true
}
}
// WatcherIgnoreExt ignores files with matching file extensions.
func WatcherIgnoreExt(exts ...string) WatcherOption {
return func(w *watcher) {
w.ignoreExts = exts
}
}
// Watch starts watching changes on the paths. options are used to configure the
// behaviour of watch operation.
func Watch(ctx context.Context, paths []string, options ...WatcherOption) error {
w := &watcher{
wt: wt.New(),
onChange: func() {},
interval: time.Millisecond * 300,
done: &sync.WaitGroup{},
ctx: ctx,
}
w.wt.SetMaxEvents(1)
for _, o := range options {
o(w)
}
w.wt.AddFilterHook(func(info os.FileInfo, fullPath string) error {
if info.IsDir() && w.ignoreFolders {
return wt.ErrSkip
}
if w.isFileIgnored(fullPath) {
return wt.ErrSkip
}
return nil
})
// ignore hidden paths.
w.wt.IgnoreHiddenFiles(w.ignoreHidden)
// add paths to watch
if err := w.addPaths(paths...); err != nil {
return err
}
// start watching.
w.done.Add(1)
go w.listen()
if err := w.wt.Start(w.interval); err != nil {
return err
}
w.done.Wait()
return nil
}
func (w *watcher) listen() {
defer w.done.Done()
for {
select {
case <-w.wt.Event:
w.onChange()
case <-w.wt.Closed:
return
case <-w.ctx.Done():
w.wt.Close()
}
}
}
func (w *watcher) addPaths(paths ...string) error {
for _, path := range paths {
if !filepath.IsAbs(path) {
path = filepath.Join(w.workdir, path)
}
// Ignoring paths that don't exist
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
continue
}
if err := w.wt.AddRecursive(path); err != nil {
return err
}
}
return nil
}
func (w *watcher) isFileIgnored(path string) bool {
for _, ext := range w.ignoreExts {
if strings.HasSuffix(path, ext) {
return true
}
}
return false
}