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