internal/library/watch.go

83 lines · 1838 bytes

 1package library
 2
 3import (
 4	"context"
 5	"errors"
 6	"log"
 7	"os"
 8	"path/filepath"
 9	"strings"
10	"time"
11
12	"github.com/fsnotify/fsnotify"
13)
14
15// Watch calls onChange after filesystem activity under root has been quiet for debounce.
16func Watch(ctx context.Context, root string, debounce time.Duration, onChange func()) error {
17	w, err := fsnotify.NewWatcher()
18	if err != nil {
19		return err
20	}
21	if err := addTree(w, root); err != nil {
22		w.Close()
23		return err
24	}
25	go func() {
26		defer w.Close()
27		watchLoop(ctx, w.Events, w.Errors, w.Add, debounce, onChange)
28	}()
29	return nil
30}
31
32// watchLoop debounces events into onChange calls and watches newly created directories.
33// An event overflow means changes were dropped, so it schedules a rescan too.
34func watchLoop(ctx context.Context, events <-chan fsnotify.Event, errs <-chan error, add func(string) error, debounce time.Duration, onChange func()) {
35	timer := time.NewTimer(debounce)
36	timer.Stop()
37	for {
38		select {
39		case <-ctx.Done():
40			return
41		case ev, ok := <-events:
42			if !ok {
43				return
44			}
45			if ev.Has(fsnotify.Create) {
46				if fi, err := os.Stat(ev.Name); err == nil && fi.IsDir() {
47					if err := add(ev.Name); err != nil {
48						log.Printf("watch %s: %v", ev.Name, err)
49					}
50				}
51			}
52			timer.Reset(debounce)
53		case err, ok := <-errs:
54			if !ok {
55				return
56			}
57			log.Printf("watch: %v", err)
58			if errors.Is(err, fsnotify.ErrEventOverflow) {
59				timer.Reset(debounce)
60			}
61		case <-timer.C:
62			onChange()
63		}
64	}
65}
66
67func addTree(w *fsnotify.Watcher, root string) error {
68	if err := w.Add(root); err != nil {
69		return err
70	}
71	entries, err := os.ReadDir(root)
72	if err != nil {
73		return err
74	}
75	for _, e := range entries {
76		if e.IsDir() && !strings.HasPrefix(e.Name(), ".") {
77			if err := w.Add(filepath.Join(root, e.Name())); err != nil {
78				return err
79			}
80		}
81	}
82	return nil
83}