refactor(auth): Centralize auth file reading with snapshot preference

The logic for reading authentication files, which includes retries and a preference for cookie snapshot files, was previously implemented locally within the `watcher` package. This was done to handle potential file locks during writes.

This change moves this functionality into a shared `ReadAuthFileWithRetry` function in the `util` package to promote code reuse and consistency.

The `watcher` package is updated to use this new centralized function. Additionally, the initial token loading in the `run` command now also uses this logic, making it more resilient to file access issues and consistent with the watcher's behavior.
This commit is contained in:
hkfires
2025-09-20 00:14:26 +08:00
parent 1d7abc95b8
commit aba719f5fe
3 changed files with 52 additions and 44 deletions

View File

@@ -9,7 +9,6 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io/fs"
"net/http"
"os"
@@ -324,7 +323,7 @@ func (w *Watcher) reloadClients() {
// Rebuild auth file hash cache for current clients
w.lastAuthHashes = make(map[string]string, len(newFileClients))
for path := range newFileClients {
if data, err := readAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay); err == nil && len(data) > 0 {
if data, err := util.ReadAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay); err == nil && len(data) > 0 {
sum := sha256.Sum256(data)
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
}
@@ -353,7 +352,7 @@ func (w *Watcher) reloadClients() {
// createClientFromFile creates a single client instance from a given token file path.
func (w *Watcher) createClientFromFile(path string, cfg *config.Config) (interfaces.Client, error) {
data, errReadFile := readAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay)
data, errReadFile := util.ReadAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay)
if errReadFile != nil {
return nil, errReadFile
}
@@ -416,48 +415,9 @@ func (w *Watcher) clientsToSlice(clientMap map[string]interfaces.Client) []inter
return s
}
// readAuthFileWithRetry attempts to read the auth file multiple times to work around
// short-lived locks on Windows while token files are being written.
func readAuthFileWithRetry(path string, attempts int, delay time.Duration) ([]byte, error) {
read := func(target string) ([]byte, error) {
var lastErr error
for i := 0; i < attempts; i++ {
data, err := os.ReadFile(target)
if err == nil {
return data, nil
}
lastErr = err
if i < attempts-1 {
time.Sleep(delay)
}
}
return nil, lastErr
}
candidates := []string{
util.CookieSnapshotPath(path),
path,
}
for idx, candidate := range candidates {
data, err := read(candidate)
if err == nil {
return data, nil
}
if errors.Is(err, os.ErrNotExist) {
if idx < len(candidates)-1 {
continue
}
}
return nil, err
}
return nil, os.ErrNotExist
}
// addOrUpdateClient handles the addition or update of a single client.
func (w *Watcher) addOrUpdateClient(path string) {
data, errRead := readAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay)
data, errRead := util.ReadAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay)
if errRead != nil {
log.Errorf("failed to read auth file %s: %v", filepath.Base(path), errRead)
return