| 65 | } |
| 66 | |
| 67 | func (w *Watcher) handleEvent(event fsnotify.Event) { |
| 68 | // Filter only relevant events: config file or auth-dir JSON files. |
| 69 | configOps := fsnotify.Write | fsnotify.Create | fsnotify.Rename |
| 70 | normalizedName := w.normalizeAuthPath(event.Name) |
| 71 | normalizedConfigPath := w.normalizeAuthPath(w.configPath) |
| 72 | normalizedAuthDir := w.normalizeAuthPath(w.authDir) |
| 73 | isConfigEvent := normalizedName == normalizedConfigPath && event.Op&configOps != 0 |
| 74 | authOps := fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename |
| 75 | isAuthJSON := filepath.Dir(normalizedName) == normalizedAuthDir && strings.HasSuffix(normalizedName, ".json") && event.Op&authOps != 0 |
| 76 | if !isConfigEvent && !isAuthJSON { |
| 77 | // Ignore unrelated files (e.g., cookie snapshots *.cookie) and other noise. |
| 78 | return |
| 79 | } |
| 80 | |
| 81 | now := time.Now() |
| 82 | log.Debugf("file system event detected: %s %s", event.Op.String(), event.Name) |
| 83 | |
| 84 | // Handle config file changes |
| 85 | if isConfigEvent { |
| 86 | log.Debugf("config file change details - operation: %s, timestamp: %s", event.Op.String(), now.Format("2006-01-02 15:04:05.000")) |
| 87 | w.scheduleConfigReload() |
| 88 | return |
| 89 | } |
| 90 | |
| 91 | // Handle auth directory changes incrementally (.json only) |
| 92 | w.authRescanMu.Lock() |
| 93 | defer w.authRescanMu.Unlock() |
| 94 | |
| 95 | if event.Op&(fsnotify.Remove|fsnotify.Rename) != 0 { |
| 96 | if w.shouldDebounceRemove(normalizedName, now) { |
| 97 | log.Debugf("debouncing remove event for %s", filepath.Base(event.Name)) |
| 98 | return |
| 99 | } |
| 100 | // Atomic replace on some platforms may surface as Rename (or Remove) before the new file is ready. |
| 101 | // Wait briefly; if the path exists again, treat as an update instead of removal. |
| 102 | time.Sleep(replaceCheckDelay) |
| 103 | if _, statErr := os.Stat(event.Name); statErr == nil { |
| 104 | if unchanged, errSame := w.authFileUnchanged(event.Name); errSame == nil && unchanged { |
| 105 | log.Debugf("auth file unchanged (hash match), skipping reload: %s", filepath.Base(event.Name)) |
| 106 | return |
| 107 | } |
| 108 | log.Infof("auth file changed (%s): %s, processing incrementally", event.Op.String(), filepath.Base(event.Name)) |
| 109 | w.addOrUpdateClientLocked(event.Name) |
| 110 | return |
| 111 | } |
| 112 | if !w.isKnownAuthFile(event.Name) { |
| 113 | log.Debugf("ignoring remove for unknown auth file: %s", filepath.Base(event.Name)) |
| 114 | return |
| 115 | } |
| 116 | log.Infof("auth file changed (%s): %s, processing incrementally", event.Op.String(), filepath.Base(event.Name)) |
| 117 | w.removeClientLocked(event.Name) |
| 118 | return |
| 119 | } |
| 120 | if event.Op&(fsnotify.Create|fsnotify.Write) != 0 { |
| 121 | if unchanged, errSame := w.authFileUnchanged(event.Name); errSame == nil && unchanged { |
| 122 | log.Debugf("auth file unchanged (hash match), skipping reload: %s", filepath.Base(event.Name)) |
| 123 | return |
| 124 | } |