Files
FuDie0915 188225fae6 Ð޸´ȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9
¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档
Ð޸´ Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£
Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£

Ñé֤£ºÁ½¸ö Go ģ¿éµIJâÊÔ/vet¡¢¹ؼüģ¿é race¡¢ǰ¶˲âÊÔ/Éú²ú¹¹½¨/Éó¼ơ¢
Go ¿ɴï©¶´ɨÃèÓë macOS/Linux ¿ÉÐÐģ¿齻²æ±àÒë¾ùͨ¹ý¡£
Windows ¸ôÀëÆô¶¯ͨ¹ý£»δÍê³ɵĵã»÷ðÑ̺ÍԭÉúƽ̨Ñé֤¼ûÉó²鱨¸档
2026-11-10 09:59:34 +08:00

219 lines
5.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"context"
"cursor/internal/securefile"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"gopkg.in/yaml.v3"
)
type Store struct {
path string
logsRoot string
mu sync.Mutex
}
type fileSnapshot struct {
exists bool
modTime int64
size int64
}
func NewStore(path string, logsRoot string) *Store {
return &Store{
path: strings.TrimSpace(path),
logsRoot: strings.TrimSpace(logsRoot),
}
}
func (store *Store) Path() string {
if store == nil {
return ""
}
return store.path
}
func (store *Store) LogsRoot() string {
if store == nil {
return ""
}
return store.logsRoot
}
func (store *Store) snapshot() fileSnapshot {
if store == nil || strings.TrimSpace(store.path) == "" {
return fileSnapshot{}
}
info, err := os.Stat(store.path)
if err != nil {
return fileSnapshot{}
}
return fileSnapshot{
exists: true,
modTime: info.ModTime().UnixNano(),
size: info.Size(),
}
}
func (store *Store) Load(_ context.Context) (Config, error) {
if store == nil || strings.TrimSpace(store.path) == "" {
return DefaultConfig(), nil
}
store.mu.Lock()
defer store.mu.Unlock()
data, err := os.ReadFile(store.path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
defaultConfig := DefaultConfig()
if err := store.saveLocked(defaultConfig); err != nil {
return DefaultConfig(), err
}
return withRevision(defaultConfig), nil
}
return DefaultConfig(), fmt.Errorf("读取用户配置失败: %w", err)
}
var current Config
if err := yaml.Unmarshal(data, &current); err != nil {
return DefaultConfig(), fmt.Errorf("解析用户配置失败: %w", err)
}
normalized, err := NormalizeConfig(current)
if err != nil {
return DefaultConfig(), err
}
if shouldPersistNormalizedConfig(data, current, normalized) {
if err := store.saveLocked(normalized); err != nil {
return DefaultConfig(), err
}
}
return withRevision(normalized), nil
}
func (store *Store) Save(_ context.Context, cfg Config) (Config, error) {
if store == nil || strings.TrimSpace(store.path) == "" {
return Config{}, errors.New("配置存储未初始化")
}
normalized, err := NormalizeConfig(cfg)
if err != nil {
return Config{}, err
}
store.mu.Lock()
defer store.mu.Unlock()
if cfg.Revision != "" {
existing, err := store.readCurrentLocked()
if err != nil {
return Config{}, err
}
if withRevision(existing).Revision != cfg.Revision {
return Config{}, ErrConfigConflict
}
normalized.LastAgentModelHash = existing.LastAgentModelHash
}
if err := store.saveLocked(normalized); err != nil {
return Config{}, err
}
return withRevision(normalized), nil
}
func (store *Store) saveLocked(normalized Config) error {
if err := os.MkdirAll(filepath.Dir(store.path), 0o755); err != nil {
return fmt.Errorf("创建用户配置目录失败: %w", err)
}
data, err := yaml.Marshal(normalized)
if err != nil {
return fmt.Errorf("序列化用户配置失败: %w", err)
}
if err := securefile.Write(store.path, data); err != nil {
return fmt.Errorf("保存用户配置失败: %w", err)
}
return nil
}
// writeFileSync 写入并 fsync,保证 rename 前内容已落盘。
func writeFileSync(path string, data []byte) error {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
if _, err := file.Write(data); err != nil {
file.Close()
return err
}
if err := file.Sync(); err != nil {
file.Close()
return err
}
return file.Close()
}
func shouldPersistNormalizedConfig(raw []byte, current Config, normalized Config) bool {
if !yamlHasKey(raw, "backendListenAddr") || !yamlHasKey(raw, "proxyListenAddr") {
return true
}
if current.BackendListenAddr != normalized.BackendListenAddr || current.ProxyListenAddr != normalized.ProxyListenAddr {
return true
}
if current.ProviderStreamIdleTimeout == normalized.ProviderStreamIdleTimeout {
return false
}
return yamlHasKey(raw, "providerStreamIdleTimeout")
}
func yamlHasKey(raw []byte, key string) bool {
var root yaml.Node
if err := yaml.Unmarshal(raw, &root); err != nil {
return false
}
if len(root.Content) == 0 || root.Content[0].Kind != yaml.MappingNode {
return false
}
mapping := root.Content[0]
for index := 0; index+1 < len(mapping.Content); index += 2 {
if mapping.Content[index].Value == key {
return true
}
}
return false
}
func (store *Store) readCurrentLocked() (Config, error) {
data, err := os.ReadFile(store.path)
if errors.Is(err, os.ErrNotExist) {
return DefaultConfig(), nil
}
if err != nil {
return Config{}, err
}
var current Config
if err = yaml.Unmarshal(data, &current); err != nil {
return Config{}, err
}
return NormalizeConfig(current)
}
func (store *Store) updateLastAgentModelHash(value string) (Config, error) {
store.mu.Lock()
defer store.mu.Unlock()
cfg, err := store.readCurrentLocked()
if err != nil {
return Config{}, err
}
cfg.LastAgentModelHash = value
if err = store.saveLocked(cfg); err != nil {
return Config{}, err
}
return withRevision(cfg), nil
}