mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 20:58:33 +10:00
138 lines
3.1 KiB
Go
138 lines
3.1 KiB
Go
package ccm
|
|
|
|
import (
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/sagernet/fswatch"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
)
|
|
|
|
const credentialReloadRetryInterval = 2 * time.Second
|
|
|
|
func resolveCredentialFilePath(customPath string) (string, error) {
|
|
if customPath == "" {
|
|
var err error
|
|
customPath, err = getDefaultCredentialsPath()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
if filepath.IsAbs(customPath) {
|
|
return customPath, nil
|
|
}
|
|
return filepath.Abs(customPath)
|
|
}
|
|
|
|
func (c *defaultCredential) ensureCredentialWatcher() error {
|
|
c.watcherAccess.Lock()
|
|
defer c.watcherAccess.Unlock()
|
|
|
|
if c.watcher != nil || c.credentialFilePath == "" {
|
|
return nil
|
|
}
|
|
if !c.watcherRetryAt.IsZero() && time.Now().Before(c.watcherRetryAt) {
|
|
return nil
|
|
}
|
|
|
|
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
|
Path: []string{c.credentialFilePath},
|
|
Logger: c.logger,
|
|
Callback: func(string) {
|
|
err := c.reloadCredentials(true)
|
|
if err != nil {
|
|
c.logger.Warn("reload credentials for ", c.tag, ": ", err)
|
|
}
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.watcherRetryAt = time.Now().Add(credentialReloadRetryInterval)
|
|
return err
|
|
}
|
|
|
|
err = watcher.Start()
|
|
if err != nil {
|
|
c.watcherRetryAt = time.Now().Add(credentialReloadRetryInterval)
|
|
return err
|
|
}
|
|
|
|
c.watcher = watcher
|
|
c.watcherRetryAt = time.Time{}
|
|
return nil
|
|
}
|
|
|
|
func (c *defaultCredential) retryCredentialReloadIfNeeded() {
|
|
c.stateAccess.RLock()
|
|
unavailable := c.state.unavailable
|
|
lastAttempt := c.state.lastCredentialLoadAttempt
|
|
c.stateAccess.RUnlock()
|
|
if !unavailable {
|
|
return
|
|
}
|
|
if !lastAttempt.IsZero() && time.Since(lastAttempt) < credentialReloadRetryInterval {
|
|
return
|
|
}
|
|
|
|
err := c.ensureCredentialWatcher()
|
|
if err != nil {
|
|
c.logger.Debug("start credential watcher for ", c.tag, ": ", err)
|
|
}
|
|
_ = c.reloadCredentials(false)
|
|
}
|
|
|
|
func (c *defaultCredential) reloadCredentials(force bool) error {
|
|
c.reloadAccess.Lock()
|
|
defer c.reloadAccess.Unlock()
|
|
|
|
c.stateAccess.RLock()
|
|
unavailable := c.state.unavailable
|
|
lastAttempt := c.state.lastCredentialLoadAttempt
|
|
c.stateAccess.RUnlock()
|
|
if !force {
|
|
if !unavailable {
|
|
return nil
|
|
}
|
|
if !lastAttempt.IsZero() && time.Since(lastAttempt) < credentialReloadRetryInterval {
|
|
return c.unavailableError()
|
|
}
|
|
}
|
|
|
|
c.stateAccess.Lock()
|
|
c.state.lastCredentialLoadAttempt = time.Now()
|
|
c.stateAccess.Unlock()
|
|
|
|
credentials, err := platformReadCredentials(c.credentialPath)
|
|
if err != nil {
|
|
return c.markCredentialsUnavailable(E.Cause(err, "read credentials"))
|
|
}
|
|
|
|
c.absorbCredentials(credentials)
|
|
return nil
|
|
}
|
|
|
|
func (c *defaultCredential) markCredentialsUnavailable(err error) error {
|
|
c.access.Lock()
|
|
hadCredentials := c.credentials != nil
|
|
c.credentials = nil
|
|
c.access.Unlock()
|
|
|
|
c.stateAccess.Lock()
|
|
before := c.statusSnapshotLocked()
|
|
c.state.unavailable = true
|
|
c.state.lastCredentialLoadError = err.Error()
|
|
c.state.accountType = ""
|
|
c.state.rateLimitTier = ""
|
|
shouldInterrupt := c.checkTransitionLocked()
|
|
shouldEmit := before != c.statusSnapshotLocked()
|
|
c.stateAccess.Unlock()
|
|
|
|
if shouldInterrupt && hadCredentials {
|
|
c.interruptConnections()
|
|
}
|
|
if shouldEmit {
|
|
c.emitStatusUpdate()
|
|
}
|
|
|
|
return err
|
|
}
|