mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
fix(windows): Improve path handling and file read reliability
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
config.yaml
|
config.yaml
|
||||||
|
*.exe
|
||||||
|
bin/*
|
||||||
docs/*
|
docs/*
|
||||||
logs/*
|
logs/*
|
||||||
conv/*
|
conv/*
|
||||||
|
|||||||
@@ -122,13 +122,14 @@ func main() {
|
|||||||
log.Fatalf("failed to get home directory: %v", errUserHomeDir)
|
log.Fatalf("failed to get home directory: %v", errUserHomeDir)
|
||||||
}
|
}
|
||||||
// Reconstruct the path by replacing the tilde with the user's home directory.
|
// Reconstruct the path by replacing the tilde with the user's home directory.
|
||||||
parts := strings.Split(cfg.AuthDir, string(os.PathSeparator))
|
remainder := strings.TrimPrefix(cfg.AuthDir, "~")
|
||||||
if len(parts) > 1 {
|
remainder = strings.TrimLeft(remainder, "/\\")
|
||||||
parts[0] = home
|
if remainder == "" {
|
||||||
cfg.AuthDir = filepath.Join(parts...)
|
|
||||||
} else {
|
|
||||||
// If the path is just "~", set it to the home directory.
|
|
||||||
cfg.AuthDir = home
|
cfg.AuthDir = home
|
||||||
|
} else {
|
||||||
|
// Normalize any slash style in the remainder so Windows paths keep nested directories.
|
||||||
|
normalized := strings.ReplaceAll(remainder, "\\", "/")
|
||||||
|
cfg.AuthDir = filepath.Join(home, filepath.FromSlash(normalized))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ type Watcher struct {
|
|||||||
lastConfigHash string
|
lastConfigHash string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
authFileReadMaxAttempts = 5
|
||||||
|
authFileReadRetryDelay = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
// NewWatcher creates a new file watcher instance
|
// NewWatcher creates a new file watcher instance
|
||||||
func NewWatcher(configPath, authDir string, reloadCallback func(map[string]interfaces.Client, *config.Config)) (*Watcher, error) {
|
func NewWatcher(configPath, authDir string, reloadCallback func(map[string]interfaces.Client, *config.Config)) (*Watcher, error) {
|
||||||
watcher, errNewWatcher := fsnotify.NewWatcher()
|
watcher, errNewWatcher := fsnotify.NewWatcher()
|
||||||
@@ -298,7 +303,7 @@ func (w *Watcher) reloadClients() {
|
|||||||
// Rebuild auth file hash cache for current clients
|
// Rebuild auth file hash cache for current clients
|
||||||
w.lastAuthHashes = make(map[string]string, len(newFileClients))
|
w.lastAuthHashes = make(map[string]string, len(newFileClients))
|
||||||
for path := range newFileClients {
|
for path := range newFileClients {
|
||||||
if data, err := os.ReadFile(path); err == nil && len(data) > 0 {
|
if data, err := readAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay); err == nil && len(data) > 0 {
|
||||||
sum := sha256.Sum256(data)
|
sum := sha256.Sum256(data)
|
||||||
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
|
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
|
||||||
}
|
}
|
||||||
@@ -327,7 +332,7 @@ func (w *Watcher) reloadClients() {
|
|||||||
|
|
||||||
// createClientFromFile creates a single client instance from a given token file path.
|
// createClientFromFile creates a single client instance from a given token file path.
|
||||||
func (w *Watcher) createClientFromFile(path string, cfg *config.Config) (interfaces.Client, error) {
|
func (w *Watcher) createClientFromFile(path string, cfg *config.Config) (interfaces.Client, error) {
|
||||||
data, errReadFile := os.ReadFile(path)
|
data, errReadFile := readAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay)
|
||||||
if errReadFile != nil {
|
if errReadFile != nil {
|
||||||
return nil, errReadFile
|
return nil, errReadFile
|
||||||
}
|
}
|
||||||
@@ -385,8 +390,38 @@ func (w *Watcher) clientsToSlice(clientMap map[string]interfaces.Client) []inter
|
|||||||
return s
|
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) {
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < attempts; i++ {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
|
if i < attempts-1 {
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
// addOrUpdateClient handles the addition or update of a single client.
|
// addOrUpdateClient handles the addition or update of a single client.
|
||||||
func (w *Watcher) addOrUpdateClient(path string) {
|
func (w *Watcher) addOrUpdateClient(path string) {
|
||||||
|
data, errRead := readAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay)
|
||||||
|
if errRead != nil {
|
||||||
|
log.Errorf("failed to read auth file %s: %v", filepath.Base(path), errRead)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
log.Debugf("ignoring empty auth file: %s", filepath.Base(path))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha256.Sum256(data)
|
||||||
|
curHash := hex.EncodeToString(sum[:])
|
||||||
|
|
||||||
w.clientsMutex.Lock()
|
w.clientsMutex.Lock()
|
||||||
|
|
||||||
cfg := w.config
|
cfg := w.config
|
||||||
@@ -395,24 +430,6 @@ func (w *Watcher) addOrUpdateClient(path string) {
|
|||||||
w.clientsMutex.Unlock()
|
w.clientsMutex.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read file to check for emptiness and calculate hash
|
|
||||||
data, errRead := os.ReadFile(path)
|
|
||||||
if errRead != nil {
|
|
||||||
log.Errorf("failed to read auth file %s: %v", filepath.Base(path), errRead)
|
|
||||||
w.clientsMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(data) == 0 {
|
|
||||||
// Empty file: ignore (wait for a subsequent WRITE)
|
|
||||||
log.Debugf("ignoring empty auth file: %s", filepath.Base(path))
|
|
||||||
w.clientsMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate a hash of the current content and compare with the cache
|
|
||||||
sum := sha256.Sum256(data)
|
|
||||||
curHash := hex.EncodeToString(sum[:])
|
|
||||||
if prev, ok := w.lastAuthHashes[path]; ok && prev == curHash {
|
if prev, ok := w.lastAuthHashes[path]; ok && prev == curHash {
|
||||||
log.Debugf("auth file unchanged (hash match), skipping reload: %s", filepath.Base(path))
|
log.Debugf("auth file unchanged (hash match), skipping reload: %s", filepath.Base(path))
|
||||||
w.clientsMutex.Unlock()
|
w.clientsMutex.Unlock()
|
||||||
|
|||||||
Reference in New Issue
Block a user