aboutsummaryrefslogtreecommitdiffstats
path: root/watcher.go
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-07 09:03:31 +0200
committerBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-07 09:03:31 +0200
commita0d157d45c5b4c63afc2cfc82bde3ae3207bc455 (patch)
treec6152cf56c437945359a3b80fc16b76e9ba7332a /watcher.go
parente4c2f78abe427d9fdbfb46bb205f2210a03704fa (diff)
downloadsourcewatch-a0d157d45c5b4c63afc2cfc82bde3ae3207bc455.tar.gz
sourcewatch-a0d157d45c5b4c63afc2cfc82bde3ae3207bc455.zip
Add Windows native watcher with Unix socket IPC
Architecture: - Windows binary: watches files via ReadDirectoryChangesW (fsnotify), sends events as newline-delimited JSON over Unix domain socket - Container: performs initial sync, then listens on socket for events from Windows watcher New files: - cmd_windows.go: Windows serve command (watcher + socket server) - cmd_linux.go: Linux socket client + event listener - types.go: shared FileEvent, FileMeta, Manifest types - watcher_test_helper.go: inotify-based watcher for integration tests Changes: - main.go: simplified to just call run() - watcher.go: removed fsnotify dependency, extracted Watcher interface - sync.go: removed duplicate type definitions - watcher_test.go: uses watchForTests() helper Usage: sourcewatch.exe -dir C:\myproject -socket D:\sourcewatch.sock docker run -v D:\sourcewatch.sock:/run/sourcewatch.sock \ -v C:\myproject:/host:ro -v mydata:/data sourcewatch
Diffstat (limited to 'watcher.go')
-rw-r--r--watcher.go142
1 files changed, 12 insertions, 130 deletions
diff --git a/watcher.go b/watcher.go
index 14cc584..1ee3972 100644
--- a/watcher.go
+++ b/watcher.go
@@ -1,102 +1,11 @@
package main
import (
- "context"
"os"
"path/filepath"
- "sync"
- "time"
-
- "github.com/fsnotify/fsnotify"
)
-func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, stack *GitignoreStack, dryRun bool) error {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- return err
- }
- defer watcher.Close()
-
- var mu sync.Mutex
- debounceTimers := make(map[string]*time.Timer)
- debounceOps := make(map[string]fsnotify.Op)
-
- if err := addWatchersRecursively(watcher, hostDir, stack); err != nil {
- return err
- }
-
- for {
- select {
- case <-ctx.Done():
- return nil
-
- case event, ok := <-watcher.Events:
- if !ok {
- return nil
- }
-
- relPath, err := filepath.Rel(hostDir, event.Name)
- if err != nil {
- continue
- }
-
- if isGitDir(relPath) {
- continue
- }
-
- if stack.IsIgnored(relPath) {
- continue
- }
-
- logDebug("event: %s %s", event.Op, relPath)
-
- mu.Lock()
- if existing, exists := debounceOps[relPath]; exists {
- debounceTimers[relPath].Stop()
- debounceOps[relPath] = existing | event.Op
- } else {
- debounceOps[relPath] = event.Op
- }
- path := relPath
- debounceTimers[relPath] = time.AfterFunc(200*time.Millisecond, func() {
- mu.Lock()
- op := debounceOps[path]
- delete(debounceOps, path)
- delete(debounceTimers, path)
- mu.Unlock()
- handleEvent(op, hostDir, dataDir, path, manifest, stack, dryRun, watcher)
- })
- mu.Unlock()
-
- case err, ok := <-watcher.Errors:
- if !ok {
- return nil
- }
- logErrorf("watcher: %v", err)
- }
- }
-}
-
-func handleEvent(op fsnotify.Op, hostDir, dataDir, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun bool, watcher *fsnotify.Watcher) {
- destPath := filepath.Join(dataDir, relPath)
- srcPath := filepath.Join(hostDir, relPath)
-
- switch {
- case op&fsnotify.Create != 0:
- syncCreate(srcPath, destPath, relPath, manifest, stack, dryRun, watcher)
-
- case op&fsnotify.Write != 0:
- syncWrite(srcPath, destPath, relPath, manifest, dryRun)
-
- case op&fsnotify.Remove != 0:
- syncRemove(destPath, relPath, manifest, dryRun)
-
- case op&fsnotify.Rename != 0:
- syncRemove(destPath, relPath, manifest, dryRun)
- }
-}
-
-func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun bool, watcher *fsnotify.Watcher) {
+func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun bool, watcher Watcher) {
info, err := os.Lstat(srcPath)
if err != nil {
if os.IsNotExist(err) {
@@ -110,16 +19,16 @@ func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, stack *Gi
if hasGitignore(srcPath) {
stack.Push(srcPath)
}
- if err := watcher.Add(srcPath); err != nil {
- logErrorf("watch %s: %v", relPath, err)
- }
- filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
- if err != nil || !info.IsDir() || path == srcPath {
+ if watcher != nil {
+ watcher.Add(srcPath)
+ filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil || !info.IsDir() || path == srcPath {
+ return nil
+ }
+ watcher.Add(path)
return nil
- }
- watcher.Add(path)
- return nil
- })
+ })
+ }
if dryRun {
logInfo("[dry-run] would create dir: %s", relPath)
return
@@ -238,33 +147,6 @@ func syncRemove(destPath, relPath string, manifest *Manifest, dryRun bool) {
}
}
-func addWatchersRecursively(watcher *fsnotify.Watcher, root string, stack *GitignoreStack) error {
- return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- relPath, err := filepath.Rel(root, path)
- if err != nil {
- return err
- }
-
- if isGitDir(relPath) {
- if info.IsDir() {
- return filepath.SkipDir
- }
- return nil
- }
-
- if info.IsDir() {
- if hasGitignore(path) {
- stack.Push(path)
- }
- if !stack.IsIgnored(relPath) {
- return watcher.Add(path)
- }
- }
-
- return nil
- })
+type Watcher interface {
+ Add(path string) error
}