tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/watchfs/fs.go (about)

     1  package watchfs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/fs"
     7  	"log"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"tractor.dev/toolkit-go/engine/fs/watchfs/watcher"
    13  )
    14  
    15  var Interval time.Duration = 500 * time.Millisecond
    16  
    17  type EventType uint
    18  
    19  const (
    20  	EventError EventType = 1 << iota
    21  	EventCreate
    22  	EventWrite
    23  	EventRemove
    24  	EventRename
    25  	EventChmod
    26  	EventMove
    27  )
    28  
    29  type Event struct {
    30  	Type    EventType
    31  	Path    string
    32  	OldPath string
    33  	Err     error
    34  	fs.FileInfo
    35  }
    36  
    37  func (e Event) String() string {
    38  	if e.Type == EventError {
    39  		return fmt.Sprintf("{Error %s}", e.Err.Error())
    40  	}
    41  	return fmt.Sprintf("{%s %s %s}", map[EventType]string{
    42  		EventCreate: "create",
    43  		EventWrite:  "write",
    44  		EventRemove: "remove",
    45  		EventRename: "rename",
    46  		EventChmod:  "chmod",
    47  		EventMove:   "move",
    48  	}[e.Type], e.Path, e.OldPath)
    49  }
    50  
    51  type Config struct {
    52  	Recursive bool
    53  	EventMask uint
    54  	Ignores   []string
    55  	Handler   func(Event)
    56  }
    57  
    58  func Join(watches ...*Watch) <-chan Event {
    59  	joined := make(chan Event)
    60  	var wg sync.WaitGroup
    61  	wg.Add(len(watches))
    62  	for _, w := range watches {
    63  		go func(ch <-chan Event) {
    64  			for v := range ch {
    65  				joined <- v
    66  			}
    67  			wg.Done()
    68  		}(w.Iter())
    69  	}
    70  	go func() {
    71  		wg.Wait()
    72  		close(joined)
    73  	}()
    74  	return joined
    75  }
    76  
    77  type Watch struct {
    78  	path    string
    79  	cfg     Config
    80  	inbox   chan Event
    81  	unwatch func(*Watch)
    82  }
    83  
    84  // NewWatch is for building your own WatchFS implementation.
    85  // Use the returned chan Event for sending events and the
    86  // chan bool for waiting for a close.
    87  func NewWatch(path string, cfg Config) (*Watch, chan Event, chan bool) {
    88  	inbox := make(chan Event)
    89  	closer := make(chan bool)
    90  	unwatch := func(*Watch) {
    91  		close(closer)
    92  	}
    93  	return &Watch{
    94  		path:    path,
    95  		cfg:     cfg,
    96  		inbox:   inbox,
    97  		unwatch: unwatch,
    98  	}, inbox, closer
    99  }
   100  
   101  func (w *Watch) Iter() <-chan Event {
   102  	return w.inbox
   103  }
   104  
   105  func (w *Watch) Close() {
   106  	w.unwatch(w)
   107  }
   108  
   109  type FS struct {
   110  	fs.FS
   111  	watcher *watcher.Watcher
   112  	watches map[*Watch]struct{}
   113  	mu      sync.Mutex
   114  }
   115  
   116  func New(fsys fs.FS) *FS {
   117  	_, ok := fsys.(fs.StatFS)
   118  	if !ok {
   119  		panic("stat needed on fs")
   120  	}
   121  	return &FS{
   122  		FS:      fsys,
   123  		watcher: watcher.New(fsys),
   124  		watches: make(map[*Watch]struct{}),
   125  	}
   126  }
   127  
   128  func (f *FS) matchWatches(path string) (watches []*Watch) {
   129  	f.mu.Lock()
   130  	defer f.mu.Unlock()
   131  	for w := range f.watches {
   132  		if strings.HasPrefix(path, w.path) {
   133  			watches = append(watches, w)
   134  		}
   135  	}
   136  	return
   137  }
   138  
   139  func (f *FS) startWatcher() {
   140  	go f.watcher.Start(Interval)
   141  	for {
   142  		select {
   143  		case event := <-f.watcher.Event:
   144  			// log.Println(event)
   145  			for _, w := range f.matchWatches(event.Path) {
   146  				// TODO: eventmask + ignores
   147  				e := Event{
   148  					FileInfo: event.FileInfo,
   149  					Path:     event.Path,
   150  					OldPath:  event.OldPath,
   151  					Type: map[watcher.Op]EventType{
   152  						watcher.Create: EventCreate,
   153  						watcher.Write:  EventWrite,
   154  						watcher.Move:   EventMove,
   155  						watcher.Remove: EventRemove,
   156  						watcher.Rename: EventRename,
   157  						watcher.Chmod:  EventChmod,
   158  					}[event.Op],
   159  				}
   160  				if w.cfg.Handler != nil {
   161  					w.cfg.Handler(e)
   162  				} else {
   163  					w.inbox <- e
   164  				}
   165  			}
   166  		case err := <-f.watcher.Error:
   167  			if err == watcher.ErrWatchedFileDeleted {
   168  				log.Println(err)
   169  			} else {
   170  				panic(err)
   171  			}
   172  		case <-f.watcher.Closed:
   173  			return
   174  		}
   175  	}
   176  }
   177  
   178  func (f *FS) unwatch(w *Watch) {
   179  
   180  	if w.cfg.Recursive {
   181  		f.watcher.RemoveRecursive(w.path)
   182  	} else {
   183  		f.watcher.Remove(w.path)
   184  	}
   185  
   186  	f.mu.Lock()
   187  	delete(f.watches, w)
   188  	f.mu.Unlock()
   189  }
   190  
   191  func (f *FS) Watch(name string, cfg *Config) (*Watch, error) {
   192  	if wfs, ok := f.FS.(interface {
   193  		Watch(name string, cfg *Config) (*Watch, error)
   194  	}); ok {
   195  		w, err := wfs.Watch(name, cfg)
   196  		if err == nil || !errors.Is(err, errors.ErrUnsupported) {
   197  			return w, err
   198  		}
   199  	}
   200  
   201  	if !f.watcher.IsRunning() {
   202  		go f.startWatcher()
   203  	}
   204  
   205  	if cfg == nil {
   206  		cfg = &Config{}
   207  	}
   208  
   209  	if cfg.Recursive {
   210  		if err := f.watcher.AddRecursive(name); err != nil {
   211  			return nil, err
   212  		}
   213  	} else {
   214  		if err := f.watcher.Add(name); err != nil {
   215  			return nil, err
   216  		}
   217  	}
   218  
   219  	w := &Watch{
   220  		path:    name,
   221  		cfg:     *cfg,
   222  		inbox:   make(chan Event),
   223  		unwatch: f.unwatch,
   224  	}
   225  
   226  	f.mu.Lock()
   227  	f.watches[w] = struct{}{}
   228  	f.mu.Unlock()
   229  
   230  	return w, nil
   231  }