diff options
| author | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2026-07-07 09:03:31 +0200 |
|---|---|---|
| committer | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2026-07-07 09:03:31 +0200 |
| commit | a0d157d45c5b4c63afc2cfc82bde3ae3207bc455 (patch) | |
| tree | c6152cf56c437945359a3b80fc16b76e9ba7332a /watcher.go | |
| parent | e4c2f78abe427d9fdbfb46bb205f2210a03704fa (diff) | |
| download | sourcewatch-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.go | 142 |
1 files changed, 12 insertions, 130 deletions
@@ -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 } |
