aboutsummaryrefslogtreecommitdiffstats
path: root/cmd_windows.go
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-07 09:20:10 +0200
committerBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-07 09:20:10 +0200
commit0642028735adcb2d431e5e146ca87accb4954900 (patch)
tree882c17b457539239e36f6a27b803e391d66cba6e /cmd_windows.go
parenta0d157d45c5b4c63afc2cfc82bde3ae3207bc455 (diff)
downloadsourcewatch-0642028735adcb2d431e5e146ca87accb4954900.tar.gz
sourcewatch-0642028735adcb2d431e5e146ca87accb4954900.zip
Add Windows-assisted initial sync for faster startup
New protocol: - Windows binary scans directory (fast native NTFS) and sends manifest - Container receives manifest, compares with local /data state - Only copies files that differ (size, modtime, mode, symlink) Protocol messages: - manifest: sends full file metadata from Windows - manifest_done: signals manifest transfer complete - event_create/write/remove: file change events Flow: 1. Container connects to socket 2. Windows binary sends manifest (file metadata) 3. Container scans /data (fast local volume) 4. Container copies only changed files from /host (9P bind mount) 5. Container enters event-waiting loop This dramatically speeds up initial sync by avoiding slow 9P scans.
Diffstat (limited to 'cmd_windows.go')
-rw-r--r--cmd_windows.go165
1 files changed, 137 insertions, 28 deletions
diff --git a/cmd_windows.go b/cmd_windows.go
index b68851a..61c7f1d 100644
--- a/cmd_windows.go
+++ b/cmd_windows.go
@@ -3,8 +3,8 @@
package main
import (
+ "bufio"
"context"
- "encoding/json"
"flag"
"fmt"
"net"
@@ -38,10 +38,12 @@ func run() {
}
SetLogLevel(level)
- logInfo("watcher: starting on %s", *sourceDir)
- logInfo("watcher: serving on %s", *socketPath)
+ logInfo("watcher: scanning %s ...", *sourceDir)
stack := NewGitignoreStack(*sourceDir)
+ manifest := scanDirectory(*sourceDir, stack)
+
+ logInfo("watcher: found %d files, serving on %s", len(manifest.Files), *socketPath)
watcher, err := fsnotify.NewWatcher()
if err != nil {
@@ -86,40 +88,25 @@ func run() {
logDebug("client connected: %s", conn.RemoteAddr())
go func(c net.Conn) {
- defer func() {
- clientsMu.Lock()
- delete(clients, c)
- clientsMu.Unlock()
- c.Close()
- }()
- buf := make([]byte, 1)
- for {
- if _, err := c.Read(buf); err != nil {
- return
- }
+ sendManifest(c, manifest)
+
+ scanner := bufio.NewScanner(c)
+ for scanner.Scan() {
}
+
+ clientsMu.Lock()
+ delete(clients, c)
+ clientsMu.Unlock()
+ c.Close()
}(conn)
}
}()
broadcast := func(evt FileEvent) {
- data, err := json.Marshal(evt)
- if err != nil {
- return
- }
- data = append(data, '\n')
-
clientsMu.Lock()
defer clientsMu.Unlock()
for c := range clients {
- if _, err := c.Write(data); err != nil {
- go func() {
- clientsMu.Lock()
- delete(clients, c)
- clientsMu.Unlock()
- c.Close()
- }()
- }
+ sendEvent(c, evt)
}
}
@@ -205,6 +192,39 @@ func run() {
return
}
+ // Update manifest
+ srcPath := filepath.Join(*sourceDir, path)
+ switch evt.Op {
+ case "create":
+ if info, err := os.Lstat(srcPath); err == nil {
+ linkTarget := ""
+ if info.Mode()&os.ModeSymlink != 0 {
+ linkTarget, _ = os.Readlink(srcPath)
+ }
+ manifest.Files[path] = FileMeta{
+ Size: info.Size(),
+ Mode: info.Mode(),
+ ModTime: info.ModTime(),
+ Symlink: linkTarget,
+ }
+ }
+ case "write":
+ if info, err := os.Lstat(srcPath); err == nil {
+ linkTarget := ""
+ if info.Mode()&os.ModeSymlink != 0 {
+ linkTarget, _ = os.Readlink(srcPath)
+ }
+ manifest.Files[path] = FileMeta{
+ Size: info.Size(),
+ Mode: info.Mode(),
+ ModTime: info.ModTime(),
+ Symlink: linkTarget,
+ }
+ }
+ case "remove":
+ delete(manifest.Files, path)
+ }
+
broadcast(evt)
})
mu.Unlock()
@@ -218,6 +238,95 @@ func run() {
}
}
+func scanDirectory(root string, stack *GitignoreStack) *Manifest {
+ manifest := &Manifest{Files: make(map[string]FileMeta)}
+
+ filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+
+ relPath, err := filepath.Rel(root, path)
+ if err != nil {
+ return nil
+ }
+
+ if relPath == "." {
+ return nil
+ }
+
+ if isGitDir(relPath) {
+ if info.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ if info.IsDir() && hasGitignore(path) {
+ stack.Push(path)
+ }
+
+ if stack.IsIgnored(relPath) {
+ return nil
+ }
+
+ linkTarget := ""
+ if info.Mode()&os.ModeSymlink != 0 {
+ linkTarget, _ = os.Readlink(path)
+ }
+
+ manifest.Files[relPath] = FileMeta{
+ Size: info.Size(),
+ Mode: info.Mode(),
+ ModTime: info.ModTime(),
+ Symlink: linkTarget,
+ }
+
+ return nil
+ })
+
+ return manifest
+}
+
+func sendManifest(conn net.Conn, manifest *Manifest) {
+ msg := &ProtocolMessage{
+ Type: "manifest",
+ Files: manifest.ToJSON(),
+ }
+ data, err := EncodeMessage(msg)
+ if err != nil {
+ return
+ }
+ data = append(data, '\n')
+ conn.Write(data)
+
+ done := &ProtocolMessage{Type: "manifest_done"}
+ doneData, _ := EncodeMessage(done)
+ doneData = append(doneData, '\n')
+ conn.Write(doneData)
+}
+
+func sendEvent(conn net.Conn, evt FileEvent) {
+ msg := &ProtocolMessage{
+ Type: "event",
+ Path: evt.Path,
+ }
+ switch evt.Op {
+ case "create":
+ msg.Type = "event_create"
+ case "write":
+ msg.Type = "event_write"
+ case "remove":
+ msg.Type = "event_remove"
+ }
+ data, err := EncodeMessage(msg)
+ if err != nil {
+ return
+ }
+ data = append(data, '\n')
+ conn.Write(data)
+}
+
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 {