Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
157 lines
3 KiB
Go
157 lines
3 KiB
Go
package localfs
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
wt "github.com/radovskyb/watcher"
|
|
|
|
"github.com/ignite/cli/v29/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
|
|
}
|