Add optimistic concurrency control and atomic file operations

This commit is contained in:
2025-04-27 18:57:46 +08:00
parent 3ab209f899
commit 618bae3afe
3 changed files with 315 additions and 162 deletions

View File

@@ -1,11 +1,13 @@
package services
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"strconv"
"strings"
"time"
"voidraft/internal/models"
)
@@ -16,8 +18,8 @@ type ConfigService struct {
configPath string
rootDir string
homePath string
mutex sync.RWMutex
config *models.AppConfig // 缓存最新配置
config *models.AppConfig
lastModTime time.Time // 上次读取配置文件的修改时间
}
// NewConfigService 创建新的配置服务实例
@@ -40,16 +42,16 @@ func NewConfigService(fileService *FileService) *ConfigService {
rootDir: defaultConfig.Paths.RootDir,
configPath: defaultConfig.Paths.ConfigPath,
homePath: homePath,
config: defaultConfig, // 初始化缓存配置
config: defaultConfig,
}
// 初始化配置目录和文件(非锁定方式)
// 初始化配置目录和文件
service.initializeConfig()
return service
}
// initializeConfig 初始化配置目录和文件,避免死锁
// initializeConfig 初始化配置目录和文件
func (cs *ConfigService) initializeConfig() {
// 确保配置目录存在
dirPath := filepath.Join(cs.homePath, cs.rootDir)
@@ -78,149 +80,251 @@ func (cs *ConfigService) initializeConfig() {
log.Printf("Config file not found, creating default config")
// 创建默认配置文件
defaultConfig := models.NewDefaultAppConfig()
if err := cs.saveAppConfigInitial(defaultConfig); err != nil {
if err := cs.saveAppConfig(defaultConfig); err != nil {
log.Printf("Failed to save default config: %v", err)
}
} else {
// 加载现有配置
log.Printf("Loading existing config file")
existingConfig, err := cs.loadAppConfigInitial()
existingConfig, modTime, err := cs.loadAppConfigWithModTime()
if err != nil {
log.Printf("Failed to load existing config, using default: %v", err)
} else {
cs.config = existingConfig
cs.lastModTime = modTime
}
}
}
// saveAppConfigInitial 初始化时保存配置,不使用互斥锁
func (cs *ConfigService) saveAppConfigInitial(config *models.AppConfig) error {
// 更新配置元数据
config.Metadata.LastUpdated = time.Now()
// 保存到文件
configPath := cs.GetFullConfigPath()
log.Printf("saveAppConfigInitial: Saving to %s", configPath)
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
return fmt.Errorf("failed to save initial config file: %w", err)
}
// 更新内存中的配置
cs.config = config
return nil
}
// loadAppConfigInitial 初始化时加载配置,不使用互斥锁
func (cs *ConfigService) loadAppConfigInitial() (*models.AppConfig, error) {
config := &models.AppConfig{}
configPath := cs.GetFullConfigPath()
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
return nil, fmt.Errorf("failed to load initial config file: %w", err)
}
return config, nil
}
// initConfigDir 确保目录存在,如不存在则创建
func (cs *ConfigService) initConfigDir() error {
configDir := filepath.Join(cs.homePath, cs.rootDir)
return cs.fileService.EnsureDir(configDir)
}
// GetFullConfigPath 获取完整的配置文件路径
func (cs *ConfigService) GetFullConfigPath() string {
return filepath.Join(cs.homePath, cs.rootDir, cs.configPath)
}
// mergeConfigs 合并两个配置,实现智能合并策略
func (cs *ConfigService) mergeConfigs(currentConfig *models.AppConfig, existingData []byte) (*models.AppConfig, error) {
// 解析磁盘配置
var diskConfig models.AppConfig
if err := json.Unmarshal(existingData, &diskConfig); err != nil {
return nil, fmt.Errorf("failed to parse existing config: %w", err)
}
// 创建合并后的配置(基于磁盘配置)
mergedConfig := diskConfig
// ===== 1. 编辑器配置合并策略 =====
// 编辑器配置是用户最直接的操作,总是保留当前配置
log.Printf("Merge: Preserving current editor config")
mergedConfig.Editor = currentConfig.Editor
// ===== 2. 路径配置合并策略 =====
// 如果当前实例已经修改了根目录(非默认值),保留当前配置
defaultConfig := models.NewDefaultAppConfig()
if currentConfig.Paths.RootDir != defaultConfig.Paths.RootDir &&
currentConfig.Paths.RootDir != diskConfig.Paths.RootDir {
log.Printf("Merge: Using custom root directory from current config: %s", currentConfig.Paths.RootDir)
mergedConfig.Paths.RootDir = currentConfig.Paths.RootDir
} else {
log.Printf("Merge: Using root directory from disk config: %s", diskConfig.Paths.RootDir)
}
// 对配置路径采用相同策略
if currentConfig.Paths.ConfigPath != defaultConfig.Paths.ConfigPath &&
currentConfig.Paths.ConfigPath != diskConfig.Paths.ConfigPath {
log.Printf("Merge: Using custom config path from current config: %s", currentConfig.Paths.ConfigPath)
mergedConfig.Paths.ConfigPath = currentConfig.Paths.ConfigPath
} else {
log.Printf("Merge: Using config path from disk config: %s", diskConfig.Paths.ConfigPath)
}
// ===== 3. 元数据合并策略 =====
// 版本号使用较高的版本
if compareVersions(currentConfig.Metadata.Version, diskConfig.Metadata.Version) > 0 {
log.Printf("Merge: Using higher version from current config: %s", currentConfig.Metadata.Version)
mergedConfig.Metadata.Version = currentConfig.Metadata.Version
} else {
log.Printf("Merge: Using version from disk config: %s", diskConfig.Metadata.Version)
}
// 更新时间总是使用当前时间
mergedConfig.Metadata.LastUpdated = time.Now()
// ===== 4. 其他配置项 =====
log.Printf("Merge: Completed using comprehensive merge strategy")
return &mergedConfig, nil
}
// compareVersions 比较两个版本号
// 返回: 1 如果v1>v2, 0 如果v1=v2, -1 如果v1<v2
func compareVersions(v1, v2 string) int {
// 分割版本号
parts1 := strings.Split(v1, ".")
parts2 := strings.Split(v2, ".")
// 确保两个版本号有相同数量的部分
for len(parts1) < len(parts2) {
parts1 = append(parts1, "0")
}
for len(parts2) < len(parts1) {
parts2 = append(parts2, "0")
}
// 逐个比较各部分
for i := 0; i < len(parts1); i++ {
num1, err1 := strconv.Atoi(parts1[i])
num2, err2 := strconv.Atoi(parts2[i])
// 如果有无法解析为数字的部分,按字符串比较
if err1 != nil || err2 != nil {
if parts1[i] > parts2[i] {
return 1
} else if parts1[i] < parts2[i] {
return -1
}
continue
}
// 数值比较
if num1 > num2 {
return 1
} else if num1 < num2 {
return -1
}
}
// 所有部分都相等
return 0
}
// loadAppConfigWithModTime 从文件加载配置,同时返回文件修改时间
func (cs *ConfigService) loadAppConfigWithModTime() (*models.AppConfig, time.Time, error) {
config := &models.AppConfig{}
configPath := cs.GetFullConfigPath()
// 先获取文件修改时间
modTime, err := cs.fileService.GetFileModTime(configPath)
if err != nil {
return nil, time.Time{}, fmt.Errorf("failed to get config file modification time: %w", err)
}
// 然后加载文件内容
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
return nil, time.Time{}, fmt.Errorf("failed to load config file: %w", err)
}
return config, modTime, nil
}
// handleConfigConflict 处理配置冲突的回调函数
func (cs *ConfigService) handleConfigConflict(existingData []byte) (interface{}, error) {
// 如果没有当前配置,无法合并
if cs.config == nil {
return nil, fmt.Errorf("no current config to merge")
}
// 合并配置
mergedConfig, err := cs.mergeConfigs(cs.config, existingData)
if err != nil {
return nil, err
}
// 更新内存中的配置
cs.config = mergedConfig
return mergedConfig, nil
}
// 内部方法:从文件加载配置
func (cs *ConfigService) loadAppConfig() (*models.AppConfig, error) {
config, modTime, err := cs.loadAppConfigWithModTime()
if err != nil {
return nil, err
}
// 更新最后修改时间
cs.lastModTime = modTime
return config, nil
}
// 内部方法:保存配置到文件
func (cs *ConfigService) saveAppConfig(config *models.AppConfig) error {
// 更新配置元数据
config.Metadata.LastUpdated = time.Now()
// 保存到文件,使用乐观并发控制
configPath := cs.GetFullConfigPath()
log.Printf("saveAppConfig: Saving to %s with last mod time: %s",
configPath, cs.lastModTime.Format(time.RFC3339))
err := cs.fileService.SaveJSONWithCheck(
configPath,
config,
cs.lastModTime,
cs.handleConfigConflict,
)
if err != nil {
return fmt.Errorf("failed to save config file: %w", err)
}
// 更新内存中的配置和最后修改时间
cs.config = config
// 获取最新的修改时间
newModTime, err := cs.fileService.GetFileModTime(configPath)
if err == nil {
cs.lastModTime = newModTime
}
return nil
}
// GetAppConfig 获取应用配置
func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) {
cs.mutex.RLock()
defer cs.mutex.RUnlock()
// 返回内存中的配置副本
if cs.config != nil {
return cs.config, nil
}
// 从文件加载
config := &models.AppConfig{}
configPath := cs.GetFullConfigPath()
log.Printf("GetAppConfig: Loading from %s", configPath)
// 如果配置文件存在,则加载
if cs.fileService.FileExists(configPath) {
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
log.Printf("GetAppConfig: Failed to load config: %v", err)
return nil, fmt.Errorf("failed to load config file: %w", err)
}
log.Printf("GetAppConfig: Successfully loaded config")
// 更新内存中的配置
cs.config = config
} else {
// 文件不存在,使用默认配置
log.Printf("GetAppConfig: Config file not found, using default")
config = models.NewDefaultAppConfig()
config, err := cs.loadAppConfig()
if err != nil {
log.Printf("GetAppConfig: Failed to load config: %v", err)
// 使用默认配置
defaultConfig := models.NewDefaultAppConfig()
cs.config = defaultConfig
// 保存默认配置到文件
if err := cs.SaveAppConfig(config); err != nil {
if err := cs.saveAppConfig(defaultConfig); err != nil {
log.Printf("GetAppConfig: Failed to save default config: %v", err)
return nil, fmt.Errorf("failed to save default config: %w", err)
}
return defaultConfig, nil
}
// 更新内存中的配置
cs.config = config
return config, nil
}
// SaveAppConfig 保存应用配置
func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
// 更新配置元数据
config.Metadata.LastUpdated = time.Now()
// 保存到文件
configPath := cs.GetFullConfigPath()
log.Printf("SaveAppConfig: Saving to %s", configPath)
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
log.Printf("SaveAppConfig: Failed to save config: %v", err)
return fmt.Errorf("failed to save config file: %w", err)
}
// 更新内存中的配置
cs.config = config
log.Printf("SaveAppConfig: Successfully saved config")
return nil
return cs.saveAppConfig(config)
}
// UpdateEditorConfig 更新编辑器配置
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
// 如果内存中已有配置,直接更新
if cs.config != nil {
log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig)
cs.config.Editor = editorConfig
// 保存到文件
configPath := cs.GetFullConfigPath()
if err := cs.fileService.SaveJSON(configPath, cs.config); err != nil {
log.Printf("UpdateEditorConfig: Failed to save config: %v", err)
return fmt.Errorf("failed to save config file: %w", err)
}
log.Printf("UpdateEditorConfig: Successfully saved updated config")
return nil
return cs.saveAppConfig(cs.config)
}
// 没有内存中的配置,需要先加载
config, err := cs.loadAppConfigInitial()
config, err := cs.loadAppConfig()
if err != nil {
log.Printf("UpdateEditorConfig: Failed to load config: %v", err)
// 使用默认配置
@@ -230,35 +334,19 @@ func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) er
// 更新编辑器配置
config.Editor = editorConfig
// 更新配置元数据
config.Metadata.LastUpdated = time.Now()
// 保存到文件
configPath := cs.GetFullConfigPath()
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
log.Printf("UpdateEditorConfig: Failed to save config: %v", err)
return fmt.Errorf("failed to save config file: %w", err)
}
// 更新内存中的配置
cs.config = config
log.Printf("UpdateEditorConfig: Successfully saved config with updated editor settings")
return nil
return cs.saveAppConfig(config)
}
// GetEditorConfig 获取编辑器配置
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
cs.mutex.RLock()
defer cs.mutex.RUnlock()
// 如果内存中已有配置,直接返回
if cs.config != nil {
return cs.config.Editor, nil
}
// 否则从文件加载
config, err := cs.loadAppConfigInitial()
config, err := cs.loadAppConfig()
if err != nil {
log.Printf("GetEditorConfig: Failed to load config: %v", err)
// 使用默认配置
@@ -275,24 +363,18 @@ func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
// ResetToDefault 重置为默认配置
func (cs *ConfigService) ResetToDefault() error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
// 创建默认配置
defaultConfig := models.NewDefaultAppConfig()
log.Printf("ResetToDefault: Resetting to default config")
// 保存到文件
configPath := cs.GetFullConfigPath()
if err := cs.fileService.SaveJSON(configPath, defaultConfig); err != nil {
err := cs.saveAppConfig(defaultConfig)
if err != nil {
log.Printf("ResetToDefault: Failed to save default config: %v", err)
return fmt.Errorf("failed to save default config: %w", err)
}
// 更新内存中的配置
cs.config = defaultConfig
log.Printf("ResetToDefault: Successfully reset to default config")
return nil
}