1package watcher
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strconv"
9 "strings"
10
11 "arche/internal/store"
12
13 "github.com/fsnotify/fsnotify"
14 "golang.org/x/sys/unix"
15)
16
17const PidFile = "watch.pid"
18
19func IsActive(archeDir string) bool {
20 data, err := os.ReadFile(filepath.Join(archeDir, PidFile))
21 if err != nil {
22 return false
23 }
24
25 pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
26 if err != nil {
27 return false
28 }
29
30 return unix.Kill(pid, 0) == nil
31}
32
33func Run(ctx context.Context, workRoot, archeDir string, st store.Store) error {
34 if err := os.WriteFile(
35 filepath.Join(archeDir, PidFile),
36 []byte(strconv.Itoa(os.Getpid())),
37 0o644,
38 ); err != nil {
39 return fmt.Errorf("write pid: %w", err)
40 }
41 defer os.Remove(filepath.Join(archeDir, PidFile))
42
43 w, err := fsnotify.NewWatcher()
44 if err != nil {
45 return fmt.Errorf("fsnotify: %w", err)
46 }
47 defer w.Close()
48
49 if err := filepath.Walk(workRoot, func(p string, info os.FileInfo, walkErr error) error {
50 if walkErr != nil || !info.IsDir() {
51 return nil
52 }
53 if filepath.Base(p) == ".arche" {
54 return filepath.SkipDir
55 }
56 return w.Add(p)
57 }); err != nil {
58 return fmt.Errorf("watch setup: %w", err)
59 }
60
61 for {
62 select {
63 case <-ctx.Done():
64 return nil
65
66 case event, ok := <-w.Events:
67 if !ok {
68 return nil
69 }
70 const interesting = fsnotify.Write | fsnotify.Create | fsnotify.Rename | fsnotify.Remove | fsnotify.Chmod
71 if event.Op&interesting == 0 {
72 continue
73 }
74 rel, err := filepath.Rel(workRoot, event.Name)
75 if err != nil || strings.HasPrefix(rel, "..") || strings.HasPrefix(rel, ".arche") {
76 continue
77 }
78 rel = filepath.ToSlash(rel)
79 if markErr := st.MarkWCacheDirty(rel); markErr != nil {
80 fmt.Fprintf(os.Stderr, "arche watch: mark dirty %q: %v\n", rel, markErr)
81 }
82
83 if event.Op&fsnotify.Create != 0 {
84 if info, statErr := os.Lstat(event.Name); statErr == nil && info.IsDir() {
85 _ = w.Add(event.Name)
86 }
87 }
88
89 case _, ok := <-w.Errors:
90 if !ok {
91 return nil
92 }
93 }
94 }
95}